Production image releases 1.25.1 → 1.61.1 (74 releases, June 2026).
| service | image / pin | version span |
|---|---|---|
| dm | iblai-dm-pro | 4.267.1 → 4.294.1-ai |
| os | iblai-os-spa | 0.70.1 → 0.88.2 |
| lms | iblai-lms-spa | 0.36.0 → 0.43.2 |
| edx | iblai-edx-pro | sumac.2.32.0 → sumac.2.36.0 |
| cli | iblai/infra-cli | 1.11.0 → 1.12.0 |
iblai-dm-pro — 4.267.1 → 4.294.1-ai
Text-to-speech grew up. Chat-message TTS now streams audio from the provider so playback starts before the whole clip is encoded, and messages over 4000 characters are split on sentence boundaries and re-encoded through a single lameenc MP3 encoder into one gapless stream (new lameenc==1.8.2 dependency). Agents gained a voice_instructions field that controls the reading style — passed to OpenAI as the speech instructions (switching synthesis to gpt-4o-mini-tts) and to Google as the synthesis prompt. Google TTS moved to the Gemini TTS model (gemini-3.1-flash-tts-preview), auto-detecting language from the text via langdetect. Message text is HTML-stripped before synthesis via strip_html_tags, which preserves accessibility content (alt, aria-label, title) so it is spoken rather than dropped, and drops aria-hidden="true" elements so screen-reader-hidden content is never read aloud.
Emoji and symbol handling for spoken output was iterated to a stable rule. An initial pass rewrote decorative emoji and symbols into their spoken names before synthesis (new emoji==2.15.0 dependency); a follow-up narrowed this so only emoji that belong to a math equation are kept and spoken (inside delimited LaTeX/markdown maths such as $...$, \(...\), or next to a math operator), while every other decorative emoji is stripped. The unicodedata symbol-name fallback was removed so math symbols (→, ∑, ≈) pass through untouched instead of being mangled into verbose Unicode names. Authorization for TTS is a plain session-ownership check — only the session's student may synthesize its messages (403 otherwise).
Privacy mode became a first-class, fully-featured chat state. The PII privacy router (Mentor.privacy_action) gained an allow option alongside redact/mask/block: detected PII is passed to the LLM unchanged but still written to ModerationLog for audit (migration 0327_alter_mentor_privacy_action; default stays redact). More significantly, DISABLED (privacy-mode) sessions — which previously lost all context between turns because history writes short-circuited — now retain in-session context via an ephemeral, Redis-only conversation history (24h TTL, size-bounded, refreshed each turn, never persisted to Postgres). IBLChatMessageHistory writes append to the cache and all four read paths read from it; get_guided_prompts falls back to it when there are no history rows. Because session-driven privacy severs Session.student (student=None), a series of fixes restored the authenticated owner across the affected surfaces: chat file uploads and the guided-prompts endpoint, artifact reads (via a short-lived username-keyed ephemeral grant cache covering retrieve/versions/version_detail only), TTS for student-less messages (addressed by generation_id, with the /tts/ route converter widened from to ), and voice calls (the call-credentials endpoint now issues credentials for student-less privacy sessions and the voice path skips the per-student cloned-voice lookup). A separate fix made chat privacy mode work with RBAC-enabled non-anonymous agents by falling back to flow.username for policy evaluation.
Per-agent chat-history and RAG tuning replaced hardcoded limits. Three new nullable Mentor columns — chat_history_max_tokens, chat_history_max_messages, and rag_retrieval_k — make retention and main-RAG k configurable per agent (migration 0332_mentor_chat_and_rag_limits), each coalescing NULL to a module default (15000 tokens / 20 messages / k=4) so existing rows are unchanged; the default token cap was raised from the prior hardcoded 2000 to 15000. IBLChatMessageHistory now enforces a dual cap (token budget AND message count) across all four read entry points, resolved per-agent via _resolve_history_caps, and the main-RAG retriever folds the resolved k into search_kwargs via _get_retriever_search_kwargs. Separately, chat-time payload.metadata (product group, state, level, etc.) is now folded into the RAG query string sent to the vector store so client-supplied metadata influences retrieval.
The page-content HTML-stripping behavior converged on a per-agent toggle. Whether the current web page's raw HTML (payload.page_content) reaches the LLM went through several revisions — stop stripping (so inline questions like "what does the Submit button do?" work), then a revert (raw HTML was bloating prompts past the context window), then a tenant-wide STRIP_PAGE_CONTENT_HTML setting with an admin endpoint — before settling on a per-agent Mentor.strip_page_content_html column (migration 0326, default False). The tenant-wide setting, its PageContentConfigView admin endpoint, and serializer were removed; should_strip_page_content_html now resolves the toggle from the session's agent. strip_html was also hardened to preserve and explicitly surface accessibility names ([alt: ...], [aria-label: ...], [title: ...]) while decomposing / blocks entirely.
Agent-driven course creation shipped end to end. A CourseCreationToolkit (registered as "Course Creation" in TOOL_MAPPING, replacing the old course_stepwise toolkit) lets an agent create a draft course, generate and push the outline, schedule background content drafting, and publish to edX. A component-type system (tutorial / assessment / essay assessment ComponentType enum) dispatches per-type drafters and encodes the type in the edX unit name via reserved prefixes. Graded assessment units provision a per-unit grading agent (Mentor + MentorGraderConfiguration + GraderCriterion rubric) and wire an ibl_mentor_xblock into the unit, with a structured multiple-choice schema (AssessmentChoice/AssessmentQuestion/AssessmentComponentOutput) enforcing choice bounds and exactly-one-correct. The create_course Celery task atomically claims its row to prevent duplicate runs, bounds retries via an attempts counter (max 3), and runs on the AI-agent queue with 4h/4.5h time limits; idempotency guards skip drafting when edX already has the outline/unit/component. Course-creation responses now surface the real edX course_id (course-v1:org+number+run) instead of the database PK, and a new embed_show_canvas boolean controls whether a display canvas is shown when an agent is embedded on another page.
The Claw / OpenClaw gateway expanded into a full management surface. OpenClawClient gained cron, MCP-server, and config-management RPCs, unified under a new BaseClawClient abstract parent, and exposed through REST endpoints scoped to a ClawInstance under /api/ai-mentor/orgs/: config/ (merge-patch of openclaw.json with a __OPENCLAW_REDACTED__ secret sentinel and baseHash optimistic concurrency), mcp-servers/ CRUD, cron-jobs/ create/list/run with discriminated schedule.kind and payload.kind unions, cron-runs/, cron-status/, and wake/ one-shot text injection. Dispatch runs the async claw client inline under asyncio.run + asyncio.wait_for, mapping outcomes to 200/201/204, 501 for unsupported claw types (e.g. cron on IronClaw), 502 for worker errors, and 504 for upstream timeouts, with full @extend_schema OpenAPI annotations and a 24-request Postman collection. Later releases added an agent skills library with three-tier scoping and per-agent skill push (recording push status on ClawMentorConfig), a cert_expires_at field on ClawInstance for TLS near-expiry checks, and improved Claw worker health checks, routing, device pairing, skill cleanup, and DM-initiated worker upgrades.
Agents gained a segment grouping. A new Segment model (name, description) and a nullable Mentor.segment FK (migration 0328_segment_mentor_segment) group agents by vertical, with initial segments — Legal, HealthCare, Financial Services, Government, Small Business, Enterprise, K-12, and Higher Education — seeded via migration and the seed_mentor_metadata command.
Credential storage moved to encryption-at-rest. Credential reads now treat encrypted columns as the sole source of truth: FIELD_ENCRYPTION_KEY is required at startup, writes update only encrypted fields, plaintext fallback paths were removed, and migration 0323_backfill_encrypted_credentials backfills encrypted columns. Deployments must configure a stable FIELD_ENCRYPTION_KEY before starting 4.269.0-ai and must not rotate it during rollout.
RBAC was re-architected to be principal-agnostic. dl-core-app 3.0.0 made RbacEnforcer policy-source agnostic — it now takes a PolicySource (e.g. UserPolicySource(user_pk)) instead of a bare user_pk, with a new core/rbac_policy_sources.py holding the PolicySource protocol, UserPolicySource, a deny-all NullPolicySource, and policy_source_for() dispatch. As a security consequence, non-user principals resolve to a deny-all source so a Django auth-user pk can no longer collide into another principal's policies. Four new composable global roles — Limited Editor, Limited Viewer, Dataset Curator, Global Analytics Viewer — were added (seeded via idempotent seed_rbac_data), along with optional description fields on RbacRole/RbacPolicy. A PlatformApiKey.mode field (owner default or token_policies) lets a key derive permissions from policies attached directly to the token; token_policies keys are denied by default and only accepted by views that explicitly enable them through RbacAuthGate.
Consolidated tokens and server-to-server provisioning matured. A tenant-scoped PlatformApiKey can mint a consolidated proxy token for users on its own platform — existing users via consolidated-token/proxy/, new users via the new consolidated-token/provision/ (with edX as the source of truth for identity) — without DM-admin credentials, gated behind IBL-managed flags. The provision endpoint later also returns an edX JWT (edx_jwt_token), minted server-to-server from edX via the new EDX_REQUEST_JWT_PATH setting.
Billing and monetization were restructured around Stripe product IDs and a throttle-tiers admin. A new Throttle tiers admin (with global free/free_trial/credits/enterprise tiers and per-platform overrides) replaced six legacy throttle-tier dynamic settings, finalized by a cleanup_legacy_throttle_settings management command. CreditAccount.current_plan/previous_plan are now stored as stable Stripe product IDs (current_plan_id/previous_plan_id, indexed; migration 0091 backfills and drops the legacy columns), resolving display names against the Throttle tiers registry and making Stripe webhook redeliveries idempotent. A canonical unique_id-keyed billing surface (/api/billing/items/ plus price-UUID endpoints) lets clients carry one id instead of the nested (platform_key, item_type, item_id) triplet, backed by per-request resolution mixins; billing/urls.py was split into a modular package and duplicate root-mounting of billing/crm was removed to clean up the OpenAPI document. Vercel and Apify were seeded as integration credential providers.
Per-tenant platform migration landed on a vendored import/export manager. ibl-dm-import-export-manager was vendored into the monorepo and enabled in DM_APPS, then gained per-tenant platform migration (JSONL export/import, S3-backed bundle storage, resume, deduplication, and admin/REST/management-command interfaces via new PlatformMigrationRun and ImportIdMap models, gated behind PLATFORM_MIGRATION_ENABLED). A MANIFEST.in fix made the admin templates load in git-less Docker builds, and the Phase 7 redaction audit was corrected to allowlist the non-secret connected_services.jsonl -> token_type (OAuth scheme, not a credential) so clean exports stop failing closed.
CRM error handling, notification routing, and copy were cleaned up. OrganizationSerializer/PersonSerializer now translate uniqueness clashes into 400 field errors instead of 500s, including the race-window IntegrityError path. CRM notification types (CRM_PERSON_CREATED, CRM_DEAL_STAGE_CHANGED, CRM_PERSON_LINKED_TO_USER) moved from learning to nurturing notifications so they route through the centralized nurturing SMTP server, with their template context variables now declared. Separately, a large main+GLOBAL notification copy pass reworded "mentor" → "agent" and MentorAI → ibl.ai across 38 template/util files (placeholders, tags, and the mentor.iblai.app host preserved), with a light de-education pass. A notification-templates tag-filter 500 (missing tags on CustomNotificationTemplateAdapter) was fixed, and custom notifications gained a metadata JSONField sourcing their tags.
Models, health checks, and observability. The deprecated gemini-2.0-flash default was replaced by a deployment-configurable gemini-3-flash-preview via a new DEFAULT_GOOGLE_FLASH_MODEL setting threaded through every hardcoded call site, and GPT_5_MODELS was expanded with current gpt-5.x identifiers. Guided-prompt generation was split into SystemMessage + HumanMessage so it no longer trips Azure's jailbreak prompt shield. The agent health check now exercises the real chat-write path (a rolled-back Session/ChatMessageHistory insert probe) so schema/code skew surfaces as 503 instead of staying green. Memsearch auto-capture was fixed to honor model defaults on first access by switching four reads to aget_or_create. Content analytics (/api/analytics/content/) now always includes active_courses/active_programs in platform-scoped totals, with active_courses counting distinct courses that have an active enrollment. Template-agent lookups got a partial index mentor_template_lookup_idx (built CONCURRENTLY) to stop sequential-scanning a tenant's agent table, and mentor-with-settings was fixed to honor forkable_with_training_data and to tear down orphaned agents when post-create serialization fails.
Google Drive ingestion scope was narrowed, then reverted. The document-ingestion OAuth scope was tightened from drive.readonly to drive.file to match Google's verification guidance, but this broke ingestion of existing user-owned files, so it was reverted to drive.readonly; tenants that re-authorized under drive.file must re-issue tokens.
Security hardening. An Orca CVE remediation pass on the DM image raised patched versions for prod-confirmed-open dependencies — cryptography 49.0.0 (with cffi 2.0.0 / pyOpenSSL 26.0.0 / PyNaCl 1.6.2), urllib3 2.7.0, aiohttp 3.14.1, authlib 1.7.2, lxml 6.1.1, pypdf 6.14.2, python-multipart, ujson, GitPython, jwcrypto, and requests — un-pinning requests in ibl-dm-axd-app so the bump resolves and fixing a runtime-pin clobber. A late fix to RecentlyAccessedMentorsView restored cross-tenant visibility of starred/recent public agents that live on the main tenant, evaluating the RBAC filter against the main platform's resource path.
iblai-os-spa — 0.70.1 → 0.88.2
The OS SPA is the renamed agent app. The spa.agent surface was renamed end-to-end to os (served at os.), sharing the iblai-mentor-spa-pro image, and the in-app product name changed from agentAI to OS with iblai.org default URLs migrated to iblai.app; backward-compat 301 redirects route agent.ibl.ai → os.ibl.ai.
Streaming reasoning and tool-call visibility. The chat surface added a ReasoningSection (auto-open/collapse) and a ToolCallIndicator / ToolCallItem (collapsible details, unique-tool counting) inside AIMessageBubble, streaming reasoning content and tool calls out of additional_kwargs. The whole reasoning + tool-call UI is gated on a "Verbose Reasoning" toggle (renamed from "Show Reasoning") in agent settings, which later releases hardened to respect RBAC. Streaming polish included hiding the action toolbar and empty bubbles while a message streams and restoring them on completion.
Private/temporary chat. A private (temporary) chat mode was added with a reorganized, categorized privacy/settings tab, working across embed and small screens, with the privacy toggle moved into settings.
Chat input and history. Users can paste text and files directly into uploads (clipboard file-extraction helper, attachment chips showing the full MIME type), chat history paginates with infinite scroll-up, and keyboard focus stays on the message textarea while chatting (WCAG-576) rather than jumping to the stop/copy buttons. A prompt-caching toggle and a configurable copy-character limit (NEXT_PUBLIC_MAXIMUM_CHARACTER_SIZE_TO_COPY) were added to agent settings, along with an "Optimize Page Context Tokens" embed toggle and a streamable text-to-speech endpoint.
New surfaces. A dedicated Projects page route was added (sourced from the SDK, with agentless-project navigation blocked), the app was internationalized with next-intl, a Chrome side-panel extension hosting shipped, and a dataset-curator/viewer role was exposed on the access tab. The agent picker and Explore agents section were unified onto the ai-search endpoint.
Desktop and mobile app. The Tauri desktop build gained GhostOS support, new Ollama backend functions, model-manager installation status re-polling, usable RAM/VRAM checks with program-install fallbacks, a macOS DMG release, and MSIX/Store packaging fixes; deep-link handling rewrites custom-scheme (iblai-mentor://) URLs back into the webview and routes Android SSO deep links back into the app.
Copy and RBAC. Education-specific copy was genericized — "Students" visibility/labels became "Users", the "Student Success" tools category was renamed, and marketing/explore copy dropped "learning" phrasing. The "Add Resource" button is now hidden without documents-create permission, support-phone display sits behind an env flag, and the Google Drive picker was patched to drop its hardcoded drive.readonly scope. Late-June work patched vitest/dompurify/@opentelemetry/core vulnerabilities and added dependency vulnerability scanning to the PR pipeline.
iblai-lms-spa — 0.36.0 → 0.43.2
The LMS SPA is the renamed Skills app. The spa.skillsai surface was renamed to lms (served at lms., sharing the iblai-skills-spa-pro image), the Tauri app was renamed to Agentic LMS, the deployment URL moved to https://lms.ibl.ai, and skillsai.ibl.ai → lms.ibl.ai 301 redirects were added. Early June also saw an extended run of dependency-security overrides (axios, hono, postcss, dompurify, basic-ftp, brace-expansion, and Cargo deps for the Tauri shell) plus an OAuth popup for the desktop build.
Public catalog and access. A public course catalog gained login/signup buttons, home/discover/recommended pages are hidden when not logged in, and course-metadata content-mode targeting was implemented. AI-analytics buttons are now visible to holders of view-analytics and watcher RBAC permissions, with analytics layout height restructured and an audit analytics page added.
Reading and layout. The course outline became collapsible on tablet and small-PC screens (appearing uncollapsed, dropping the tooltips), the default sidebar-agent load logic was aligned with the OS app, and grade info no longer appears in the education panel when unset.
Platform parity. Android SSO deep-link support was added with an aligned package id, a support-phone toggle was implemented behind an env update, and the final June release delivered an overall SPA optimization pass (lazy loading and re-render fixes, with a next.config.mjs update).
iblai-edx-pro — sumac.2.32.0 → sumac.2.36.0
Create courses on behalf of another user. POST /api/v1/ibl/course/create accepts an optional on_behalf_of username so a staff/superuser caller (e.g. an automation service account) can create a course owned by another user, backed by new ibl-edx-course-creator helpers (resolve_on_behalf_of_user staff-gate and grant_course_authoring_access granting CourseInstructorRole+CourseStaffRole); the target user only needs org membership on this path while the legacy self path still requires platform admin.
Server-to-server JWT minting. GET /ibl-auth/request-jwt/ gained a second mode: with a username/email query param, a trusted server (e.g. Data Manager) can mint an edX JWT for a named user without that user's browser session, gated on an OAuth-app allowlist (IBL_S2S_JWT_ALLOWED_OAUTH_APPS, default ["ibl_edx"]) and a staff+superuser check (IBL_S2S_JWT_REQUIRE_ADMIN); with no identifier the existing self-service flow is byte-for-byte unchanged.
Studio boot fix. Studio (CMS) had failed to boot with a RuntimeError (InvalidToken model missing an app_label) because the disposable-email-blocking refactor eagerly imported ibl_edx_account_api.models at URL-load time while the app was only registered for the LMS; adding a cms.djangoapp entry point (models-only, no CMS URLs/settings) registers it in Studio's INSTALLED_APPS. The Studio content-mode target-user dropdowns were also renamed to plurals (Learners/Watchers/Admins).
A second, independent OIDC SSO backend. IBLOpenIDOAuthSSOBackend (ibl_base_oauth_sso_backend.auth_openid) is a behavioral clone of the base backend reading a parallel IBL_OPENID_SSO_* / IBL_OPENID_USE_IDP_USERNAME namespace, so a deployment can run a second, fully independent OIDC integration alongside the existing IBL_OAUTH_SSO_* one; it stays inert until its settings and a matching OAuth2ProviderConfig are configured. The companion ibl-cli-ops 5.37.0 renders these settings and registers the backend in THIRD_PARTY_AUTH_BACKENDS.
Large-callback login fix. The uWSGI buffer-size was raised to 65536 so large OAuth2 callbacks no longer 502 — confirmed on Syracuse Azure AD, whose ~8.4k request line exceeded the 8192 the Sumac image had regressed to when uWSGI launch moved from run.sh to a static uwsgi.ini; it now matches the nginx inbound ceiling (64k).
Security hardening. An Orca CVE remediation pass on the Sumac image bumped Python deps (aiohttp, nltk, Jinja2, Mako, pillow, lxml 6.1.1 + lxml-html-clean, protobuf, sqlparse, requests, PyJWT, and more), re-applied runtime pins last in the production stage so they can no longer be clobbered (urllib3 had shipped at 2.2.3 despite a 2.7.0 pin), upgraded the crypto stack (cryptography 46.0.5, pyOpenSSL 26.0.0 closing CRITICAL CVE-2026-27459) by dropping the unused snowflake-connector-python, and pulled current Ubuntu 22.04 OS security patches. Known deferrals (pymongo, social-auth-app-django, and bundled edx-platform npm) are hard-blocked upstream.
iblai/infra-cli — 1.11.0 → 1.12.0
Optional AWS WAFv2 on the single-server ALB. 1.11.0 added opt-in WAFv2 at provision time (wizard sub-step, provision-env ENABLE_WAF/WAF_ALLOWED_IPS, or launch/launch-env flags), attaching a Regional Web ACL tuned for ibl.ai's subdomain layout: operator-IP-gated admin allow rules for DM Swagger, edX Studio, Django /admin/, and DM /data; a public allow rule for learn.; six AWS managed rule groups; and a path-traversal block (.git/.env/.htaccess/etc.), totaling ≈1355 WCU. A new iblai infra waf post-provision subgroup (enable / enable-env / disable / status) toggles it on an already-provisioned stack without re-running the wizard, and a shared TerraformRunner.reapply() helper (which pins the original bucket_suffix to prevent accidental S3 bucket renames) now backs both the WAF commands and a refactored iblai infra retry. WAFv2 entries were added to REQUIRED_IAM_POLICY with a wafv2:ListWebACLs smoke check so iblai infra permissions --check surfaces readiness up-front.
Bootstrap and SSO fixes. 1.12.0 fixed DM bootstrap to run CREATE EXTENSION IF NOT EXISTS timescaledb right after pgvector — the DM postgres image preloads TimescaleDB but the flow had only ever created vector, so setup_timescale_views --full-setup silently degraded and analytics hypertables were never built; the blanket ignore_errors: true on that task was replaced with register + failed_when so genuine tracebacks now surface. Microsoft SSO was fixed to use the standard azuread-oauth2 backend across every reference (OAuth2ProviderConfig.backend_name, the IBL_EDX_BASE_OAUTH_SSO_BACKEND block, other_settings.backend_uri, and IBL_SPA.AUTH.IBL_DIRECT_SSO_URL) instead of deriving a bogus slug that prevented sign-in; IBL_DM.ENABLE_RBAC_GROUP_MANAGEMENT=true is now set by default, and the exact Azure AD redirect URI (https://learn.) is documented in .env.setup.example and the end-of-run confirmation.
Companion catalog operations (via the bundled ibl-cli-ops pin). Across June the prod-images catalog advanced its ibl-cli-ops pin from 5.24.0 to 5.38.0, shipping operator-facing tooling alongside the SPA renames: ibl spa restart [SPA ...] with implicit derivative pairing (restarting mentor also restarts os; skills also restarts lms), CORS headers for the /ibl-auth/get-sso-login-url/ SSO endpoint, NEXT_PUBLIC_ENABLE_SUPPORT_PHONE and NEXT_PUBLIC_MAXIMUM_CHARACTER_SIZE_TO_COPY env plumbing for the SPAs, and an opt-in pg-healthcheck per-node Postgres activity/lock sampler that surfaces the offending query and PID during a spike, exposing low-cardinality Prometheus gauges (pg_blocked_backends, pg_longest_active_query_seconds, …) and four alerts without query text ever entering Prometheus.