---
title: "Platform Updates — July 16–21, 2026"
slug: "platform-update-2026-07-21"
date: "2026-07-21"
tag: "Application"
summary: "14 production image releases (1.87.0 → 1.102.0): granular edX course-creation tools, next-intl internationalization, a Chrome side-panel extension, an LMS sidebar and navbar revamp, and an independent second OpenID SSO backend."
author: "ibl.ai Engineering"
---

Production image releases `1.87.0` → `1.102.0` (14 releases, Jul 16–21, 2026).

| service | image / pin | version span |
|---|---|---|
| dm | `iblai-dm-pro` | `4.307.0-ai` → `4.313.0-ai` |
| os | `iblai-os-spa` | `0.95.8` → `0.98.6` |
| lms | `iblai-lms-spa` | `0.46.0` → `0.50.0` |
| edx | `iblai-edx-pro` | `sumac.2.40.1` → `sumac.2.41.0` |
| cli | `ibl-cli` | `5.50.0` → `5.51.0` |

---

## `iblai-dm-pro` — `4.307.0-ai` → `4.313.0-ai`

**Granular course-creation tools.** The agent's Course Creation toolkit gained per-object tools so an agent can build and inspect an edX course piece by piece instead of only delegating to the background pipeline: `push_course_outline`, `create_structural_block`, `create_html_component`, `create_multiple_choice_component`, `create_mentor_component` (an optionally-graded agent wired into a unit), `get_unit_components`, `get_component_content`, `get_course_details` (by db id or xblock id), and `delete_xblock`. The delegating tools now state in their descriptions that they hand the task to a subagent.

**Course naming is now the model's job.** `create_full_course_on_edx` pushes section, subsection and unit names verbatim — no more injected `Module N:` prefixes or `N.M` subsection numbering. Only the unit `type` prefix, which the content-drafting pipeline consumes, is still applied.

**Agent skills are referenceable by the deep graph agent.** The RAG `dynamic_prompt` middleware ran inside deepagents' `SkillsMiddleware` and replaced the whole system message, discarding the skills listing that progressive disclosure depends on. The system prompt is now rendered eagerly and passed via `create_deep_agent(system_prompt=...)`, so the skills listing appends to it; skill files are built with `create_file_data` and seeded under the absolute `/skills/` path.

**Longer agent runs.** Graph-agent default recursion limit raised to 300, and to 750 with deep research, for long multi-step tasks such as course creation.

**Web crawler tuning.** Playwright navigation timeout is now a global config setting, `WEBCRAWLER_PLAYWRIGHT_TIMEOUT_SECONDS`, overridable per tenant and defaulting to 30s — a slow-site tenant can be given more headroom without slowing crawls for everyone else. The crawler now navigates with `wait_until="domcontentloaded"` rather than waiting on every sub-resource (images, trackers, chat widgets), which makes JavaScript- and marketing-heavy sites crawl reliably. Default concurrency drops from 15 to 5 and the page limit from 1000 to 200, since each concurrent unit is a full Chromium page; both remain per-tenant overridable on the `DocumentEmbedding` model.

**Chat privacy.** Sessions with chat history disabled now use a throwaway in-memory checkpointer, so graph state is never persisted for a session that opted out. `IBLChatMessageHistory` gained `should_commit_history()` / `ashould_commit_history()`, which return `False` when the resolved chat-privacy mode is `DISABLED`, letting callers with their own persistence layer skip it.

