Skip to content

v6.2.0-edge.1: AI usage analytics, personal folders, and cache-friendly Loop agents

AI usage analytics with a trustworthy cost basis, personal conversation folders, a multi-select conversation sidebar, and Loop agents that keep provider prompt caches warm across iterations. Plus the Betty LLM provider, the Opus 5.5 / GPT-6 / Grok 4.7 catalog refresh, and fixes to durable entity actions, magic links and Data Explorer views.

Upgrade Notes apply: six schema migrations apply on mj migrate, cache-invalidation broadcasts no longer carry row data by default, and prompt model selection now enforces the runner’s model type.

Edge builds are prereleases.

MemberJunction 6.x is released under the Business Source License 1.1; 5.x and earlier stay ISC. What the licence grants, and what needs certification, is in the licence section of the v6 upgrade guide.

  • AI usage analytics on a cost basis you can trust (@memberjunction/ai-prompts, @memberjunction/ai-agents, @memberjunction/ai-core-plus, @memberjunction/core-entities, @memberjunction/ng-dashboards, @memberjunction/ng-query-viewer, @memberjunction/server): Cost reporting was wrong in both directions and could not be sliced by user or agent run. Prompt runs now record UserID, cost columns share decimal(19,8) precision, and a new vwAIUsageFacts view gives one row per prompt run with time buckets, attribution dimensions and flags such as IsPriced and SourceKind. Eight saved queries in the AI category group every cost by currency beside its priced/unpriced counts, and the seven analytics dashboards read them, rendering an unpriced cost as an em dash instead of $0.00. Parallel runs now record every arm as its own ParallelChild row; previously the losing arms were never recorded. The rules are in guides/AI_USAGE_AND_COST_ANALYTICS_GUIDE.md.
  • Personal conversation folders (@memberjunction/core-entities, @memberjunction/server, @memberjunction/ng-conversations, @memberjunction/ng-core-entity-forms): Project.OwnerUserID lets a folder belong to one user; NULL keeps it shared with the environment, as every existing folder is. The sidebar and Assign Project picker show shared folders plus the user’s own, and a row-level-security filter on the UI role’s read permission keeps a personal folder’s name unreadable through any reader for users whose every read grant is filtered. A personal folder can later be shared; a shared folder cannot be taken private.
  • Conversation sidebar multi-select, bulk actions and sorting (@memberjunction/ng-conversations, @memberjunction/ng-resource-permissions, @memberjunction/core-entities): Ctrl/Cmd-click, Shift-click, hover checkboxes and touch long-press select conversations; a selection bar and a single right-click menu offer Pin, Move to folder, Share and Delete across the selection, and dragging a selection moves it into a folder. Date/Name sorting applies to every section, the search box gains a clear button, and mj-resource-share-dialog can share several resources at once through a new Contexts input.
  • Host rules for multi-person chats (@memberjunction/ng-conversations, @memberjunction/conversations-runtime, @memberjunction/server, @memberjunction/ai-agents): mj-conversation-chat-area gains opt-in inputs — AgentReplyMode, AllowedAgentIDs, MentionPeople, AgentHistoryFrom, AgentTurnHandler, AutoNameConversation — and a composerExtra slot, so a host can run a chat among several people without forking the chat area. Every default keeps existing behaviour.
  • Chat-wide search in Explorer (@memberjunction/ng-conversations, @memberjunction/ng-explorer-core, @memberjunction/core-entities): The cross-entity search panel was never mounted on any shipped surface. It is now reached from a “Search all of Chat for …” row under the conversation list, returns only conversations and artifacts the user can already see, and message search — which could never match — works on SQL Server and PostgreSQL.
  • Betty LLM provider (@memberjunction/ai-betty): A new BettyLLM driver for the MJ-native Betty assistant, alongside the unchanged @memberjunction/ai-betty-bot. It sends the latest user turn and passes earlier turns as context, where the legacy provider silently discarded follow-ups. Requires BETTY_API_BASE_URL and AI_VENDOR_API_KEY__BETTYLLM.
  • Decision model type (@memberjunction/ai, @memberjunction/core-entities): Adds the Decision AI model type and the abstract BaseDecision primitive for models that answer typed questions (Likelihood, Choice, Score) with a probability per answer. No drivers ship yet.
  • MJAPI reloads metadata on SIGHUP (@memberjunction/server): Sending SIGHUP hard-reloads metadata from the database without a restart.
  • Loop agents keep provider prompt caches warm (@memberjunction/ai-agents, @memberjunction/ai-prompts, @memberjunction/ai-anthropic, @memberjunction/ai): The Loop system prompt rendered date/time, scratchpad and payload at its tail, which broke the provider’s prefix cache every iteration. That state now travels as a trailing <mj-runtime-state> message, carried per provider according to a new PrefixPromptCache setting in the model-configuration cascade, which gains a vendor layer (AIVendor.Configuration). Measured on Sage from the third iteration, cached input rose to 66–96% across Anthropic, OpenAI, xAI, Gemini and Cerebras models (from 0–65%), and prompt cost per million tokens fell 37–80%.
  • Agents stop retrying tools that cannot succeed (@memberjunction/ai-agents): An action that returned Success: false was reported as successful, so agents retried unconfigured tools indefinitely. A run-scoped circuit breaker now disables an action after a fatal configuration error, after two identical failing calls, or after five consecutive failures, and tells the model why.
  • AI model catalog refresh (@memberjunction/aiengine, @memberjunction/ai-deepinfra, @memberjunction/ai-siliconflow, @memberjunction/ai-elevenlabs): Adds Claude Opus 5.5, Grok 4.7, GPT-6 Sol and Luna, Xiaomi MiMo V2.6 Pro and Flash, and ElevenLabs Eleven v4 and v4 Turbo, plus new DeepInfra and SiliconFlow drivers serving GLM-5.3-Flash.
  • Records load their IsA subtype with fewer round trips (@memberjunction/core): An entity can opt in (UseForLoadedRecords on its SubtypeSelector, or a resolver’s ResolveLoadHint) to predicting a record’s subtype from cached data, cutting a browser record open from three round trips to two and a 100-row entity RunView from 201 to 101.
  • Business-user vocabulary (@memberjunction/core, @memberjunction/ng-entity-viewer, @memberjunction/ng-dashboards, @memberjunction/ng-resource-permissions): Default data-browsing surfaces use the entity’s own plural (“No Contacts to display”) through the new EntityInfo.DisplayNamePlural, and Data Explorer and the Sharing Center no longer show the word “entity” or raw domain names.
  • Grid exports match what the grid shows (@memberjunction/ng-entity-viewer): Exports now use the on-screen columns, order and headers instead of the host-declared column list, and “Send Message” appears only for entities that support communication.
  • Accessible names for form controls (@memberjunction/ng-ui-components): mj-combobox, mj-switch, mj-numeric-input, mj-datepicker and mj-page-search gain AriaLabel, AriaLabelledBy, AriaDescribedBy and InputId, reaching each control’s secondary buttons too, so they no longer announce as unnamed controls (WCAG 2.1 4.1.2).
  • Test-harness data bridges are capped (@memberjunction/react-test-harness): A generated component’s query returned 205,802 rows and exhausted the host process heap. Data-bridge calls now clamp MaxRows to dataCaps (default 1000), and each call’s rows returned and total are reported in dataAccess.
  • Generated forms with many related panels no longer peg the CPU (@memberjunction/ng-base-forms): Resolving form contributions was quadratic in an entity’s related-entity panels and ran on every change-detection pass; it is now linear.
  • Public APIs renamed to PascalCase without breaking consumers (most packages): Public members and exported functions gain PascalCase names; every old name remains as a @deprecated forwarding stub, so existing code compiles and behaves identically.
  • Durable entity actions ran without their parameters (@memberjunction/actions, @memberjunction/actions-base, @memberjunction/task-graph, @memberjunction/server): Since 6.1.0, an entity action with RunMode = 'Durable' stored its parameters as an array that the dispatcher silently dropped, so every durable binding ran with no inputs (for example Common.LogActivity failing with TypeCode is required.). Parameters now arrive by name, a task whose payload is not a name → value object or not valid JSON fails with a message naming the task, redaction uses the live ActionParam definitions, and the Workflows run view shows why a failed step failed.
  • Magic links failed on hosts whose API login is not db_owner (@memberjunction/server): Redemption ran a raw UPDATE the data roles are not granted, so every link — including every MJ Forms public link — answered 410 “already redeemed”. It now runs through a granted spConsumeMagicLinkInvite procedure on SQL Server, and a database failure reports HTTP 500 instead of 410.
  • Cache-invalidation events exposed rows to users who could not read them (@memberjunction/server, @memberjunction/core, @memberjunction/core-entities): The cacheInvalidation subscription reaches every connected client and carried the full saved row, bypassing row-level security. Row data is now sent only for allowlisted entities, and consumers re-read the record as the signed-in user through the new ResolveEntityEventRow.
  • Saving a default Data Explorer view with a filter did nothing (@memberjunction/ng-entity-viewer): Configuring a Smart or Traditional filter on the unsaved default view and clicking Save created nothing and showed no error. It now opens the name prompt, suggesting a name from the filter, and saves a view; the quick-save path also stopped discarding smart filters.
  • Opening a saved view in its own tab froze Explorer (@memberjunction/ng-explorer-core): The tab compared MJ: User Views with User Views exactly, treated every emission as a change and reloaded endlessly. Dynamic views of different entities also stopped sharing one tab and cache entry.
  • Ctrl/Cmd+/ never opened the command palette (@memberjunction/ng-explorer-core, @memberjunction/ng-whiteboard): A duplicate @HostListener silently replaced the handler. The palette now opens, lists only the user’s own apps, and its search row opens Search Results; the whiteboard’s Sees dropdown closes on an outside click again.
  • A While loop with an unevaluable condition reported success (@memberjunction/ai-agents): A malformed condition produced a green, zero-iteration loop with the cause recorded nowhere. The loop now fails with the evaluator’s message, and loop step errors render their text instead of [object Object].
  • Prompts could select a model of the wrong type (@memberjunction/ai-prompts): A prompt with no AIModelTypeID could run an embeddings or realtime model with a high PowerRank. AIPromptRunner now selects only LLMs.
  • Foreign-key picker reopened after being dismissed (@memberjunction/ng-base-forms): A slow lookup reopened the dropdown after Escape or blur, and nothing could then close it. Dismissal now retires in-flight lookups, an outside press closes the panel, and Escape no longer also closes the hosting dialog.
  • Required fields flagged an empty string as missing (@memberjunction/ng-base-forms): A cleared NOT NULL text field showed the red required underline for a value the save accepts. Only null or undefined is now treated as empty.
  • RunQuery fetched every row when only MaxRows was set (@memberjunction/generic-database-provider): Without StartRow, the whole result crossed the network and was trimmed in memory — a MaxRows=10 call returned 479 rows’ worth. MaxRows alone now pages at the database.
  • 5.51 clients failed to delete against a 6.1 server (@memberjunction/server): DeleteOptionsInput.SkipRecordChanges became required in 6.1.0, so older clients sending delete options failed schema validation. It is optional again, defaulting to false.
  • Numeric CHECK constraints produced no value list (@memberjunction/codegen-lib, @memberjunction/core): On SQL Server, a numeric or single-value IN (...) CHECK constraint lost both its validation and its dropdown. CodeGen now captures them, and validation compares numeric values numerically.
  • Provider and driver instances leaked or were rebuilt on every call (@memberjunction/communication-engine, @memberjunction/storage, @memberjunction/auth-providers): Communication providers were rebuilt on every send, and replaced storage drivers and auth providers were dropped without releasing their SDK clients and socket pools. They are now cached or disposed.
  • My Profile dialog clipped its bottom controls (@memberjunction/ng-explorer-core): On short windows the notification settings and Sign out were cut off with nothing to scroll; the settings now scroll in one region and the footer stays pinned.
  • Folder save failures showed a generic message (@memberjunction/ng-conversations): The New/Edit Folder dialog now reports why a save was refused instead of “Failed to save project”.
  • Hover-only row actions were unreachable by keyboard (@memberjunction/ng-entity-viewer, @memberjunction/ng-dashboards): The view selector and home dashboard revealed row actions on :hover alone; they now also appear on keyboard focus.
  • Six schema migrations apply on mj migrate: Project.OwnerUserID with its row-level-security filter on the UI role’s read of MJ: Projects; AIVendor.Configuration; AIPromptRun.UserID, decimal(19,8) cost precision, analytics indexes and vwAIUsageFacts; spConsumeMagicLinkInvite; the repoint of the duplicate STT model type onto Speech to Text; and the release metadata sync.
  • Cache-invalidation broadcasts carry no row data by default: cacheSettings.recordDataBroadcastEntities defaults to [], so every remote save of an AutoRefresh engine entity triggers a full reload of each matching engine config rather than an in-place update. List entities every signed-in user may read to restore in-place updates for them, or ['*'] to restore the previous behaviour wholesale.
  • Prompt model selection enforces the LLM type: a prompt whose AIModelTypeID names a non-LLM type, or an explicit model override naming a model of another type, now fails with an error naming both types instead of running. Prompt model bindings pointing at another type are skipped and logged. No shipped prompt is affected.
  • Durable tasks queued before this release fail loudly: tasks still carrying the old array payload now fail with Task <id> has an InputPayload that is an array… instead of running without inputs. Re-trigger the source save, or use the failed step’s Edit input & retry in the Workflows run view.
  • BeforeAgentTurn fires on every route: it now fires once per turn before any reply row exists, carries the resolved agent and route, and a cancel leaves nothing behind. Previously it fired only on the conversation manager’s route, after that route’s placeholder row was saved.
  • Regenerated types narrow for numeric CHECK constraints: on SQL Server, regenerating against a schema with a numeric or single-value IN (...) CHECK now types that property as a literal union (for example 1 | 2 | 3) instead of number. MJ’s own schema has none.
  • Removed AIPromptRunner failover helpers: the unused protected methods buildFailoverCandidates and createCandidatesFromModels are gone; a subclass that overrode or called them must drop that code.
  • Mixed-version clients: metadata fields such as EntityInfo.spCreate now serialize under their PascalCase names. A client older than its server drops them; same-version deployments are unaffected.