Skip to content

MemberJunction Developer Guides

This folder is the home for cross-cutting, “read this before you build that” reference docs. Package-specific docs live in packages/<Pkg>/CLAUDE.md or packages/<Pkg>/README.md and are linked from here when relevant.

If you’re about to start work in one of the areas below, read the guide first — these documents capture patterns that have already been litigated.

This file is the complete index of MJ development guides. The root CLAUDE.md points here rather than duplicating the list, so this is the one place to look — and the one place to update when you add a guide.

  • Building Applications on MemberJunction — The hub guide for using MJ as a first-class application development platform. Explains the metadata-driven, schema-to-app model, the unified-TypeScript / isomorphic object model, AI-native app patterns, and links out to the authoritative README/guide for every layer (data modeling, CodeGen, entities, API, UI, Actions, AI, deployment). Start here if you’ve got data in MJ and want to build on it.
  • Framework Comparison — Objective comparison of MJ against Next.js/Vercel, Supabase, Rails, Django, and a hand-rolled Node+ORM+SPA stack: where each shines, where MJ differs, and how to choose. Companion to the app-building guide.
  • Agent Framework Comparison — Objective comparison of MJ’s AI agent framework and infrastructure against LangGraph, CrewAI, AG2/AutoGen, the vendor agent SDKs, Semantic Kernel, LlamaIndex, and the TypeScript-native frameworks: orchestration paradigms, payload/state governance, HITL, model gateway, observability, memory, permissions, and deployment (self-host or managed via MJ Central). Companion to the app-stack comparison above.
  • BaseEntity Server-Side Patterns — Use before writing a new server-side entity subclass under MJCoreEntitiesServer. Covers the persisted-embedding pattern (Save() + EmbedTextLocal + engine cache sync), cross-record invariants via ValidateAsync (NOT DB triggers), and FK cleanup before delete. Reference implementations: MJAIAgentNoteEntityServer, MJTagEntityServer, MJTagScopeEntityServer. Lift the recipes from there — don’t reinvent.

  • Transactions, Batching & Entity Graphs — Read before writing anything that saves more than one record together. Disambiguates MJ’s three overlapping mechanisms: provider transactions (BeginEntityTransaction() — server-only, ambient, re-entrant with savepoints, joins any transaction already in flight), Transaction Groups (an arbitrary batch facility — atomic, but saves are deferred, so there is no PK after the parent save, no read-your-writes, and Save() returns true before anything persists — which is why it is the wrong tool for parent/children), and entity graphs (RelatedRecordCollection companions declared on a shared client+server subclass; entity.Save() persists parent and children as one unit, locally inside a transaction on the server or routed whole to the server via MJ.SaveEntityGraph from the browser). Also covers Load: 'explicit' | 'immediate' | 'lazy' | 'never', why immediate loading is excluded from LoadFromData() (N+1), RunView.IncludeRelatedRecords batched loading, and the 6.2 retirement of the BeginISATransaction trio that caused torn writes. Includes a decision tree and anti-patterns.

  • Recursive Foreign Keys & Hierarchy Traversal Guide — Comprehensive architecture for self-referencing entities (categories, org charts, taxonomies, folder trees). Explains the generated 4-function TVF suite (GetHierarchyMeta, GetDescendants, GetAncestors, GetRootID), the 5 view columns (Root<Field>, <Field>Depth, <Field>Path, <Field>IsLeaf, <Field>ChildCount), optimizer pruning via inline TVFs / LEFT JOIN LATERAL, strongly typed GetDescendants(), GetAncestors(), and GetChildren() TypeScript entity subclass methods, and the interactive Angular <mj-hierarchy-tree> component.

  • Soft Deletes Guide — How DeleteType='Soft' works end to end: CodeGen-managed __mj_DeletedAt column, filtered base views, soft-delete spDelete, interaction with AllowRecordMerge.

  • UUID Comparison Guide — Critical patterns for comparing UUIDs across SQL Server (uppercase) and PostgreSQL (lowercase):

    • Always use UUIDsEqual() instead of === for UUID comparisons
    • Use NormalizeUUID() for Set/Map key operations
    • Angular template binding patterns
    • Automated enforcement tests
  • Caching & Real-Time Synchronization Guide — Multi-tier server caching, RunView cache behavior, BypassCache, BaseEntity event-driven invalidation, Redis pub/sub, real-time browser sync. Read before touching caching or write-after-write data flow. Also documents the BaseEngine integration (ObserveProperty, DataChange$) that gives reactive UIs over entity caches for free.

  • Keyset Pagination Guide — Deep pagination via RunViewParams.AfterKey (keyset/seek) instead of StartRow, for background jobs and bulk processing that iterate all records of a large entity. Keyset stays O(log N) per page regardless of depth. Single-column PK only — throws AfterKeyNotSupportedError for composite-PK entities. Reference implementations: ScheduledGeocodingAction, VectorBase, EntityVectorSyncer.

  • Filter Builder & Evaluation — One CompositeFilterDescriptor JSON for User Views (SQL) and in-memory rules (prices, processes). Write Source.Field when the builder is given sources; read both dotted and bare names. CompositeFilter (FromJSON, Evaluate, SummaryText) lives in @memberjunction/core (no Angular). SafeExpressionEvaluator is a different tool (authored JS). Read before adding a “when does this apply?” UI.

  • Remote Operations Guide — The typed, provider-routed Remote Operations primitive — BaseRemotableOperation<TInput,TOutput> (in @memberjunction/core) invoked from one call site on both client (marshalled over GraphQL) and server (in-process). Framed as MJ’s 4th data primitive alongside BaseEntity (CRUD), RunView (dynamic set reads), and RunQuery (stored queries). Covers: when to use it vs. an Action vs. a bespoke resolver; the three authoring modes driven by an MJ: Remote Operations row’s GenerationTypeManual (CodeGen emits the typed base, you write the InternalExecute subclass), AI (RO-4: MJRemoteOperationEntityServer has an LLM author the body from Description against the ambient input/provider/user/context contract + a JSONType Libraries declaration, gated by CodeApprovalStatus), and Default; the RemoteOperationGeneratorBase CodeGen emitter (@memberjunction/codegen-lib) → remote_operations.ts; the RouteOperation power-tool seam (IRemoteOperationProvider on ProviderBase); the auth chain (API-key scope ∥ user permissions + the RemoteOperationEngineBase Active/Approved metadata gate + per-op Authorize); and LongRunning progress — attached onProgress works both in-process AND over the wire (a per-call RemoteOperationProgress subscription channel published from the resolver); only detached fire-and-forget remains partial. Read before hand-rolling a resolver+client for a typed capability the browser and server both invoke, OR before adding a new operation (declare a metadata row, don’t hand-write the base). Complements (does not replace) the Transport-Layer guide and the Actions boundary. For a visual before/after with mermaid diagrams (the layers Remote Operations removes, from two real migrations), see the companion Remote Operations Showcase (lives with the @memberjunction/core package that defines the primitive).

  • Transport-Layer Architecture Guide — The canonical engine → resolver → GraphQL client → thin UI layering (plus the optional Action layer for agentic/workflow/low-code invocation) for any custom server-side capability the browser or an agent invokes — clustering, search, classify, LLM calls, “run this pipeline” buttons. Covers:

    • Why business logic lives in the framework-agnostic engine exactly once, and what each adapter layer must NOT do
    • Step-by-step: build the engine → thin TypeGraphQL resolver (ResolverBase + per-request user) → typed GraphQL<Feature>Client in @memberjunction/graphql-dataprovider → thinnest Angular wrapper (never inline gql) → optional Action that calls the engine directly
    • A decision table for which layers you actually need (and when to just use the generated entity CRUD layer instead)
    • JSON-string-field pattern for complex payloads, client/engine type decoupling, and reference implementations (GraphQLClusterClient, SearchKnowledgeResolver, etc.)
    • Read this before hand-writing any new resolver or GraphQL client. Not for plain entity CRUD — that’s already generated.
  • Record Set Processing & Record Processes Guide — MJ’s single hardened substrate for “do X to a set of an entity’s records” — and the saved, metadata-defined Record Process layer on top of it. Two layers: the substrate (@memberjunction/record-set-processor engine + -base seams) is a composition of three pluggable seams — Source (ArraySource/ViewSource/ListSource/FilterSource/KeysetSource, cursor-paginated + resumable) × Processor (per-record work → RecordResult) × Tracker (GenericProcessRunTrackerMJ: Process Runs/Process Run Details, or NoOpTracker/custom) — wrapped by an engine that owns batching, bounded concurrency, token-bucket rate-limiting, an error-rate circuit breaker, a budget gate, progress, a pause/cancel handshake, resume, and per-record isolation; and the Record Process (MJ: Record Processes row → the RecordProcessExecutor facade) with four work types (FieldRules — declarative field rules with a dry-run preview, self-writing; Action / Agent / Infer — wrapped by WriteBackProcessor when an OutputMapping is set), scopes (stored ScopeType View/List/Filter/SingleRecord + a runtime RecordProcessScopeOverride records/view/list/filter for “run against the current grid selection”), dry-run (the first-class ProcessRun.DryRun flag), and three triggers (OnDemand / OnChange / Schedule). The UI lives in @memberjunction/ng-record-process-studio (the Bulk Operations studio — editor + visual FieldRules builder + reactive history) and @memberjunction/ng-entity-action-ux (the in-grid RecordProcessRunnerUX runner); the typed API surface is the RecordProcess.RunNow + control Remote Operations. Read before building any bulk/set-iterating operation, adding a work type, or touching the substrate — don’t re-implement batching/resume/audit; compose the seams or declare a Record Process. Folder overview: packages/RecordSetProcessor/README.md.

  • Dev Workspace Quickstart — The practical setup guide for mj dev workspace: turn a plain folder of sibling repo clones (MJ + app repos) into one pnpm workspace where an edit in any repo is live everywhere in ~1s, nothing committed to any repo. Covers the bootstrap order (build MJ once, then let --clean-members replace its standalone install), the daily pnpm --filter loop, recovery from standalone-install split-brain, teardown, and the run-from-source invocation until the next edge release ships the command. Start here to set up multi-repo local dev; the normative contract is the spec below.

  • Dynamic Package Loading Guide — How packages whose names are only known at runtime (installed Open Apps’ server packages, a host’s generated packages, the app whose repo you are standing in) get imported into every MJ process — MJAPI, the mj CLI, MCP/A2A, test bootstraps — so GetEntityObject returns the real subclass instead of a silent BaseEntity fallback. Process IDs and per-process scoping (Processes / ExcludeProcesses / policy), the MJ_DYNAMIC_PACKAGES=none / --no-app-packages kill switch, configuring a downstream app and an MJ install, adding the loader to a new host, and a troubleshooting table. Read before wiring any new process to an MJ database.

  • Open App Workspace Linking Spec — The normative (RFC-2119) specification for cross-repo Open App local development: mj dev workspace generates an ephemeral pnpm workspace at the member repos’ common parent — local source for selected repos, the registry for everything else, nothing committed to any repo, one physical copy of every shared dependency — plus the mj doctor conformance checks and era-based version-compatibility rules. Companion decision record: docs/decisions/2026-07-30-openapp-local-dev-architecture.md. Read before touching Open App linking, the workspace generator, or dev-mode registration.

  • Server Extensions Guide — The authoritative guide for mounting custom server capabilities into MJServer via @memberjunction/server-extensions-core. Covers when to use an extension vs. a TypeGraphQL resolver vs. an Action vs. a Remote Operation; lifecycle phases (Pre-Auth for webhooks with external signatures vs. Post-Auth for authenticated endpoints with user context); reserved root path safety (preventing collisions with /graphql, /health, /auth, etc.); the shared ServerExtensionServiceRegistry and OnAllExtensionsMounted hook; non-HTTP attachment (WebSocket clients, Slack Socket Mode); and host configuration via mj.config.cjs. Read before adding any custom Express routes, webhooks, or background workers to MJAPI.

  • Scaling CodeGen across many schemas — How CodeGen emits and incrementally regenerates TypeScript and GraphQL when a database has many schemas: MJ Core, application schemas, or both. Entity remains the identity; schema is the incremental unit. Covers per-schema emit, write-if-changed, dirty-schema regen, exclude-table strings, parallel file gen, incremental tsc, catalog projections (the catalog stays complete), and schemaOutput / entityPackageName. Optional test bed: Demos/BigSchemaDemo. Read before configuring CodeGen for a multi-schema database or adding another generated output target.
  • Migration → CodeGen End-to-End Workflow — The workflow every schema change follows, whatever its type (new tables, new columns, constraint changes, column modifications, extended properties): write DDL → apply it → run CodeGen → consolidate the output into one replayable migration file. Explains why a single consolidated file matters (guaranteed replay order, atomic review, consistent state on fresh installs) and the separator convention for appending CodeGen output. Companion to migrations/CLAUDE.md, which holds the authoring rules themselves.
  • PG Migration Glossary — The vocabulary of the dual-platform (SQL Server → PostgreSQL) migration release process: curated metadata, metadata-sync migrations, the PG ledger and why it is immutable, marker files, migrate-only deployments, gapped databases, baselines, reseed migrations, and superseded updates. Read before discussing or debugging a PG release gap — issue #3253 (196 lost record deletions, a 126-byte marker, and a baseline dumped from a gapped database) is a lot easier to follow once these terms are pinned down.
  • PostgreSQL Schema Casing Guide — Why entity ClassName/CodeName and GraphQL type names go lowercase-broken on PostgreSQL (unquoted DDL folds schema names to lowercase, so __mj_BizAppsCommonmjbizappscommon…Entity instead of the published mjBizAppsCommon…Entity → TS2724 build break), and how MJ fixes it with a case-stable SchemaInfo.CanonicalSchemaName (sourced from mj-app.json schema.name via the OpenApp record, backfilled by CodeGen’s metadata-sync proc) preferred via COALESCE/?? in BOTH the vwEntities SQL view and the runtime GraphQL prefix. Net-zero on SQL Server. Read before touching schema-prefixed identifiers, the OpenApp install path, or spUpdateSchemaInfoFromDatabase — and note that proc lives in TWO synced copies (SS baseline/migration + PG metadataSupportObjects.ts). Includes the remediation runbook for existing PG installs (seed the OpenApp row).
  • Field-Level Security Guide — Role-based per-field Read/Update/Create control (MJ: Entity Field Permissions). Field security is on or off per entity (Entity.EnableFieldLevelSecurity), never inferred from whether rows exist; enabling snapshots the entity’s existing entity-level permissions so it changes nothing until you tighten a field, and disabling keeps the rows dormant. Covers the trinary verb model (Allow / Deny / No Access, where No Access is the neutral default) and its aggregation across roles — any Allow, and no Deny; a Deny wins, and there is no exempt user, not even the MJ system user (its access comes from ordinary rows, protected by save-time configuration guards); why Read is required for Update and Create and is clamped twice; why a field with no rows on an enabled entity is denied; the unrestrictable targets (PKs, __mj_ columns, the security/identity entities) and why read-only fields take Read rules but not write verbs. Every enforcement surface: output projection on both cache paths, the GraphQL read boundary, predicate rejection for ExtraFilter/OrderBy/Aggregates, the save-time update guard, create suppression (denied create values are silently dropped to the column default — never rejected), the denied-read strip that makes restricted-user round-trip saves safe, and record-name lookups degrading to the primary key. Note the two denial wordings: read denials keep the deliberately ambiguous does not exist … or you do not have access (don’t “fix” it), while a write denial on a field you can read names the missing permission. Read the configuration-constraints section before restricting any field: NOT NULL columns can be restricted (only Create-denial on a NOT NULL column with no default is unsupported), the MJ: Record Changes trust boundary (audit payloads leak denied values to anyone who can read them), saved queries are NOT FLS-filtered (run-grant is the boundary), and server-internal code sees full values. Includes the direct-connection (BI/Power BI) summary: SQL Server column DENYs for custom roles only, PostgreSQL gets no DB-tier FLS at all, no RLS for direct connections on either platform, BI roles must be SELECT-only. Read before configuring field permissions or building on an FLS-restricted entity.
  • Unified Permissions Guide — How MJ answers “can this user do this?” across every resource type (agents, artifacts, dashboards, queries, collections, entity rows, and anything you add) through one normalized model. Covers the three-concerns mental modelAuthorizations (named capability/feature gates via AuthorizationEvaluator), Entity Permissions / RLS (row-level CRUD filtering via getRowLevelSecurityWhereClause), and the unified PermissionEngine (per-record sharing/access) — and how to pick the right layer; the PermissionProviderBase contract + normalized vocabulary (PermissionAction = Read/Create/Update/Delete/Share/Execute/Admin, GranteeType, NormalizedPermission); the PermissionEngine aggregator that reads the MJ: Permission Domains catalog and ClassFactory-instantiates each @RegisterClass(PermissionProviderBase, …) provider (adding a domain is data + a class, never an engine edit); the 9 shipping domains + backing storage; the two access paths (cached runtime helper like AIAgentPermissionHelper, open-by-default, hot path — vs. the unified provider, closed-by-default, Sharing Center/audit) and why they differ; a recipe to add permissions to a new resource type (worked example); and mermaid diagrams (decision flow, provider fan-out, add-a-domain). Read before gating any action, building a sharing UI, auditing access, or adding a new permissioned resource.
  • Magic Link Access Guide — How to share an app-scoped, passwordless session with external users (MJ-issued RS256 magic links). Covers enabling the feature, the two-layer model (framework mechanism vs. per-deployment scenario config vs. runtime-provisioned users), and the recipe for defining an external-access scenario via metadata (restricted role + entity permissions + application role). Read before wiring up external/guest access — external user accounts are runtime-provisioned, but the role + permissions that scope them are version-controlled metadata.
  • Search Overview Guide — Decision tree across MJ’s search/lookup APIs — EntityByName/EntityByID (definition lookup), SearchEntity/SearchEntities (per-entity ranked hybrid search, see ENTITY_SEARCH_GUIDE), FullTextSearch (multi-entity DB-level FTS, see FULL_TEXT_SEARCH_GUIDE), and SearchEngine.Search (cross-source unified search with scopes, see SEARCH_SCOPES_AND_RAG_GUIDE). Read this first when you need to find records / definitions / cross-source matches — picking the wrong API can mean wasted round-trips or missed semantic matches.
  • Entity Search Guide — Per-entity ranked hybrid search via SearchEntity / SearchEntities. Reached from the Search Overview decision tree above.
  • Search Scopes & RAG+ Guide — Implementation guide for Search Scopes + agent RAG+ architecture: cross-source unified search with scopes via SearchEngine.Search. Companion to plans/search-scopes-rag-plus.md.
  • Content Segmentation GuideSegmentation decides what gets embedded; the embedding model only decides how. How MJ chunks content before vectorization via the pluggable BaseSegmenter strategies (StructuralText, SemanticText, Transcript, FixedWindow) in @memberjunction/ai-segmentation. Covers strategy selection, why the embedding and tagging chunk sites keep separate token budgets, writing a new strategy, and the multimodal model — one native vector per media chunk plus Description/Transcript as readable text. Read before changing how any pipeline chunks content, or before ingesting audio/video/images.
  • Content Autotagging Guide — The Knowledge Hub pluggable autotagging pipeline: providers, keyword extraction, taxonomy bridging.
  • Taxonomy & Tagging Guide — How the tag taxonomy itself is shaped, scoped, governed, grown, embedded, reviewed, and pruned. Companion to the autotagging guide.
  • Duplicate Detection & Merge Guide — Configuring and running intelligent duplicate detection on an entity, reviewing the AI’s verdicts, and merging. The principle: vectors filter, reasoning validates — a fast embedding/vector pass finds candidate near-duplicates, then an optional gated LLM pass judges the high-probability ones (Merge / NotDuplicate / Uncertain). The LLM never originates matches. Covers the cost/quality lever (ReasoningThreshold) and how to swap the reasoning model. Written for both users configuring it and PS teams implementing it for a client.
  • External Data Sources Guide — Backing an MJ Entity or Query with a remote system (Snowflake / MongoDB / external PostgreSQL / …) read live through a pluggable driver — no replication into the MJ DB (Linked-Server-style). Covers the engine → provider-dispatch → router → driver layering, configuring an ExternalDataSourceType + ExternalDataSource + credentials, read-only enforcement (ReadOnlyExternalBaseEntity), TTL caching, the RLS-refusal / injection-safe-filter / unsupported-param (AfterKey/aggregates/search) guards, connection auth self-heal, how to add a driver, and known limitations. Read before touching anything under packages/ExternalDataSources/ or wiring an entity/query to a remote source. Distinct from Integrations (scheduled pull-sync into MJ) and Materialization (plans/query-entity-materialization.md, persist hot results into MJ tables).
  • Workflows and Task Graphs GuideRead before building anything that runs more than one step. Untangles the three things people call “a workflow”: the definition (a Flow agentAIAgentStep + AIAgentStepPath — the only durable form of a graph you can reopen and edit), the contract (TaskGraphSpec, which nothing persists), and the run (Task + TaskDependency rows, written for every producer). Covers the decision between a Flow agent, a Loop agent, a Record Process and a single Action; the submit-and-detach lifecycle that makes work outlive the run that asked for it (and why cost therefore cannot be totalled during that run); the seven node kinds and what runs each; exclusive groups — a flow’s fan-out is an exclusive choice, so highest Priority wins, ties break on ascending Sequence, losers become Skipped (a normal outcome that satisfies dependents and is grey, never red), and one unevaluable condition holds the whole group rather than firing every branch; the payload mapping dialect shared by both engines (* wildcard, case-insensitive lookup, [] append, $message fields, static:/payload./data./context. prefixes) and why a lost mapping produces a workflow that completes successfully having done nothing; loops as a single Task row that iterates internally (maxIterations: 0 means unlimited; parallel loops keep results in iteration order); onError and failureSemantics; a worked end-to-end configuration of the shipped Demo Flow Agent; submitting a graph from code; where everything is recorded (Task rows, ActionExecutionLog — including one row per loop iteration — and TotalCostRollup vs TotalCost); layout (authored geometry persists, derived layout never does); a troubleshooting table for the four ways a graph stops; and the ordered checklist for adding a node kind. Companion to packages/TaskGraph/README.md.
  • Workflow Debugger Guide — How to step a live task graph from Agent form → Run → Debug (start-paused) or Workflows → Runs: the drop-in <mj-task-graph-debugger>, breakpoints, Continue-from-breakpoint (skipBreakpointTaskID), kick-on-submit, queued/running/traveled paint, the left invocation pane, edge overrides for a held path, force-complete, edit-input-and-retry, and the visual rule that an operator-forced edge must never look like a condition that genuinely evaluated. Companion to the workflows guide, packages/TaskGraph/src/debug-state.ts, and @memberjunction/ng-task-graph-editor.
  • Agent Authoring via MCP Guide — How external MCP clients (Claude Code, Codex, Claude Desktop, Cursor) author, introspect, test, and govern MJ agents through the agent-management tool group on @memberjunction/ai-mcp-server — the whole agent definition traveling as one AgentSpec JSON document via AgentSpecSync. Covers client connection (API key vs OAuth 2.1/DCR, StreamableHTTP /mcp), the scope model (agent:read / agent:manage / action:read / agent:execute), the authoring loop (catalog → spec → create/update → run → audit) with the ActionSmith/Codesmith builder agents closing capability gaps, the AgentSpec semantics that bite (full-replace Update_Agent with orphan cleanup, server-assigned IDs, child vs related sub-agents, payload ACL defaults), and the governance convention: iterate via MCP against dev, promote via metadata/ + mj sync with Git review — never grant agent:manage on production-facing credentials. Read before pointing any desktop agent at an MJ MCP server for agent-building work.
  • External Agent Harness Guide — Running an MJ agent whose reasoning comes from an EXTERNAL harness (Claude Code, Codex CLI, OpenCode, Gemini CLI, Pi) in a sandbox, while MJ keeps identity, permissions, payload contracts, HITL, cost control and audit. The load-bearing idea: a harness turn is protocol-identical to a Loop iteration — the harness ends each turn by emitting the Loop next-step JSON envelope, so actions/sub-agents/skills run through MJ’s existing validated machinery and there is ONE authority channel, not two (which is why MCP loopback is read-only). Covers the dual-registry 'HarnessAgentType' key (protocol under BaseAgentType, driver under BaseAgent, one DriverClass column selecting both), capability honesty (the runtime EMULATES what an adapter lacks, so over-claiming is a silent behavioural gap — Gemini CLI reports SessionResume: false and pays replay tokens), sandboxes where the provider owns process placement (adapters never spawn; local scopes a directory but does NOT contain the process, so networkPolicy is advisory there), credential resolution (grants → server env → AI_VENDOR_API_KEY__*, always as process env and never as prompt text), why every turn must write a real AIPromptRun (run totals are derived from step rollups, so a missing row means zero cost forever and a blind guardrail), the intentional opaque-super-step audit boundary, cloud deployment (never bake harness binaries into the MJAPI image), the four known gaps, and why the in-process SDK adapter was removed. Read before implementing, configuring or reviewing harness work.
  • Agent Memory Guide — The complete agent-memory architecture — note lifecycle (Provisional → Active → Archived), injection (strategies, recency-wins precedence, scoping), in-flight memoryWrites with its framework guard pipeline, and the Memory Manager’s hardening/consolidation/decay phases. Includes a configuration reference and troubleshooting queries. Read before touching anything under agent notes/examples or the memoryWrites capability.
  • Agent Skills & Plan Mode Guide — Two BaseAgent-framework capabilities that ship together (one migration). Skills (MJ: AI Skills) = reusable capability bundles (Instructions + bundled Actions + bundled sub-agents) an agent activates mid-run via a progressive-disclosure catalog (only name+description in the prompt until activation); resolved by AIEngineBase.GetSkillsForAgent through a three-layer gate (AIAgent.AcceptsSkills None/All/Limited × AISkill.Status × per-grant MJ: AI Agent Skills.Status); activation appends Instructions + widens the tool surface via a 'specific'-scoped ActionChange/SubAgentChange targeting the activating agent (applies at any depth, never cascades — a 'root' scope would be a bug for sub-agents). Permissions use full agent parity: a dedicated MJ: AI Skill Permissions table (User xor Role grantee × View/Run/Edit/Delete) with two access paths over one table — the cached, open-by-default AISkillPermissionHelper (@memberjunction/ai-engine-base, the runtime gate) and the closed-by-default AISkillPermissionProvider (@RegisterClass(PermissionProviderBase,'MJAISkillPermissionProvider'), the unified PermissionEngine/Sharing-Center view) — grantee-exclusivity enforced by MJAISkillPermissionEntityServer.Validate(); sharing gated by the Can Share Skills authorization (the old AI Skills Resource-Type sharing was retired). AIEngineBase.GetSkillsForAgent(agent, user?) takes an optional user to intersect the agent gate with the user’s Run permission, so the model’s skill catalog is permission-filtered. Users invoke a skill by typing /skill-name in the composer (mirrors @agent/#entity; picker filtered by the helper, chips use AISkill.IconClass/Color); selected IDs thread as ExecuteAgentParams.requestedSkillIDs (same client→resolver→runtime chain as planMode) and BaseAgent.preActivateRequestedSkills activates them at run start only if they survive the guard (agent-accepted ∩ user-permitted). Portable via SKILL.md (SkillMarkdownConverter + SkillImportExportService + the AISkill.ExportMarkdown/ImportMarkdown Remote Operations — names not IDs, unresolved bundle members become non-fatal warnings); see the Unified Permissions Guide for the two-access-path pattern. Plan Mode = a per-request HITL gate (AIAgent.SupportsPlanMode capability default-ON/opt-out × ExecuteAgentParams.planMode per-request default-OFF, root-agent-only) that blocks Actions/Sub-Agent until the agent presents a 'Plan' step and a human approves it via the existing MJ: AI Agent Requests pause/resume flow; rejection forces a re-plan because resumeAgent re-enables planMode only for Plan-step-originated resumes. Both 'Skill' and 'Plan' are non-terminal steps — deliberately NOT in the DB-CHECK-constrained AIAgentRun.FinalStep union (mirror the existing 'ClientTools' cast), but they ARE in AIAgentRunStep.StepType. v5.45 governance & observability: self-activation additionally requires the double activation gateAISkill.ActivationMode × AIAgent.SkillActivationMode, both 'Auto'/'RequestedOnly' defaulting to 'RequestedOnly' (resolved by GetAutoActivatableSkillsForAgent; the /skill requested path ignores ActivationMode but honors all availability gates); AIAgent.RequirePlanMode forces plan mode on every root run (SupportsPlanMode moot); runs stamp AIAgentRun.PlanMode; in plan mode, skill activations are legal only before approval (post-approval → Retry demanding a re-plan); every step touched by a skill records AIAgentRunStep.Skills (JSON Array<AgentSkillInvocation> — activation type, provenance-of-authority gate values, agent-stated reason from skills:[{name, reason}]; Actions/Sub-Agent steps carry skill attribution with native-grant precedence = NULL). Read before touching anything under Skills, AcceptsSkills/SupportsPlanMode/ActivationMode/RequirePlanMode, the skill-step/plan-step loop wiring, skill observability, or SKILL.md.
  • User Routines Guide — The user-owned “agents on my schedule” layer (P1.5, 5.45) — MJ: User Routines/Recipients/Runs, the single 1-minute dispatcher scheduled job (UserRoutineDispatcherDriver: claim-by-advancing-NextRunAt before running, ConcurrencyMode=Skip, bounded concurrency, per-routine isolation, runs AS the owner), pure schedule math in UserRoutineProcessor (shared with MJUserRoutineEntityServer so save-path and dispatcher agree), Scheduled vs Monitoring (OnChange via result hash), activation windows (StartAt/EndAt), Template-driven notifications (metadata-seeded default), linkage-only telemetry (AgentRunID/PromptRunID/ActionExecutionLogID — no duplicated token/cost), RequestedSkillIDs pre-arming, the non-startup UserRoutineEngine, the ng-user-routines widget set + conversations bottom-sidebar entry (gated by ShowRoutines prop AND entity-Read permission). Read before touching anything under routines, the dispatcher, or the conversations routines section.
  • Real-Time Co-Agents Guide — The live, low-latency agent stack — the Realtime agent type and Voice Co-Agent (one co-agent voices any target agent via the stable invoke-target-agent tool), the triple-registry plugin architecture (server/client realtime-model drivers + interactive-channel plugins, all ClassFactory + metadata resolved), client-direct vs server-bridged topologies, AIAgentSession lifecycle/janitor, interactive channels (the live Whiteboard), progress narration, observability, and the security model. Read before touching anything realtime / voice / agent-session / channel. Companion to plans/ai-agent-sessions.md.
  • Realtime Bridges Guide — The pluggable media-transport seam that connects the one realtime agent engine to external endpoints — Zoom/Teams/Slack/Meet/Webex/Discord meetings and Twilio/Vonage/RingCentral telephony — carrying bidirectional, media-agnostic tracks (audio/video/screen, full duplex). Covers: the AIBridgeEngineBase (@memberjunction/ai-bridge-base, metadata cache) / AIBridgeEngine (@memberjunction/ai-bridge-server, composition-not-inheritance coordination + the bridge.OnMedia → session.SendInput / session.OnOutput → bridge.SendMedia transport seam) pair; the BaseRealtimeBridge driver family (sibling to BaseRealtimeModel) and how to add a new bridge driver (subclass + @RegisterClass(BaseRealtimeBridge, '<X>Bridge') + capability-gated virtuals, LoopbackBridge as the worked example); the IBridgeProviderFeatures capability model (engine gates the flag, RequireFeature throws as defense-in-depth via BridgeCapabilityNotSupportedError); the platform-agnostic TurnTakingPolicy (passive/active/hybrid); the 5 entities + their *EntityServer invariants; and the roadmap (Phase 0/1 shipped, 2+ planned). Read before touching anything bridge / meeting / telephony / media-transport, or before adding a bridge driver.
  • Remote Browser Channel Guide — The in-house realtime channel where an agent drives a real, live browser while it talks (sales demo, support walkthrough, trainer agent — demonstrate then “your turn, you try”). Built on the principle that every backend exposes the same primitive (a CDP endpoint), so the browser work lives once, generically, in @memberjunction/computer-use (enriched additively with selector-aware actions, screencast, MouseMove, accessibility/element perception) and the Remote Browser layer just maps vocabulary + manages session lifecycle. Covers: the layer cake (computer-useremote-browser-base universal contracts + RemoteBrowserEngineBase registry → remote-browser-cdp shared CdpRemoteBrowserSession kit + lossless mapRemoteBrowserAction → 5 thin backends → remote-browser-server RemoteBrowserEngine/RemoteBrowserChannel); the AIRemoteBrowserProvider registry + IRemoteBrowserProviderFeatures capability gating (two-layer, like bridges); control modes (AgentOnly/ViewOnly/Collaborative) vs control strategies (ComputerUse default vs NativeAI/Stagehand, capability-gated); goal-driven control (§9 — set a high-level goal instead of granular clicks: browser_AchieveGoalExecuteRemoteBrowserGoalRemoteBrowserEngine.AchieveGoal → pure dispatchRemoteBrowserGoal strategy switch → RunComputerUseGoal on the session’s OWN adapter; model-blind credentials via {{label}} context injection resolved at the CDP keystroke boundary; Collaborative pause-on-takeover; vision-model auto-selection + the MJProgressComputerUseEngine startup binding); and how to add a backend (subclass BaseCdpRemoteBrowserProvider, implement AcquireSession + a 3-method ICdpSessionBackend, @RegisterClass(BaseRemoteBrowserProvider, '<X>RemoteBrowser'), seed a row). Read before touching anything under packages/AI/RemoteBrowser/ or before adding a browser backend.
  • Realtime Session Capture & Recording Guide — How a voice session is captured: per-turn transcript with start/end timing + speaker identity, and the optional audio recording. Covers the create-on-start/update-on-complete turn lifecycle, recording on both topologies (server-bridged mixer and client-direct browser capture via RealtimeAudioRecorder), the seekable 16-bit PCM WAV + capture-time peaks.json waveform sidecar, agent-audio mixing (AttachRemoteStream / OnRemoteMediaStream), and streaming playback via mj-storage-media-playerCreateMediaAccessToken → the GET /media/:fileId Range route. Read before touching persistRealtimeTranscript, the recording capture/store, or playback.
  • Conversations UX Stack Guide — The 3-layer architecture for every chat surface in MJ — @memberjunction/conversations-runtime (pure-TS engine: agent dispatch, default-agent resolution, mentions, bridge, streaming, client tools, sessions observability) ↔ adapters (INotificationAdapter / IActiveTaskTracker / ISessionsAdapter) ↔ @memberjunction/ng-conversations (Angular widget) ↔ your app. Covers: when to use each layer, the slot system (6 slots: header / agentPresence / emptyState / messageRenderer / messageExtra / demonstrationSurface with project / wrap / subclass modes), Before/After cancelable events (beforeAgentTurn, beforeToolInvoked, beforeResponseFormSubmitted with event.Cancel = true enforced; sessionStarted / sessionChannelStateChanged / sessionEnded informational), persona inputs ([showAgentCharacter] + agentCharacterConfig), --mj-chat-* design tokens, default-agent resolution chain (explicit → app-scoped → global → code-const Sage fallback), sessions adapter bridging to PR #2787’s VoiceSessionService, multi-provider scoping, runtime pre-warming via @RegisterForStartup. Read before building any chat surface (overlay, full workspace, embedded panel) OR before forking the widget — slots + events almost certainly cover the use case.
  • Predictive Studio Guide — How MJ trains predictive models on a client’s own data (member retention/renewal, lapse/lead scoring) and scores records with them — core MJ, not an OpenApp, composed onto existing substrates. Covers the 4-layer architecture (data → feature → model → inference); the self-managing Python sidecar (MLSidecar in @memberjunction/predictive-studio-sidecar — the sqlglot-ts bundled-microservice pattern: managed child-process spawn on an ephemeral port is the default, Docker-free; remote-URL mode for scaled deployments; npm run setup:python; the /train+/predict+/health contract defined once in @memberjunction/predictive-studio-core); the FeatureAssemblyExecutor correctness backbone (one code path × three contexts; the raw-vs-preprocessing fit-once/apply-everywhere anti-skew split with fitted_preprocessing travelling with the model; first-class point-in-time as-of assembly; the LeakageGuardEnforcer deny-list + post-train single-feature-dominance flag → plain-language warning + blocked promotion); training (TrainingEngine → immutable, versioned MJ: ML Models distinct from MJ: AI Models, with a locked holdout for honest metrics + full lineage); scoring (MLModelInferenceProcessor — a new 'ML Model' Record Set Processing work type registered via @RegisterClass without forking the substrate; ephemeral by default, write-back via OutputMapping; on-demand + scheduled); the generic ExperimentExperimentSessionExperimentSessionIteration primitive + the ExperimentOrchestrator wave loop (leaderboard / pruning / budget gate, run through RSP waves — reusable beyond ML); the MJ: ML Algorithms / Use Cases / Use Case Rankings 6×7 guidance matrix; the (planned) Remote Operations + Actions + Model Development Agent; the lazy-loaded Studio dashboard (PredictiveStudioDashboardComponent + PredictiveStudioEngine + 6 panels + embedded mj-conversation-chat-area copilot); a train+score walkthrough; and the live integration test (PS_INTEGRATION=1). Read before touching anything under packages/AI/PredictiveStudio/**, the MJ: ML * / MJ: Experiment* entities, the Predictive Studio dashboard, or before adding a trained-model / feature-assembly / experiment-search capability.
  • Dashboard Best Practices — Comprehensive patterns for building MJ dashboards including:
    • Architecture and naming conventions
    • State management with getter/setters
    • Engine class patterns (no Angular services for data)
    • User preferences and local caching
    • Page Chrome — the shared <mj-page-layout> + <mj-page-header> + <mj-page-body> trio that every Explorer dashboard uses, with slot rules ([meta]/[actions]/[toolbar]) and documented exceptions
    • Layout patterns, permission checking, and more
  • Explorer Chrome Conventions — The full rulebook for MJ Explorer’s shared chrome — slot rules ([meta] is state, [actions] is verbs, [toolbar] is secondary controls), filter UI decision tree, and the canonical exception list. Sub-pages of left-nav shells use <mj-page-header-interior> (Section 10) — a two-row card with [Title] + [Subtitle] inputs and the same slot conventions as <mj-page-header> — NOT their own <mj-page-header> (which would produce a doubled-header). Read before doing chrome work or deciding to deviate. (Lives in plans/, not this folder.)
  • Forms Architecture Guide — How MJ renders/edits entity records across all surfaces from one set of forms — full-page tabs, modal dialogs, and slide-in panels. Covers:
    • The 4-layer architecture (MjEntityFormHostComponent → presentation shells → MJFormPresenterService), all in @memberjunction/ng-base-forms with zero Explorer/Router coupling
    • How generated, custom (*Extended), and interactive (EntityFormOverride) forms coexist, plus the variant picker
    • EntityFormConfig — per-instance control over toolbar / related-entity sections / collapsibility / width / in-form navigation, applied without regenerating any form
    • Form contributionsBaseFormPanel metadata can claim a related-entity grid (last-wins); the container fills in DisplayInForm relationships the template did not bake. CodeGen output is unchanged. See PANELS.md and /plans/form-contributions.md
    • Form chrome — L0 CodeGen, L1 inclusion (Primary / More / None), L2 Auto ranker, L3 MJ: Form Chrome Rules, L4 user order. Policy decorates labels/icons only. See §7d
    • Imperative (forms.open({...})) and declarative (<mj-form-dialog> / <mj-form-slide-in>) usage
    • Read this before building any bespoke “edit a record in a dialog/slide-in” component — the generic capability almost certainly covers it.
  • Lazy Loading Guide — How MJExplorer’s code-split lazy loading works:
    • Adding new dashboard components (zero config — just @RegisterClass + feature module)
    • Making a package lazy-loadable (add subpath exports to package.json)
    • Adding new feature modules with subpath exports
    • How the auto-generated lazy config is produced by mj codegen manifest --lazy-config
    • Troubleshooting lazy loading issues
  • Navigation and Routing Guide — How the shell owns URL state, back/forward navigation, adding URL-synced sub-navigation to a component.
  • UI Layering Guide — 🚨 The standard for every MJ repo and every app built on MJ. The four-layer UX architecture that makes components reusable instead of copy-pasted: L0 pure-TS domain runtime → L1 presentational widgets (props in, events out, no data access) → L2 composite widgets (may read data, but only through ProviderToUse; never navigate) → L3 Explorer surfaces (entity forms + resource/dashboard components, the only layer allowed to touch NavigationService). Covers:
    • The two hard boundaries — no Router/Explorer imports below L3, no domain logic at L3 — and the “which layer is this?” decision table
    • The Before* / After* cancelable event contract (Cancel + CancelReason, After* suppressed on the canceled path), naming conventions, and why Before* handlers must be synchronous
    • Data access per layer and the multi-provider rule that makes it matter
    • Package split (*-entities / *-engine-base / *-ng-widgets / *-ng) with the allowed-dependency table
    • Enforcement — shipped as @memberjunction/standards: mj standards adopt scaffolds a repo, mj standards check runs what it adopted, opt-in per package via "mjUILayer" and version-pinned so a new standard never changes an existing repo’s result
    • A step-by-step recipe for migrating an existing 400-line do-everything screen
    • Read before building any new UI in any MJ repo, and before adding a component to a Generic package.
  • Optimistic-UI Save Pattern — ⚠️ Status: proposal for review, not yet an adopted convention. Documents a pattern several Angular surfaces adopted independently (render the user’s action immediately, reconcile after Save() resolves) plus a proposed framework hook (EntitySaveOptions.OnValidated) that generalizes it. Includes two competing implementations — generic vs. inline — for reviewers to compare before any codebase-wide sweep. Read for context on perceived-latency work on chat/list/settings surfaces; do not treat as settled guidance until the proposal is resolved.
  • Media Player package — The generic mj-media-player (zero-MJ-dep audio/video player: transport, real waveform scrubber, playback speed, ±skip, time-synced transcript, multi-track video grid) and the MJStorage-bound mj-storage-media-player wrapper (resolves an MJ: Files id to a permission-gated, Range-streamed /media URL with server-supplied waveform peaks). Used by the artifact audio/video viewers + previews and the realtime session-review overlay.
  • Integration Testing Quickstart — The integration-test tier — real provider stack (live DB + GraphQL, real cache managers/engines), headless, sitting between unit tests (mocked) and browser/computer-use regression (“mock the top layer, keep everything else real”). Covers: the one-library-two-front-ends architecture (@memberjunction/testing-integration check bundles on one IntegrationCheckRegistry, consumed identically by the tsx suite scripts / run-all.ts aggregator AND the metadata-driven “Integration Test” TestType dispatched by IntegrationTestDriver through mj test); the dedicated-process rule (the instrumented cache MUST be LocalCacheManager’s first caller — MJ_INTEGRATION_TEST=1 on the CLI; server-transport suites refuse to run inside a live MJAPI); the two proof techniques (UniqueFilter cold-cache determinism with zero mutation + InstrumentedLocalStorageProvider per-category counters); tiers/gating (deterministic / mutation / live-model via RUN_MUTATION_TESTS / RUN_AGENT_TESTS, plus PS_INTEGRATION flows); every way to run it (npm run test:integration, per-suite tsx, mj test run/suite, the smoke test, the cross-server rig, the golden diff, the CI PR gate) and all four authoring methods (add a check to a bundle · new bundle + Test row + suite membership · metadata-only composition · standalone script). Read before running or extending the integration suites, or touching anything under packages/TestingFramework/testing-integration/.
  • Angular Testing Guide — How to test MJ Angular components. MJ runs Vitest everywhere (Jest is deprecated). Two complementary styles: class-level tests (instantiate with new, exercise pure logic — getters, navigation methods, @Output emission — fast, no DOM; these already exist and stay) and DOM-level tests (render into a headless jsdom DOM with TestBed + ComponentFixture, set @Inputs, dispatch DOM events, assert on rendered output — the half of a component’s contract that lives in the template: @if gating, bindings, (click) wiring, conditional classes, a11y attributes, which class-level tests cannot see). Includes a decision tree for which style to write, and when to reach for live Playwright/e2e instead (WebRTC and real-media paths). Rollout plan: plans/testing/angular-dom-testing-rollout.md.
  • Turbo Remote Cache — How MJ’s CI build cache is wired, worked with locally, and operated. Covers: the four job-level env vars and which are secrets vs variables; why every way of breaking it is silent (no token → Remote caching disabled; no signature key → reads the cache and uploads nothing; a reusable workflow without secrets: inherit → empty token while vars propagate and make it look configured; and working correctly → no message at all); the two mechanisms that exist because of that (the preflight step and the coverage test); what artifact signing does and does not protect against (tamper-evidence for storage, not a defense against a compromised job — every job shares one key); local setup, including the signature key you must also export or your uploads silently fail; and runbooks for rotating the key (invalidates the whole cache), rotating the token (cheap), and turning the cache off. Read before adding a workflow that runs turbo, or when CI has become mysteriously slower.
  • Release Engineering Runbook — The operator manual for MJ’s four release operations: routine Edge releases (the next → main publish, plus the proposed weekly/on-demand automation), the LTS candidate cut (the pre-exit dance, with a worked 6.1 example), LTS line patch releases (one dispatch of publish.yml’s LTS path), and the certification flip (release-lines.json + dist-tag-all + scorecard). Safety rails for all four. Policy lives in plans/lts-process.md (canon); concepts for non-operators in VERSIONING.md. Read before pushing any release button.

The complete/ subdirectory holds guides whose subject matter has been fully implemented and absorbed into the codebase. Kept for historical context — newer guides should go in the top-level folder.


A topic earns a guide in this folder when:

  1. It spans multiple packages or touches the framework as a whole — package-internal guidance belongs in that package’s CLAUDE.md.
  2. It captures non-obvious patterns a developer would not derive by reading the code alone (gotchas, conventions, “why we do it this way”).
  3. The patterns have been validated in production — speculative designs belong in plans/, not here.

When adding a guide, also:

  • Add an entry to this README under the appropriate section. This README is the single index — root CLAUDE.md links here rather than maintaining a parallel list, so there is nowhere else to update.
  • If the guide is a hard requirement for a specific subtree, add a pointer from that subtree’s CLAUDE.md (e.g. packages/Angular/CLAUDE.md) so it loads when someone works there.