**Cost attribution and tracing.** Langfuse traces emitted from bare `llm.invoke()` / `ainvoke()` paths — title generation, topic generation, guided-prompt generation, agent summary, memory component, quiz customization, moderation and the edX course assistant — now carry `platform_key`, `user` and `session`, so usage is attributable per platform. Every trace additionally carries new `mentor_provider` and `mentor_model` metadata (the agent's configured LLM provider and model), so cost can be grouped or filtered by provider or model; this is surfaced through a Langfuse custom dashboard grouped by `metadata.platform_key`. Health-check agents (`fake-llm-mentor`, `fake-embedding-llm-mentor`) are excluded from tracing on both the chat and embedding paths, keyed on `mentor.unique_id`, across the LangChain, Google-ADK, A2A and OpenAI-assistant runners; set `ENABLE_LANGFUSE_FOR_FAKE_MENTORS` to opt back in for load testing.

**Deployment.** No migrations. `WEBCRAWLER_PLAYWRIGHT_TIMEOUT_SECONDS` defaults to 30s, so existing crawler behavior is preserved without action. To raise it for one tenant, add a Global Configuration row in the DM Django admin with that key, the value in seconds, and Platform set to the tenant key — leaving Platform empty changes the global default. The override takes effect on that tenant's next document retrain or crawl.

## `iblai-os-spa` — `0.95.8` → `0.98.6`

**Internationalization.** The app is now internationalized with `next-intl`, with dynamic rendering so cookie-based locale selection doesn't break the build.

**Chrome side-panel extension.** New extension hosting the `<agent-ai>` element in the browser side panel.

**Chat.** Pasted text and files are converted into uploads via a new clipboard file-extraction helper; attachment chips show the full MIME type; chat history paginates with infinite scroll-up; a `NEXT_PUBLIC_MAXIMUM_CHARACTER_SIZE_TO_COPY` env var sets the paste-to-file threshold, with empty-string values treated as unset. Thought-process steps split on bold delimiters.

**Access control.** New dataset curator viewer role on the Access tab.

**Tenancy.** The tenant switcher now appears when a user belongs to tenants other than main.

**Voice.** The text-to-speech endpoint is streamable.

**Model manager.** Installation status repolls, and program installations have fallbacks.

**Desktop.** macOS DMG release added, and the app icon is rounded into a native macOS squircle.

**SEO.** New SEO foundation — robots, sitemap, manifest, metadata and JSON-LD. Public agent pages and the explore directory are indexable with rich metadata; shared chat pages emit rich OG cards but are noindexed; SEO fetches are never cached, and explore metadata lists only public agents. Copy says "Agents" rather than "Mentors", with a screenshot OG fallback.

**Markdown rendering.** LaTeX preprocessing was extracted into its own module, and `$…$` styling wrappers emitted by the LLM are unwrapped so they render as prose.

**Other.** `iblai-js` bumped to `1.20.1`; user phone support is behind an env flag; the embed snippet dropped a redundant auth redirect.

## `iblai-lms-spa` — `0.46.0` → `0.50.0`

**Sidebar and navbar revamp.** The sidebar and top navbar were restructured around the shared `PlatformSidebar` component, with amber accents replaced by blue and reduced font weights, followed by an optimization pass.

**Tenancy.** Tenant switcher shown when a learner is enrolled on tenants other than main.

**Agent course mode.** New media list dropdown.

**Assessment mode.** Fullscreen is no longer clipped, and an informative popover was added.

**Navigation copy.** Course display name is preferred over title in the navbar, and empty-state labels were corrected.

**Desktop.** macOS DMG release workflow with independent Tauri versioning and auto-bump (app `v1.0.15`); the SSO auth session callback handler was restored.

## `iblai-edx-pro` — `sumac.2.40.1` → `sumac.2.41.0`

**Second OpenID SSO backend gets an independent settings namespace.** As shipped in 2.40.0, `IBLOpenIDTwoBackend` was a thin subclass that inherited every value from the shared `IBL_OPENID_SSO_*` configuration, so editing provider one's endpoint, field keys, JWKS URI, JWT decode options or id-token-field behavior changed provider two too. The second backend now reads its own `IBL_OPENID_SSO_TWO_*` namespace and the two providers share no configuration value.

Implementation bumps `ibl-edx-base-oauth-sso-backend-app` `1.9.0` → `1.10.0`. `auth_openid_multi.py` is a behavioral clone of `auth_openid.py`, byte-identical in its class body and differing only in config source and class name; `config.py` defines a one-for-one `OPENIDTWO_*` block reading `IBL_OPENID_SSO_TWO_*` with its own defaults. The working base backend is untouched — independence is achieved by cloning, not by modifying the shared base. `MultiConfigOpenIDMixin` is retired, its per-config OIDC discovery folded into the clone's `oidc_config`, which resolves `OIDC_ENDPOINT` via `self.setting(...)` so each provider's own `OAuth2ProviderConfig.other_settings` still overlays ahead of the namespace default. Client key and secret continue to come from each backend's own `OAuth2ProviderConfig`.

**Deployment.** Shared `apps/` package change — picked up on image rebuild plus LMS restart (`ibl edx config && ibl edx restart`), not live.

## `ibl-cli` — `5.50.0` → `5.51.0`

**Registers and configures the second OpenID SSO backend.** Open edX keys each `OAuth2ProviderConfig` by `(site_id, backend_name)`, so a second config under the same backend name supersedes the first instead of coexisting. `IBLOpenIDTwoBackend` therefore runs alongside the existing `openid-oauth2` provider under its own name, `openidtwo-oauth2`. The `ibl-edx-base-oauth-sso-backend` Tutor plugin template renders the full `IBL_OPENID_SSO_TWO_*` namespace into `openedx-common-settings` — a one-for-one parallel of `IBL_OPENID_SSO_*` covering `NAME`, `OIDC_ENDPOINT`, `USE_IDP_USERNAME`, the username/fullname/email/first-name/last-name keys, `ID_TOKEN_ISSUER`, the access-token, authorization, revoke-token, userinfo and JWKS URLs, `JWT_DECODE_OPTIONS` and `USE_IDTOKEN_FIELDS` — and appends the backend's dotted path to `THIRD_PARTY_AUTH_BACKENDS`.

**Operator steps.** Set the `IBL_OPENID_SSO_TWO_*` keys in `config.yml`, create one `OAuth2ProviderConfig` per backend name, regenerate config, and restart edX. Per-deployment secrets (client key and secret, optionally `OIDC_ENDPOINT` / `platform_key`) still come from each backend's own provider config row, overlaid ahead of these defaults. Requires ibl-edx-pro `ibl-base-oauth-sso-backend` >= `1.10.0`.
