Protected_IDs of skills already activated during this run. Prevents re-activation from re-appending the same instructions to context / re-pushing duplicate actionChanges/subAgentChanges entries when the LLM references an already-active skill again.
protected, not private: a subclass that gates activation with its own policy (a tenant
licensing check, an entitlement model) needs to know what actually activated — to hand the
active skill to a search as its principal, to stamp a UI chip, to persist a mode. Read it;
mutate only through the activation paths. See also ActivatedSkillIDs.
Protected_Service for evaluating agent decisions inline via fast decision models.
Protected_Reverse map from sanitized tool name back to Action, for the turn currently being prepared.
Set whenever this run declares Actions as native tools (plan §8.1) and read when the turn's
response comes back, because a tool call names run_ad_hoc_query and the dispatcher needs
"Run Ad-hoc Query". Left undefined on the envelope path so the loop can tell the two apart.
Protected_Full observability records for every skill activated this run — one AgentSkillInvocation
per activation, carrying activation type ('requested' | 'auto'), the provenance-of-authority
gate values that admitted the skill, and the agent-stated reason when self-activated.
Serialized onto AIAgentRunStep.Skills: Skill steps record their own activation(s), Prompt
steps record the full set in effect for the turn, and Actions/Sub-Agent steps record the
skill(s) that granted the executed tool (see getSkillAttributionForAction /
getSkillAttributionForSubAgent).
protected for the same reason as _activatedSkillIDs.
Protected_Shared root-tree cumulative budget tracking object for the current execution tree.
Initialized at the start of Execute (or inherited from params.treeBudget
when running as a sub-agent) and forwarded to all child and related sub-agent runs.
Static ReadonlyAgentThe prefix of the abort reason the wall-clock guard in Execute raises when a run
outlives maxExecutionTimeMs. The guard appends the agent and the budget after it; a reader
(CancellationReasonForAbort) checks the prefix, never the wording after it.
Static ReadonlyCarryTool families whose step results are eligible for prior-turn carry-forward —
derived from CarryForwardToolFamily (the single source the stamp sites use).
Read-tool families only: memory writes, pipelines, and client tools also record
StepType='Tool' steps but must never be replayed as reusable results.
Protected Static ReadonlyDEFAULT_System-wide safety net on prompt iterations, overridable per run via
ExecuteAgentParams.absoluteMaxIterations.
A static rather than a local const because two places now depend on the same number: the
limit check that STOPS a run, and isFinalPermittedIteration, which has to predict
that stop one turn ahead. Two copies of 5000 would be a silent mismatch the moment either
moved — the gate would force tool_choice: 'none' on the wrong turn, or fail to force it
on the right one, and neither shows up as an error.
Static ReadonlyDefaultThe mode a run uses when the caller does not set ExecuteAgentParams.stopContinuationMode.
Protected Static ReadonlymaxBudget for the stopped-run carry-forward message. Smaller than a standalone tool result on purpose: this rides at the front of a continuation turn's context, and a stopped run can hold dozens of search results. ~7k tokens keeps the useful values and drops the long tails.
Static ReadonlyStoppedPlaced just before the user's new message when a stopped run's stack is resumed, so the model knows the turns above it were interrupted and what follows is how to continue.
Static ReadonlySummarizeDisplay name of the seeded system prompt behind summarizeRange's recursive sub-call (see metadata/prompts/.summarize-range-prompt.json). Resolved with a trimmed, case-insensitive compare — never an exact-case inline literal.
ProtectedActivatedThe skills activated so far in this run (requested or self-activated), in activation order.
ProtectedAgentProtectedAgent name hierarchy from root to current (e.g. ['Sage', 'Skip', 'Researcher']).
Access the current run for the agent
Accessor for the agent's type instance
ProtectedAgentOverridable accessor for the current agent instance's agent-type state
ProtectedDefaultEngine-default wall-clock timeout applied to any agent run whose
ExecuteAgentParams.maxExecutionTimeMs is not set. Sub-classes can
override to globally change the default. Intentionally generous
(2 hours) — tighten per-run for interactive scenarios.
ProtectedDepthProtectedDepth of this agent in the execution hierarchy (0 = root).
ProtectedFileProtectedAccumulated file outputs (PDF, Excel, Word, etc.) produced this run.
Mirrors the existing MediaOutputs accessor pattern but is scoped to
driver sub-classes since it's a more internal collection.
ProtectedmaxProtectedCharacter budget (~4 chars/token) for a SINGLE standalone artifact-tool result injected into
the conversation. A get_full on a large artifact can otherwise dump the whole thing into
context and overflow the model's window — the exact failure pipelines exist to avoid. Override
in a subclass to tune. Pipelines are unaffected: their intermediate results never flow through
here, and the executor already caps a pipeline's final output.
Gets the currently accumulated media outputs for this agent run.
Array of promoted media outputs
ProtectedParentProtectedParent step counts used to build the 2.1.3 hierarchical step label.
ProtectedProviderReturns the active metadata provider for this agent run. Subclasses MUST
use this getter (rather than new Metadata() or Metadata.Provider) so
that per-request provider isolation is preserved on the server.
ProtectedResolvedThe resolved FileStorageAccount ID for this agent run. Set during Execute() via the hierarchical resolution chain (Runtime → Agent → Category → Type → fallback). Included in the ExecuteAgentResult so AgentRunner can route file artifact uploads.
Subclasses can read this to customize storage behavior based on the resolved account.
ProtectedscratchpadGets the shared AgentTreeBudget tracking cumulative steps, tokens, cost, and elapsed wall-clock duration across the root agent and all descendant sub-agents.
Gets the current validation retry count for the agent run. This count tracks how many times the agent has retried validation during the FinalPayloadValidation step.
ProtectedvolatileThe strings whose presence in a system prompt's unrendered template text means the template still renders the volatile state itself, so the trailing fragment must be suppressed. Defaults to VOLATILE_TEMPLATE_MARKERS: the three block headings plus the date and payload placeholders. Extension point — an agent type whose template lays the state out under other headings overrides this to return its own markers.
ProtectedactionWhether THIS action may use the run's runtime API key for THIS driver class. The default is
yes: the run was started on those keys, and an action that calls a vendor on the user's behalf
(Generate Image) is doing what the prompts do. Override to narrow it — an agent that knows
which of its actions talk to which vendor can refuse everything else, and a refusal costs the
action nothing but the customer's key: it falls back to the platform key as if the run had none.
Under a 'RuntimeOnly' credential scope a refusal leaves the action with no key for that class.
ProtectedactionHelper method to get a value from a nested object path. Supports both dot notation (obj.prop) and array indexing (arr[0]).
/**
The ExecuteSingleActionOptions the main loop passes for this run's agent type: the
circuit-breaker exemption when the type has opted out (BaseAgentType.UsesActionCircuitBreaker
is false — Flow), otherwise none. Kept as a seam so a subclass can widen or narrow the
exemption without touching the loop.
ProtectedactivateActivate the requested skills that passed every gate: record the activation step, enable each skill's capabilities, persist Conversation-scoped ones, and append the activation message.
ProtectedactiveThe skills active for THIS RUN as a whole: what the root agent activated (carried down through
ExecuteAgentParams.parentActivatedSkillIDs, since skills activate on the root only) plus anything
this instance activated itself. Stamped as Context.ActiveSkillIDs on every action call and handed
to every sub-agent, so a Scoped Search issued three levels down still binds to the root's skill.
Protectedappendaction results as ONE tool turn — a tool_result block per call, paired by id — when the turn's results go back natively; otherwise the markdown user message as before. An action that cannot be paired with a call id keeps the markdown message for itself.
Protectedappendreplays the model's own tool-call turn into history once per turn, so the tool-result turns that follow have a call to answer (every provider requires it; BaseLLM validates it). A no-op when the turn's results go back as the markdown user message.
ProtectedapplyResult type for applyActionChanges method. Contains both the modified actions and any dynamic execution limits.
ProtectedapplyProtectedapplyProtectedApplies auto-alignment rules to includeResponseTypeDefinition based on other flags.
When a documentation flag (e.g., includeForEachDocs) is explicitly set to false, the corresponding response type section (e.g., forEach) should also be excluded unless explicitly set otherwise.
Auto-alignment mappings:
While the master switch is off (DecisionsEnabled), the decisions and finishIf docs and response fields are off, even when set explicitly.
The merged params object to modify in place
OptionalexplicitResponseType: Record<string, unknown>The explicitly set response type config from agent/runtime (not schema defaults)
ProtectedapplyProtectedThe message half of injectStoppedRunResults, applied once the pre-execution phase has settled so it never races the memory and RAG injections running beside the load.
Full stack: the stopped run's own turns are spliced in ahead of the user's new message, in the shape the loop built while running, so the agent resumes where it stopped. When the stack cannot be resumed (no recorded prompt, or its anchor is missing) this falls back to the summary. Summary: the completed results go in as one carried-forward message. Fail-soft.
ProtectedapplyProtectedApplies runtime SubAgentChanges to a base sub-agent set — the sub-agent counterpart of applyActionChanges. Returns a NEW array (never mutates the cached base set).
ProtectedassembleThe message array sent for ONE request under 'trailingMessage' placement: a copy of the history
with every non-system message's fragment tag literals escaped, then the real fragment last.
Escaping at send time (rather than where text enters the history) covers every source at once — user turns, action results, sub-agent results, skill activations — without rewriting stored data, and it is deterministic, so the cached prefix stays byte-stable across iterations. System messages and framework-authored volatile state messages are left alone.
OptionalisAppendOnly: booleanProtectedattachLinks a decision call's prompt run to its step, as the Prompt step links its own, so the run's
cost and token totals and its MaxCostPerRun / MaxTokensPerRun checks count the call.
ProtectedattemptProtectedAttempts to recover from a context length exceeded error using multiple strategies. Uses escalating strategies: remove old results → compact old results → compact all → trim user message. This approach preserves the user's original request while removing stale tool results.
Agent execution parameters (conversationMessages will be modified)
Current payload to carry forward
The original error message from the failed prompt
OptionalmodelSelectionInfo: AIModelSelectionInfoModel selection information containing the model and vendor used
A Retry step with reduced context or Failed if recovery unsuccessful
ProtectedbuildProtectedThe user-role record of what the model asked to run on an Actions turn ("[You invoked ...]").
Shared by the live Actions step and the stopped-run resume, so a resumed turn reads exactly
like one the loop wrote. See the Actions step for why it is a user-role annotation.
ProtectedbuildProtectedThe action-results message body: a header naming any failures, then each result in the compact markdown shape. Shared by the live Actions step and the stopped-run resume.
ProtectedbuildProtectedBuilds the agent-invariant AgentBaseCatalog — the resolved sub-agents + actions and their formatted markdown, plus the base agent-type prompt params. Computed once per agent and cached on AIEngine (see gatherPromptTemplateData); does NOT apply any runtime overrides.
ProtectedbuildProtectedBuilds merged agent type prompt params from schema defaults, agent config, and runtime overrides.
Merge precedence (lowest to highest):
The agent type entity with schema definition
The agent entity with configured values
OptionalruntimeOverrides: Record<string, unknown>Optional runtime overrides from ExecuteAgentParams.data
Merged prompt params object
ProtectedbuildBuilds a compact hint listing the current user's permitted runnable agents when a task-graph or folded sub-agent step references an unknown or inaccessible agent name.
OptionalcontextUser: UserInfoProtectedbuildThe failed ActionResult a blocked call returns in place of dispatching.
ProtectedbuildProtectedBuilds the summarizeRange recursive-sub-call host: resolves the seeded 'Summarize Conversation Range' prompt (priority-ordered cheap models — the RLM "strong root model, cheap sub-call model" split) and runs it via the standard prompt runner so the AIPromptRun records itself.
ProtectedbuildProtectedBuilds hierarchical step string from parent and current step counts.
Examples:
Exposed as protected so driver sub-classes can emit step labels that
match the framework's 2.1.3 nesting convention.
Current agent's step number (1-based)
Array of parent step counts from root to immediate parent
Formatted hierarchical step string, or undefined if currentStep is undefined/null
ProtectedbuildProtectedBuilds the user-oriented initialization progress message for an agent run.
_depth === 0): 'Getting ready...'_depth === 1): 'Starting <SubAgentName>...'_depth > 1): '<ParentBreadcrumb>: Starting <SubAgentName>...'Execution parameters for the current agent
Formatted initialization progress message
ProtectedbuildProtectedBuilds a per-run PipelineToolRegistry that unifies the three pipeline-able substrates behind one namespace: built-in transforms, the agent's effective Actions, and the run's artifact tools. Transforms register first so their reserved names win; a source whose name collides with a transform is skipped for pipeline use (still callable normally) and logged, rather than aborting the whole pipeline.
ProtectedbuildProtectedBuilds the editable plan-approval AgentResponseForm: the Markdown-rendered plan (with an
Edit toggle so the human can amend it before approving), an optional feedback field that
travels back to the agent with the decision (most useful on Reject — it steers the re-plan),
and an Approve/Reject button group. Override to change the card's layout (e.g. split the
plan into per-step checkboxes instead of one field).
Approval is a HIGHER-ORDER signal, not just a form reply: conversation hosts detect
decision === 'approve' on this form and switch the conversation out of Plan Mode
(see ng-conversations' plan-decision handling), so the follow-up run executes the approved
plan instead of planning again. Rejection keeps Plan Mode on — the agent re-plans with the
feedback in context.
ProtectedbuildProtectedBuilds a user-oriented progress message for a prompt execution step without exposing internal framework terminology ("initial prompt", "payload", "context") or duplicating the sub-agent name when prefixed by formatHierarchicalMessage.
Execution parameters for the current agent
OptionalpreviousDecision: BaseAgentNextStepPrevious step decision when re-prompting
Base progress message (before formatHierarchicalMessage breadcrumb prefixing)
ProtectedbuildBuilds the fully-populated RealtimeSessionRunnerDeps from this agent's collaborators.
Each dependency is a thin closure over BaseAgent state so the runner stays decoupled from
metadata/DB. The closures cover: target delegation (via ExecuteSubAgent), non-target
tool execution, transcript persistence (as ConversationDetail), and usage checkpointing
(onto the long-lived prompt run).
The execution parameters.
The agent configuration.
The resolved realtime model + identifiers.
The long-lived prompt run for usage checkpoints (may be null).
The assembled deps object.
ProtectedbuildProtectedThe stopped run's turns in the shape the loop builds, or null when they cannot be recovered.
The latest prompt step that recorded a full stack holds the run's whole message stack as it went into that prompt; the prompt steps after it recorded only the messages appended since (see capturePromptStepMessages). The first prompt step recorded the stack the run started from (history plus this run's injections, which the new run adds for itself). The difference is the run's own turns. If the last prompt completed, its decision and the steps after it are rebuilt with the loop's own builders.
ProtectedbuildProtectedBuilds the message appended to conversationMessages when skill(s) activate — this is what
actually puts each skill's Instructions into effect for the rest of the run. Override to
change formatting (e.g. a more compact representation for a high skill-activation-count agent).
ProtectedbuildProtectedBuilds the AgentSkillInvocation observability record for a skill activation — capturing WHO pulled the trigger and the provenance-of-authority gate values in effect at activation time, so auditors can see exactly which configuration admitted the skill even if that configuration later changes.
Optionalreason: stringProtectedbuildProtectedWhat the stopped run's last completed prompt decided, rebuilt as the loop would have written it: the model's native call turn when results go back natively, the invocation record and the results of the actions that finished (and a not-executed result for the ones that did not) for an Actions turn, otherwise the completed results as a carried-forward message. Any native call left without a result is then answered, so the history is valid for every provider.
ProtectedbuildBuilds the framework-authored user message that carries the loop agent's volatile state as the
final message of the request; returns null only when every block is turned off, or when the
system prompt template in this database has not yet synced and still embeds the state itself
(see the guard below). The blocks mirror the sections the template used to render (see
RuntimeStateFragmentBuilder), and each honors the same include flag the template did.
Why this exists: provider prompt caching is a prefix match over tools → system → messages, so state that changes every iteration INSIDE the system prompt invalidates the entire history each call. Measured on Sage: 36% → 83% cached on Gemini 2.5 Flash, 12% → 96% on Claude Opus 5 (with the Anthropic adapter placing its breakpoint before this message), output quality unchanged.
The message carries STATE only — never rules. It is marked metadata.volatileState so adapters
can recognize it without depending on this package's tag names.
OptionalsystemPrompt: MJAIPromptEntityExtendedProtectedcapProtectedBound a standalone tool result to maxStandaloneToolResultChars: return a head slice
plus a redirect that teaches the agent to page (get_rows) or reduce (pipeline) instead of
reading a whole large artifact. Mirrors how read/search tools cap output at the tool boundary.
ProtectedcheckApplies the three circuit-breaker rules to a call that is about to be dispatched. Returns a blocked result, with the rule that fired, when the call must not go to the action engine; null when it may proceed. Rules are checked audience → fatal → identical-arguments → budget.
ProtectedcheckProtectedcheckChecks if all minimum execution requirements are met for actions and sub-agents.
NOTE: This method intentionally uses only database-configured AgentActions, not _effectiveActions.
MinExecutionsPerRun is a database-only concept - dynamically added actions via actionChanges
do not have minimum execution requirements. If minimum requirements for dynamic actions are
needed in the future, consider adding a minActionLimits property to the ActionChange interface.
The agent to check
The current agent run
Array of violation messages (empty if all requirements are met)
ProtectedcheckProtectedPre-turn fallback: compacts synchronously when the assembled window is already over
the trigger budget BEFORE the first prompt of this run, then splices the fresh
summary into the live message array. Only runs with an EXPLICIT configured budget
(agent or type ContextWindowMaxTokens) — before the first prompt the model is
unknown, and compacting against the conservative default would over-trigger on
large-context models. The post-turn hook (real model known) covers those.
Skipped under a history floor (ConversationHistoryFrom): a summary folds in the
conversation from its first message, which is what the floor excludes.
ProtectedcheckChecks if Planning Designer called required research actions/agents
ProtectedcheckProtectedEvaluates whether any root-tree cumulative budget ceiling (maxTreeSteps, maxTreeTokens,
maxTreeCost, maxTreeDurationMs) on the shared AgentTreeBudget has been reached
or exceeded.
When a ceiling is breached, sets budget.breachedReason with a descriptive diagnostic
message so both the breaching agent and any ancestor agents in the call tree can terminate
immediately with step: 'Failed' and terminate: true.
Optionalparams: ExecuteAgentParamsOptional execution parameters for the current run.
Guardrail evaluation result indicating whether a tree budget limit was exceeded.
ProtectedcheckChecks per-(Agent, User) action policy narrowing rules (MJ: AI Agent User Policies)
before dispatching an action. Returns a synthetic ActionResult when the action is denied,
deferred outside a configured time window, or requires explicit user approval.
OptionalcontextUser: UserInfoProtectedclassifyNames the rule behind a failed action summary so the directive matches what actually
happened. A blocked call reports the rule that blocked it. A dispatched failure is fatal if
recordActionFailure locked the action out (the summary has no access to the call's
arguments, so the decision is read back rather than re-derived from the message); otherwise
the budget is checked BEFORE the identical-arguments rule, because once the budget is spent
the action is blocked whatever the arguments are, and telling the model to change them
would send it in circles.
ProtectedclearClears the parameter-aware failure history for an action after it succeeds, so the identical-arguments rule and the consecutive-attempt budget start over.
ProtectedcloneProtectedBest-effort deep clone for sub-agent payloads. We need this so two parallel sub-agents can each receive their own working copy — without it, mutations by one in-flight sub-agent would race the others' reads.
Uses structuredClone (Node 17+) where available; falls back to a JSON
round-trip (ToPlainJSON) for environments without it AND when
structuredClone throws (a Proxy such as a live JSONType <Field>Object
view, or functions). Returns the original value — with an error logged —
only when even the JSON clone fails.
JSON fallback caveats — the round-trip is not shape-preserving:
Date → ISO stringMap, Set, RegExp, typed arrays → {}undefined values and function-valued properties → droppedBigInt → throws (caught by the outer try/catch, returns original)Exposed as protected so driver sub-classes performing their own parallel
dispatch get the same payload-isolation guarantee.
ProtectedcompactProtectedCompacts a message using configured compaction mode.
The message to compact
Compaction configuration
Agent execution parameters for context
Compacted content string
ProtectedcontentProtectedConverts ChatMessageContent to a string representation. Handles both simple strings and content block arrays. Text blocks keep their text; image, audio, video and file blocks become a short stub instead of their base64.
The message content to convert
String representation of the content
ProtectedconvertConverts UI markup (@{...} syntax) in user messages to plain text. This prevents agents from being confused by UI-specific JSON syntax and reduces token usage.
Modifies the messages in-place, converting:
The conversation messages array to convert (modified in-place)
ProtectedcreateProtectedCreates an AIAgentRunStep for message compaction operations. Records the compaction attempt with context about the message being compacted.
The AI prompt used for compaction
The message being compacted
Agent execution parameters
The created run step entity
ProtectedcreateOptionalcompleted?: {For steps whose outcome is fully known BEFORE the row exists (e.g. recording an
already-finished compaction pass): applies finalizeAgentRunStep in memory
so the single queued INSERT carries the terminal state, and no follow-up
finalizeStepEntity UPDATE is needed — one DB write instead of two. Do NOT use
for steps with real work between start and finish; those stay two-phase so a
crash mid-work leaves a visible Running row.
OptionalinputData?: anyOptionalparentId?: stringOptionalpayloadAtEnd?: anyOptionalpayloadAtStart?: anyOptionalskills?: AgentSkillInvocation[]Skill-invocation records to persist on AIAgentRunStep.Skills for this step. When
omitted, Prompt steps default to the full set of skills currently in effect
(_skillInvocations) so prompt injection is always visible; all other step
types default to no skill linkage. Pass explicitly for Skill steps (the activation
performed) and Actions/Sub-Agent steps (the skill(s) that granted the tool).
OptionaltargetId?: stringOptionaltargetLogId?: stringProtectedcreateProtectedCreates a chat message containing sub-agent execution results.
The sub-agent that was executed
The result from sub-agent execution
A formatted message with sub-agent results
ProtectedDecisionsThe master switch for decision-model use by this agent: decisionsEnabled in its merged
agent-type prompt params (type default < agent < the run's data.__agentTypePromptParams).
Anything but true is off, the default.
While it is off the agent never asks a decision model on its own. Each automatic use checks it
before it asks, whatever its own setting says: inline decisions (processTurnDecisions),
finishIf gates (finishIfSettings), decision discovery (isDecisionDiscoveryOnFor),
the payload change check (isPayloadFeedbackCheckOn), catalog narrowing
(ensureCatalogNarrowing) and the Memory Manager's note gate. And
applyResponseTypeAutoAlignment keeps the decisions and finishIf docs and response
fields out of the prompt. While it is on, each use keeps its own setting, and each is off by default.
Explicit uses never read it: a Flow agent's Decision step (executeDecisionStep) runs because someone placed it, and the task-graph Decision step and the Run Decision action call AgentDecisionService directly. That is why the check sits at each automatic use, not in the service.
The merged agent-type prompt params.
ProtecteddetermineBase class method that determines the next step by contacting the agent type class for the specified agent type and delegating that decision. Sub-classes can override this method to implement custom next step logic if needed.
ProtecteddoesProtectedDetermines if an action change scope applies to the current agent.
The scope of the action change
The ID of the current agent
Whether this is the root agent
OptionalagentIds: string[]Array of specific agent IDs (for 'specific' scope)
True if the change applies to this agent
ProtectedemitProtectedEmits message lifecycle event if callback is registered.
ProtectedenableProtectedEnables a skill's bundled Actions and sub-agents by pushing specific-scoped add entries
(targeted at exactly the activating agent's ID) onto params.actionChanges /
params.subAgentChanges. specific/[agent.ID] is the correct scope for "apply to THIS
agent, at whatever depth it runs, and never leak to its sub-agents":
root-scoped change would NOT do, since root means
"the depth-0 agent," not "the current agent").specific as-is, and each downstream agent checks includes(itsOwnID) → false, so the
grant never cascades to sub-agents the activating agent later delegates to.
Because params is the same object reference used for the rest of this run, every subsequent
turn's gatherPromptTemplateData() call picks up the change automatically — no extra plumbing.Which actions: the skill's ExposeToModel rows only (AIEngine.GetSkillExposedActionIDs).
A bundled action with the flag off is left OUT of the run: the effective action set is both the
prompt's tool surface and the execution allow-list, so the model neither sees it nor can call it
by name, and no agent path (PreProcessActionStep, a loop's action lookup) can reach it either.
It stays bundled for SKILL.md export and tooling; application code invokes it through the
Actions API. Skill attribution therefore never applies to it — the agent never runs it.
Override to change propagation scope (e.g. a subclass that wants skill-granted capabilities
to cascade to sub-agents could push scope: 'all-subagents' instead).
Leave a conversation-scoped skill: its MJ: Conversation Skills row becomes Ended, so it is no
longer re-requested on later runs. The app's "exit the mode" gesture (a menu option, a chip
removed in the composer) calls this. Idempotent; a missing row is not an error. FAIL-SOFT.
OptionalcontextUser: UserInfoProtectedensureProtectedEnsures that a shared AgentTreeBudget instance exists for the current execution tree
and is attached to both this._treeBudget and params.treeBudget.
params.treeBudget is already provided (for example, propagated by a parent agent
in ExecuteSubAgent or supplied directly by a caller), that exact object reference
is reused so all root, sequential child, and parallel child runs mutate the same shared
counters (totalSteps, totalTokens, totalCost, startedAtMs, breachedReason).params.treeBudget is absent (root run), a new AgentTreeBudget is created
and seeded with any top-level convenience ceilings (maxTreeSteps, maxTreeTokens,
maxTreeCost, maxTreeDurationMs) declared on params.Optionalparams: ExecuteAgentParamsOptional execution parameters for the current run.
The shared AgentTreeBudget instance for the execution tree.
ProtectedestimateProtectedEstimates total token count across all conversation messages.
Message array to estimate
Total estimated tokens
ProtectedestimateOptionalmodelName: stringProtectedexecuteProtectedExecutes the agent's internal logic.
This method contains the core execution logic that drives agent behavior. By default, it implements a sequential execution loop, but subclasses can override this to implement different execution patterns such as:
The type of the return value from agent execution
The execution parameters with wrapped callbacks (includes wrapped onProgress and onStreaming)
The loaded agent configuration
The execution result with typed final payload and step count
ProtectedexecuteProtectedExecutes a batch of artifact tool calls, recording each as its own
Tool AIAgentRunStep (a sibling of the Prompt step that requested them)
with full inputs/outputs captured in InputData/OutputData. Returns the
stored results so the caller can render them into a single recall-friendly
message for the next prompt turn.
Step naming convention: Artifact Tool: {toolName} for log/UI clarity.
ProtectedexecuteProtectedExecutes conversation-history retrieval tool calls, wrapping each invocation in its own AIAgentRunStep (StepType='Tool', "Conversation Tool: {tool}") — the same per-call observability shape as artifact tools. Reads are served from the ConversationEngine cache; per-call failures are contained in the result.
At most MAX_CONVERSATION_TOOL_CALLS_PER_TURN calls execute per response; the excess come back as skipped failure-shaped results (no run steps recorded) telling the model to re-request them next turn.
ProtectedexecuteAnswers the decision requests from one agent turn, each logged as an AIAgentRunStep
(StepType 'Decision'), with bounded concurrency. A failed request never fails the run: it
becomes that request's success: false, which the agent reads on its next turn.
Two limits bound the turn. At most decisionsMaxRequests requests run (default
MAX_DECISION_REQUESTS_PER_TURN); each request over that cap is not run and records no
step, and its result says why. And the requests make at most decisionsMaxCallsPerTurn
decision calls in total, counting every forEachItemIn item (default
MAX_DECISION_CALLS_PER_TURN). That budget is handed out in request order before any
call is made, so which calls run never depends on timing. A forEachItemIn request it cuts
short asks its first items and reports the rest in skippedCount. A request it leaves no calls
for is not run, records no step, and its result says why.
ProtectedexecuteRuns a 'Decision' next step: an agent type's decision requests, answered with no LLM turn.
Each request goes through the same path a loop turn's decisions take
(executeSingleDecisionRequestAsStep), so it gets the same prompt default, state
resolution and error handling, and is logged as a Decision run step. Where a loop turn's
results go into the conversation, these come back to the agent type on the returned 'Retry'
(decisionResults), because it routes on them. So each answer keeps its whole distribution
(SummarizeDecisionAnswers), which a path condition may read. A failed request is a result
with success: false, never a failed run: what a failure means is the agent type's call.
ProtectedexecuteProtectedExpands a previously compacted message to its original content.
The expand message request
Agent execution parameters
Current turn number
null when the expansion succeeded; otherwise a model-facing reason the expansion is impossible. Callers must surface a non-null reason into the next prompt's context — a silent no-op leaves the loop state identical and the model re-requests the same expansion indefinitely.
ProtectedexecuteProtectedExecutes a batch of in-flight memory writes, recording each as its own
Tool AIAgentRunStep (a sibling of the Prompt step that requested them)
with full inputs/outcomes captured in InputData/OutputData.
Writes run SEQUENTIALLY (not Promise.all like artifact tools) by design: each persisted note is embedded and synced into the in-memory vector service on Save, so write N must be visible to write N+1's near-duplicate check (this is also what makes same-run supersede-own work). The per-run cap bounds the cost of the serialization.
Step naming convention: Memory Write for log/UI clarity.
ProtectedexecuteProtectedExecutes the next step based on the current state.
This method can be overridden by subclasses to customize step execution behavior. It handles the execution of different step types (prompt, actions, sub-agent, chat) based on the previous decision.
Original execution parameters
Agent configuration
Previous step decision
OptionalstepCount: numberProtectedexecuteProtectedRuns a tool pipeline as a single Tool step in the run tree (sibling of the prompt step that
requested it, matching artifact-tool steps). ALL pipeline observability lives in this step's
OutputData — the per-stage breakdown, totals, bytes saved, and the tool chain — so there are
no dedicated pipeline entities and no extra SQL I/O; the run tree alone carries everything a
debug UI needs.
ProtectedexecuteProtectedexecuteProtectedExecutes the configured prompt. Always uses the attemptJSONRepair option to try to fix LLM JSON syntax issues if they arise.
The prompt parameters
The prompt execution result
ProtectedexecuteThis method executes one action using the MemberJunction Actions framework. The full ActionResult objects are returned, allowing the caller to access result codes, output parameters, and other execution details.
Parameters from agent execution for context passing
Action to execute
OptionalcontextUser: UserInfoOptional user context for permissions
Optionaloptions: ExecuteSingleActionOptionsskipCircuitBreaker bypasses the run-scoped
circuit breaker for callers that do their own failure accounting (the pipeline executor)
ActionResult object from the action execution
ProtectedexecuteAnswers one decision request, either once against its state or once per item of its
forEachItemIn array, and records it as a Decision step. The request is unchecked model
output, so any error finishes the step as failed rather than escaping: this never throws
once the step exists.
OptionalcallAllowance: numberThe most decision calls the request may make, from the turn's
decisionsMaxCallsPerTurn budget. It caps a forEachItemIn request's items below
decisionsMaxItems. A single-state request makes one call, and
executeDecisionRequestsAsSteps never starts one the budget left without it.
Unbounded by default.
Optionalsummarize: DecisionAnswerSummarizerhow the typed answers are summarised for whoever reads them next; defaults to the loop turn's model-facing summary
ProtectedexecuteProtectedExecutes a 'Skill' next step: activates one or more skills the LLM requested by name.
Activating a skill (1) appends its full Instructions to the conversation so they take
effect for the remainder of the run, and (2) enables its bundled Actions/sub-agents by
pushing root-scoped add entries onto params.actionChanges/params.subAgentChanges —
the same runtime tool-surface-extension mechanism ExecuteAgentParams already exposes to
external callers. This is NOT a nested agent run; it never terminates the loop itself.
Already-activated skills (tracked in _activatedSkillIDs) are skipped — re-requesting an
active skill is a harmless no-op rather than re-appending duplicate instructions.
Decomposed into resolveSkillActivations, buildSkillActivationMessage, enableSkillCapabilities, and recordSkillActivationStep — override any of those for fine-grained control (e.g. custom instruction formatting, additional side effects on activation) without re-implementing the whole step.
OptionalstepCount: numberProtectedExecuteExecutes a sub-agent synchronously.
This method creates a new instance of AgentRunner to execute a sub-agent. The sub-agent receives the provided message/context and runs to completion. If terminateAfter is true, the parent agent will not continue after the sub-agent completes.
Sub-agent execution details
Optionalpayload: SROptionalcontextMessage: ChatMessageOptionalstepCount: numberResult from the sub-agent execution
ProtectedexecuteProtectedSubmits a durable task graph and ends the turn (submit-and-detach).
Why detaching is the whole point. The alternative — holding the run open until the graph finishes — is exactly what the old client-driven path did, and it is what made a page reload lose the work, a server restart orphan it, and every non-Explorer channel unable to reach it at all. Once submission returns, the graph lives in Task rows and a server-side dispatcher owns it; the run has no reason to stay alive, so it doesn't.
The run step is written whether or not submission succeeds. A rejected graph is the case where forensics matter most — the operator needs to see what the model emitted and why it was refused, and a step written only on the happy path would erase precisely that.
A missing submitter is reported, never swallowed. In a host with no durable-execution package loaded there is nobody to run the graph. Returning a failure the model can read is the honest outcome; silently continuing would leave the agent believing it had scheduled work that does not exist.
ProtectedextractProtectedextractProtectedExtracts the workflow root task ID (parentTaskID) from a sub-agent run that parked on a
durable task graph (Status === 'Paused'), falling back to the sub-agent run ID so the
parent run's _awaitingWorkflowTaskID is guaranteed non-null when parking.
ProtectedfilterProtectedFilters and transforms action changes for propagation to a sub-agent.
This method applies the following propagation rules:
The action changes to filter
Filtered action changes for sub-agent, or undefined if empty
ProtectedfilterProtectedTHE ONE PLACE an application decides whether a skill is available to this user on this agent,
beyond MJ's own gates. MJ resolves availability at four sites — the prompt catalog the model
sees (every prompt turn), the validation of a model-initiated Skill step, the execution of
that step, and the pre-activation of a user's explicit /skill request — and every one of
them calls this after MJ's gates
(AcceptsSkills, Status, agent grant, user Run permission, the ActivationMode double gate) and
before anything activates. The default is the identity: MJ's gates are the whole policy.
Catalog narrowing (plan Task 3.7) is not part of this policy: it runs after it, at the catalog
site, and only hides skills from the prompt. An override never needs to call super for it.
Override it to layer a policy MJ has no table for — a tenant licensing model, a per-organization
entitlement, a feature flag — and it applies everywhere at once. Without this seam a subclass
could gate the requested path (by overriding preActivateRequestedSkills) but not the catalog,
so a self-activating agent would be OFFERED a skill the policy would then refuse.
Rules for an override: return a subset of skills (never add — MJ's gates ran first); be fast
(the catalog is built every prompt turn, so cache your policy lookups); and FAIL CLOSED — on an
error return [], never skills. BaseAgent enforces the last rule itself: if an override
throws, the site treats the result as [] (nothing offered / nothing activates, the user's
explicit request gets the usual refusal note) and logs the error — it never fails the run.
the skills MJ's gates admitted, for this purpose
'catalog' — what the model is offered (auto-activatable set); 'auto-activation' — a model-initiated Skill step being validated/executed; 'requested' — a user's explicit request (requestedSkillIDs)
the agent the skills would activate on
OptionalcontextUser: UserInfothe acting user
ProtectedfilterProtectedFilters/transforms sub-agent changes for propagation to a sub-agent — the sub-agent counterpart of filterActionChangesForSubAgent (same propagation rules).
ProtectedfinalizeProtectedFinalizes the agent run with success.
Optionalpayload: POptionalcontextUser: UserInfoProtectedfinalizeSubclass extension point for per-run cleanup of resources this instance owns outside of
MJ's own tracked state (e.g. an external sandbox or session). No-op by default. Called
unconditionally from Execute()'s top-level finally block, once per Execute() call,
so it runs on every exit path (success, failure, or cancellation) exactly like
releasePerRunDataCache.
ProtectedfinalizeProtectedFinalizes a step entity with completion status. Pairs with createStepEntity
— drivers that create custom steps should also finalize them through this
method so Status/CompletedAt/Success/ErrorMessage/OutputData are
populated consistently and the UPDATE is sequenced behind the INSERT via
queueStepSave.
Set PayloadAtEnd before calling it: the queued UPDATE re-applies the end the step has now.
An end set later must be saved with queueStepSave.
OptionalerrorMessage: stringOptionaloutputData: anyProtectedfitProtectedTrims a step name to what AIAgentRunStep.StepName can hold. Step names are built from free
text — a termination message quoting a provider's error, a tool name with its arguments, the
hierarchy breadcrumb in front of either — and one longer than the column made the whole row
unsaveable, so the step vanished from the run ("2 step record save(s) failed" — a
327-character failure reason). The width comes from the entity's field metadata
when the instance carries it, else the column's declared 255 characters.
ProtectedformatThe guidance line appended to the history for one failed action.
ProtectedformatProtectedFormats a message with agent hierarchy for streaming/progress updates.
Exposed as protected so driver sub-classes emit progress events whose
breadcrumbs line up with the framework's own — keeps the Explorer tree
view consistent across custom and built-in dispatch.
The base message to format
ProtectedformatFormats research violations into clear, actionable retry instructions
ProtectedgetGets the count of how many times a specific action has been executed in this agent run.
The agent run ID (not used anymore, kept for signature compatibility)
The action ID to count
The number of times the action has been executed
ProtectedgetUtility method to get agent prompt parameters for a given agent. This gets the highest priority prompt for the agent, and then gets the parameters for that prompt.
ProtectedgetGets the available configuration presets for an agent. Returns semantic presets like "Fast", "Balanced", "High Quality" that users can choose from. These are actual presets stored in the database with specific AIConfiguration references.
The ID of the agent to get presets for
Array of configuration presets sorted by Priority, or empty array if none configured
const agent = new ResearchAgent();
const presets = agent.GetConfigurationPresets('agent-uuid-here');
// Returns presets defined in database: [
// { Name: 'Fast', DisplayName: 'Quick Draft', AIConfigurationID: 'fast-config-uuid', IsDefault: true },
// { Name: 'HighQuality', DisplayName: 'Maximum Detail', AIConfigurationID: 'frontier-uuid', IsDefault: false }
// ]
// Note: If no presets configured, returns empty array - agent will use default behavior
Gets the default configuration preset for an agent.
The ID of the agent to get the default preset for
The default preset, or undefined if none configured
ProtectedgetExtracts design text from payload (FunctionalRequirements, TechnicalDesign, or modificationPlan)
ProtectedgetProtectedGets the effective actions for validation, using runtime changes if available. Falls back to database-configured actions if _effectiveActions is empty.
The ID of the agent to get actions for
Array of effective actions available to the agent
ProtectedgetProtectedGets the effective sub-agents for validation, using runtime subAgentChanges if available. Falls back to the database-configured relationship set if _effectiveSubAgents is empty. Mirrors getEffectiveActionsForValidation.
The ID of the agent to get sub-agents for
Array of effective sub-agents available to the agent
ProtectedgetProtectedGets the execution count for an item (action or sub-agent).
The item ID to get count for
The execution count (0 if never executed)
ProtectedgetProtectedGets the context limit for the current model. Uses model selection info from the prompt result to determine the actual model's MaxInputTokens.
OptionalmodelSelectionInfo: AIModelSelectionInfoModel selection information from the prompt execution
The maximum input tokens for the model
ProtectedgetReturns the list of sub-agent requests on a next-step decision, normalizing
the singular (subAgent) and plural (subAgents) forms. Plural takes
precedence when both are present (parallel fan-out); otherwise the singular
form is wrapped into a single-element array. Empty if neither is set.
Use this anywhere code needs to enumerate the sub-agents an LLM requested
— keeps validation and execution paths consistent and avoids the regression
where one path read .subAgent?.name and missed parallel requests.
ProtectedgetProtectedResolves which activated skill(s), if any, granted the given action to this agent — the
attribution recorded on the Actions step's Skills column. Returns undefined (no
linkage) when the action is one of the agent's NATIVE grants (an Active MJ: AI Agent Actions row), even if an activated skill also bundles it: native authority takes
precedence, and Skills = NULL is the contract for "the agent had this tool anyway".
ProtectedgetProtectedResolves which activated skill(s), if any, granted the given sub-agent to this agent — the
attribution recorded on the Sub-Agent step's Skills column. Returns undefined when the
sub-agent is a NATIVE relationship (a ParentID child of this agent, or an Active
MJ: AI Agent Relationships referenced-sub-agent row), even if an activated skill also
bundles it — same native-precedence contract as getSkillAttributionForAction.
ProtectedgetResolves the file storage account ID for this agent's file artifacts.
Delegates to AIEngineBase.ResolveAgentStorageAccountID, the shared golden path:
runtime override (params.override?.storageAccountId) → agent → category tree → agent type →
system fallback (the single active account, or the most recently updated one with a
once-per-agent warning when several are active).
This method is protected so subclasses can override the resolution logic
for custom storage routing (e.g., routing by file type or tenant).
The current agent execution parameters
The resolved FileStorageAccount ID, or null if none configured
ProtectedgetGets the count of how many times a specific sub-agent has been executed in this agent run.
The agent run ID (not used anymore, kept for signature compatibility)
The sub-agent ID to count
The number of times the sub-agent has been executed
ProtectedgetProtectedReturns the system default prompt ID for message compaction. Looks up the "Compact Agent Message" prompt by name.
ProtectedgetReads a value out of a nested object by dotted path, with name[0] array indexing.
Delegates to the one implementation. This used to be a private copy of the same walk the task-graph dispatcher performs, and the two had to agree exactly: a Loop agent and the compiled graph of the same workflow read the SAME authored mapping strings, so any divergence would make a workflow behave differently depending on which engine ran it — silently, and only for the paths where they differed.
ProtectedhandleHandles final payload validation failures based on the configured mode.
Execution parameters
The original success next step
The current payload
The validation mode (Retry, Fail, Warn)
The validation error messages
Modified next step based on validation mode
ProtectedhandleProtectedHandles validation of the starting payload if configured.
This method validates the input payload against the agent's StartingPayloadValidation schema before execution begins. It respects the agent's PayloadScope if configured.
The execution parameters
Error result if validation fails and mode is 'Fail', null otherwise
ProtectedhasTrue when any top-level argument name looks like a credential the model supplied itself
(password, secret, credential, API key, or an access / auth / bearer / refresh / ID token).
maxTokens-style names are deliberately not matched.
OptionalactionParams: Record<string, unknown>ProtectedhasProtectedChecks if any agent run guardrails have been exceeded. Override this method to implement custom guardrail logic.
Execution parameters
Current agent run
Object indicating if guardrails exceeded and details
ProtectedincrementProtectedIncrements the execution count for an item (action or sub-agent).
Exposed as protected so driver sub-classes performing custom dispatch
bump the same per-item counter the framework checks against execution
guardrails — without this, custom dispatch silently bypasses limits.
The item ID to increment (action ID or sub-agent ID)
ProtectedinitializeThis overridable method is responsible for setting up any necessary one-time initalization of the agent type. The base class sets up the AgentTypeInstance and also lets that agent type initialize its state.
ProtectedinitializeProtectedInitializes the AI and Action engines. Subclasses can override this to add any additional engine/metadata loading initialization they want to do and this method will be called at the right time in the agent execution process.
OptionalcontextUser: UserInfoOptional user context
ProtectedinitializeSub-classes can override this method to perform any specialized initialization
ProtectedinjectProtectedPushes a single user-role message containing rendered artifact-tool
results into the conversation. This mirrors the action-result
"inject once, then expire" pattern — the LLM sees the results on its
next turn, and older messages are pruned/compacted by
pruneAndCompactExpiredMessages instead of being re-rendered into
every system-prompt turn.
ProtectedInjectInject notes and examples into agent context memory. Called automatically before agent execution if injection is enabled on the agent. Injects memory context directly into conversation messages array.
The user input text for semantic search
The agent configuration entity
OptionaluserId: stringOptional user ID for scoping
OptionalcompanyId: stringOptional company ID for scoping
OptionalcontextUser: UserInfoUser context
OptionalconversationMessages: ChatMessage[]The conversation messages array to inject into
OptionalprimaryScopeEntityId: stringOptional primary scope entity ID for multi-tenant filtering
OptionalprimaryScopeRecordId: stringOptional primary scope record ID for multi-tenant filtering
OptionalsecondaryScopes: Record<string, SecondaryScopeValue>Optional secondary scope dimensions for multi-tenant filtering
OptionalsecondaryScopeConfig: SecondaryScopeConfigOptional agent-level scope config for per-dimension inheritance
OptionalsharedOnly: booleanInject only shared notes and examples (no UserID); set when the run's audience adds a reader
Object containing injected notes and examples
ProtectedinjectProtectedPushes a single user-role message containing rendered conversation-tool results into the conversation — the same inject-once-then-expire lifecycle as artifact tool results.
ProtectedInjectDecision discovery (plan Task 3.1): suggests the agent to delegate to before the first prompt.
Runs in parallel with the rest of Phase 2 of Execute(), and only when the merged agent-type
prompt params set decisionsEnabled: true (the master switch, DecisionsEnabled) and
decisionDiscovery: true, the run answers its conversation's opening request
(IsOpeningTurn), and that request
The agent being executed.
The user whose run permission decides the options.
The mutated message array that flows to the LLM (the suggestion is unshifted here).
The run's ExecuteAgentParams.data. Its __agentTypePromptParams overrides the agent's prompt
params, and its ALL_AVAILABLE_AGENTS, when an array, is the host's allow-list: no other agent is suggested.
no agent. A follow-up turn is never asked
about: it may be continuing work with an agent already engaged, and a suggestion must not pull
the agent away from it. One decision asks which of the agents the user may run (and the host
allows) should handle the request, and whether a specialist should. When both answers are
confident, a <suggested_agent> system message is unshifted onto conversationMessages, the
way pre-execution RAG adds <retrieved_context>. Each discovery is recorded as one
Agent discovery Decision step.
Fails safe: an error, a timeout, a cancelled run, an unusable answer or an unsure one adds nothing, so the agent behaves as it would without discovery. So do fewer than DECISION_DISCOVERY_MIN_OPTIONS options. Never delays the first prompt by more than DECISION_DISCOVERY_TIMEOUT_MS, and never throws.
The any-applies question does not name the options, so the decision cannot say that none of them fits: see DECISION_DISCOVERY_APPLIES_INSTRUCTIONS for why it stays that way for now.
ProtectedinjectInjects decision results into the conversation as one tool-result message for the next turn.
ProtectedinjectProtectedPushes a single user-role message containing memory-write outcomes into
the conversation, mirroring injectArtifactToolResultsMessage's
inject-once-then-expire pattern. Closing the loop here is what stops the
LLM from re-emitting the same memory on subsequent turns.
ProtectedinjectProtectedPushes the pipeline's final output (or its failure message) into the conversation for the LLM's next turn, mirroring the artifact-tool "inject once, then expire" pattern. Only the final output is surfaced — intermediate step outputs never enter the context window.
ProtectedInjectInject pre-execution RAG context for this agent using scoped search.
Runs in parallel with InjectContextMemory during Phase 2 of Execute(). Loads the
agent's AIAgentSearchScope rows (Phase IN 'PreExecution'|'Both'), renders any per-scope
query templates, calls SearchEngine.Search() per scope, cross-scope RRF fuses the
results when multiple scopes contributed, and unshifts a <retrieved_context> system
message onto conversationMessages.
When the agent has no active pre-execution scopes (or all scopes returned zero results),
this method is a no-op — no system message is injected and _injectedRAG stays null.
See plans/search-scopes-rag-plus.md §4 (Agent Integration — Pre-Execution RAG).
The most recent user message text.
The agent being executed.
Calling user (threaded to SearchEngine + Metadata).
The mutated message array that flows to the LLM (system msg is unshifted here).
The unmodified messages array (for template recentMessages).
OptionalprimaryScopeEntityId: stringMulti-tenant primary scope entity ID.
OptionalprimaryScopeRecordId: stringMulti-tenant primary scope record ID.
OptionalsecondaryScopes: Record<string, SecondaryScopeValue>Multi-tenant secondary scope dimensions.
Optionalpayload: unknownThe agent's current payload (for template rendering).
OptionalactiveSkillIDs: string[]The skills active for this run; a lone active skill is the search's skill principal.
OptionalaudienceReaders: UserInfo[]The run's audience readers beyond the caller (ExecuteAgentParams.Audience,
hydrated): each must pass the scope gate, and only results every one of them may read are injected.
The structured RAG result, or null if no scopes produced results.
ProtectedinjectProtectedCarries the PREVIOUS turn's tool results forward into this run's context.
Inline tool results (artifact + conversation tools) are injected into the run's in-memory messages only — the next turn rebuilds messages from the conversation window, so a result paged in on turn N is gone on turn N+1 and the agent must re-call the tool. The results already persist in each Tool step's OutputData; this re-injects the immediately previous completed run's successful results as one transient message. One-turn memory by construction: each run carries only its direct predecessor's results, so context never compounds.
Gated on conversationId + root depth — programmatic runs and sub-agents skip it.
Skipped under a history floor (ConversationHistoryFrom): the previous run's tool
results can quote messages from before the floor. Skipped under an audience
(ExecuteAgentParams.Audience) too: the previous run's results were fetched for
whoever ran it, not for this room.
ProtectedInjectInject this agent's scoped prompt parts into the conversation, role-faithfully.
Parallels InjectContextMemory: resolves MJ: Scoped Prompt Parts for the agent's
primary prompt under the run's polymorphic scope (the SAME PrimaryScope/SecondaryScopes the
runtime threads for memory), and unshifts the assembled role-tagged messages onto
conversationMessages. In-memory + synchronous (parts are cached on AIEngine). No-op when
the agent has no active prompt or no parts resolve for the scope. System placeholders in the
messages it adds are replaced afterwards (replaceScopedPartPlaceholders), so an override
gets that too.
OptionalprimaryScopeEntityId: stringOptionalprimaryScopeRecordId: stringOptionalsecondaryScopes: Record<string, SecondaryScopeValue>ProtectedinjectProtectedCarries a STOPPED previous turn's completed results forward into this run's context.
injectPriorTurnToolResults covers the normal case: the previous run settled and its inline tool results ride along one turn. A run the user stopped never settles, so that path skips it, and the next turn would redo every action the stopped run had already finished (observed: a stopped Sage run had five weather lookups and fifteen searches done; "keep going" redid all of them). This path looks at the newest root run for this agent in the conversation and, when it is one the user stopped, carries it forward per ExecuteAgentParams.stopContinuationMode: by default its own message stack is resumed so the agent picks up where it stopped; in summary mode its completed Actions and Tool results go in as one transient message that says to continue, not restart. A settled newest run means the normal path already has the context, so nothing is added here. During execution the two halves run separately: loadStoppedRunContinuation in the parallel phase, then applyStoppedRunContinuation once it settles.
Same gates as the sibling: root depth, a conversation, no history floor. Fail-soft.
ProtectedisDetermines whether artifact creation/lifecycle support (artifactDirective) is enabled for
the given agent so the Loop Agent system prompt includes artifactDirective and its lifecycle
instructions only when relevant.
Returns true when:
agent.ArtifactCreationMode is not 'Never', OR_artifactToolManager, ORagent.ParentID) whose parent agent creates artifacts
(parentAgent.ArtifactCreationMode === 'Always') and relies on the sub-agent's payload/directive.ProtectedisProtectedDetermines if an error is a configuration error that cannot be resolved by retrying. Configuration errors indicate issues with agent setup that need manual intervention.
This method provides detailed diagnostic information to help identify what configuration is missing or incorrect.
The error message string
Optionalconfig: AgentConfigurationOptional agent configuration to check for missing pieces
Object with determination and detailed diagnostic message
ProtectedisDetects whether an action error message represents a fatal configuration or credential problem: the tool cannot work in this environment no matter what arguments it is given, so retrying is pointless and the action is locked out for the rest of the run.
Deliberately NOT fatal: HTTP 401/403, "unauthorized" and "forbidden". Those are usually per-resource (one site blocking a fetch, one record the user cannot read) or transient (a search provider using 403 as a rate limit), so they fall through to the parameter-aware failure history where the identical-arguments rule and the consecutive-attempt budget bound them without disabling the tool for every other resource.
Also NOT fatal, for the same reason: a failure the model can fix by changing its arguments. A message that names a parameter is treated as an argument problem whatever else it says, and a call that itself carried credential-shaped arguments (password, API key, token) is never fatal even on "authentication failed", because the credential came from the model, not the environment. Demoting a message from fatal costs at most the attempt budget.
The action's failure message.
OptionalactionParams: Record<string, unknown>The arguments the call was made with, when known.
ProtectedisProtectedDetermines if a prompt execution error is fatal and should stop agent execution. Fatal errors are those that won't be resolved by retrying, such as context length exceeded (when no larger model is available), authentication failures, or invalid request format.
The result from prompt execution
true if the error is fatal and agent should terminate, false otherwise
ProtectedisWhether the turn currently being prepared is the last one this run will be allowed.
Reads TotalPromptIterations, which the loop increments immediately BEFORE composing the
prompt — so by the time this runs the counter already includes the turn about to go out, and
iterations >= limit means "this turn is the last", not "the last one already happened".
That off-by-one is the whole subtlety and is why this lives beside the limit check it mirrors.
Returns false when no run is in flight: the eval harness composes parameters through
BaseAgent without executing a loop, and a composed-but-never-run turn has no iteration
budget to be at the end of.
Only the ITERATION limits are predicted. Cost, token and time limits also stop a run, but
none can be known before the turn that crosses them — guessing would force 'none' on turns
that had budget left, which silently disables native tool calling rather than bounding it.
The run parameters, for absoluteMaxIterations
true when the framework will stop the run after this turn
ProtectedisType guard for whether the resolved agent-type instance is session-driven.
Detects the Realtime agent type without importing it (and without instanceof, which is
brittle under bundler class-duplication) by duck-typing the IsSessionDriven getter that
RealtimeAgentType adds. BaseAgentType (and Loop/Flow) do not expose this member, so the
guard returns false for them and the iterative loop runs unchanged.
The resolved agent-type instance for this run.
true only when the type explicitly marks itself session-driven.
ProtectedIsReturns true if the message is a tool result (action or client tool). Used by recovery strategies to identify compactable result messages.
ProtectedIsProtectedReturns true if the message is a turn-generated result (action, tool, client tool, sub-agent, or loop). These are the volatile, expirable messages that accumulate over turns. Everything else — system/RAG context, injected memory, the original user request — anchors the cache-stable prompt prefix and is protected from routine pruning.
ProtectedloadProtectedLoads all required configuration for agent execution.
The agent to load configuration for
Configuration object with loaded entities
ProtectedloadThe child prompt's raw template text (placeholders intact), from the cached template engine. Null when the prompt has no template or the lookup fails — which fails CLOSED: with no text to inspect, ResolveSpecializationPlacement keeps the specialization in the system prompt.
ProtectedloadProtectedThe skills persisted as Active for this run's conversation (MJ: Conversation Skills), or []
when the run has no conversation or the read fails. FAIL-SOFT: a persisted skill is a convenience
layered on the request; losing it means the user re-invokes the skill, not that the turn fails.
Override to source persisted activations from somewhere else.
ProtectedloadRaw template text (placeholders intact) for an AI prompt from the cached template engine. Null when the prompt has no template or the lookup fails.
ProtectedloadProtectedThe I/O half of injectStoppedRunResults: finds a user-stopped predecessor and loads its steps (every step for the full-stack resume, only completed results for the summary). Runs inside the pre-execution parallel phase; nothing here touches the message list.
ProtectedlogHelper method for enhanced error logging with metadata
Optionaloptions: {ProtectedlogHelper method for status logging with verbose control
The message to log
OptionalverboseOnly: booleanWhether this is a verbose-only message
Optionalparams: ExecuteAgentParamsOptional agent execution parameters for custom verbose check
ProtectedmakeMakes a SLICED conversation window safe to send on its own.
A tool turn is only valid immediately after the assistant turn that declared its call ids, so
cutting the parent's history at an arbitrary index can strand either half of a pair. Keeping
the result half throws in validateToolConversation before the request is even built; keeping
the call half is accepted there but rejected by every provider. Both halves are repaired here
so the caller's window is internally consistent whatever index it happened to cut on:
an unpairable tool turn is dropped, and an assistant turn whose calls nothing in the window
answers keeps its prose but loses the calls.
Only the slicing modes need this — 'All' is self-consistent by construction.
ProtectedmapProtectedMaps an array through an async worker with bounded concurrency. Preserves input order in the output. Used to cap parallel sub-agent and preload dispatches so a misbehaving LLM (or a runaway data source) can't exhaust the model API or DB pool.
Exposed as protected so driver sub-classes performing custom parallel
work get the same bounded-fan-out + ordered-results contract for free.
ProtectedMemoryThe observability context memory injection runs under for an agent run: the run's ID, the next
step number, and a callback that adds each rerank step to the run's steps. A rerank step carries
the rerank's prompt run, whose cost and token rollups include a prompt-backed reranker's own
prompt runs, so the run's totals and its MaxCostPerRun / MaxTokensPerRun guardrails count
them. Undefined when there is no run.
The current agent run, or null before one exists
ProtectedmentionsChecks if text mentions database operations that require entity knowledge
Protectedmergeexplicit first, then any persisted id not already present (case-insensitive UUIDs).
ProtectednormalizeNormalizes one top-level (key, value) pair of an action's arguments. The default keeps the
key and normalizes the value through normalizeActionParamValue. Return null to drop
the pair from the call's identity — the seam for ignoring arguments that legitimately differ
between otherwise identical retries.
ProtectednormalizeThe identity of one action call's arguments, for the circuit breaker's identical-arguments rule: two calls with the same normalized string are "the same call", whatever order the model wrote the keys in.
Algorithm, top to bottom:
'' (a call with no arguments).(key, value) pair passes through
normalizeActionParamEntry, which may rename it, rewrite its value, or drop it.JSON.stringify. Should that throw (a circular reference,
a BigInt), the fallback is a sorted list of the top-level keys — still deterministic, still
distinguishes differently-shaped calls, and never throws.Three protected layers so a subclass can change one part without re-implementing the rest: override normalizeActionParamEntry to ignore a key (a trace id, a timestamp the model regenerates on every call), or normalizeActionParamValue to canonicalize values (case-fold a search query, trim whitespace) so near-identical retries count as identical.
ProtectednormalizeNormalizes one value, recursively: a plain object is rebuilt with its keys sorted, an array keeps its order with each element normalized, and any other value is returned unchanged. The seam for canonicalizing values before they are compared.
"Plain" here is by prototype (Object.prototype or none), not the structural
IsPlainObject from @memberjunction/global: a Date, Map or entity instance must pass through
as an opaque leaf and serialize as itself, not be rebuilt as an empty bag of sorted keys.
ProtectednotifyProtectedHandles user-requested skill IDs that failed the activation guard (agent-accepted ∩ user-permitted). A silent drop leaves both the user AND the agent blind to the refusal — the agent then improvises around the missing capability instead of explaining it. This emits a server-side warning log and injects a system note into the conversation so the agent tells the user why the skill isn't available rather than working around it.
ProtectedpersistProtectedAfter a skill activates: if its ActivationScope is 'Conversation' and this run belongs to a
conversation, record it as Active for that conversation (creating the MJ: Conversation Skills
row, or re-activating an Ended one). Idempotent. FAIL-SOFT for the same reason as
loadConversationSkillIDs. Override to persist elsewhere.
ProtectedpreProtectedPre-activates skills the caller explicitly requested via ExecuteAgentParams.requestedSkillIDs
(typically an end user's /skill-name composer mentions), at run start — so their Instructions
and bundled Actions/sub-agents take effect from the first turn rather than waiting for the model
to discover and activate them through the catalog.
Root-agent only (skills never cascade to sub-agents), and each requested skill activates
only if it survives the guard: it must be in the set AIEngine.GetSkillsForAgent
allows for this agent (the AcceptsSkills gate) AND the acting user must have Run permission on it
— both enforced by passing params.contextUser to GetSkillsForAgent. Requested IDs that fail
either check are silently dropped, so a client can never force-activate a skill the user or agent
isn't entitled to. Reuses the same recordSkillActivationStep / enableSkillCapabilities
/ buildSkillActivationMessage machinery as the model-initiated Skill step, so activation
is recorded and takes effect identically. Plan Mode is unaffected — pre-activation widens the tool
surface, but the plan-approval gate still blocks executing those tools until the plan is approved.
ProtectedprepareProtectedprepareProtectedPrepares conversation messages for sub-agent execution based on database-configured message mode.
Message passing is controlled by MessageMode and MaxMessages fields stored in either:
Message Modes:
'None': Fresh start - only context message and task message (default)'All': Pass complete parent conversation history'Latest': Pass most recent N messages (where N = MaxMessages)'Bookend': Pass first 2 messages + indicator + most recent (N-2) messagesPriority: AIAgentRelationship.MessageMode takes precedence over AIAgent.MessageMode to allow different parent agents to pass messages differently to the same sub-agent.
Runtime state never crosses the boundary. The parent's trailing runtime-state messages
(metadata.volatileState — its payload, scratchpad and, when relocated, its specialization;
retained in the history under append-only mode) are dropped BEFORE any mode slices the
history, so a sub-agent never sees the parent's state, never has it counted against
MaxMessages, and never receives it unescaped when it builds no fragment of its own. The
sub-agent builds its own fragment at its prompt step.
Subclasses can override this method to implement custom message preparation logic specific to their domain (e.g., Skip agents adding special context).
Execution parameters with conversation history
Sub-agent request details
The sub-agent entity
OptionalcontextMessage: ChatMessageOptional context from SubAgentContextPaths
Prepared message array for sub-agent execution
ProtectedprocessProtectedTurn-loop entry point for in-flight memory writes, gated on the agent's AllowMemoryWrite flag and on the run's audience (memoryWriteSkip). When refused but the LLM emitted writes anyway (prompt drift / injection attempt), records ONE summary skip step and tells the agent the memories were NOT saved (skipMemoryWrites). Otherwise executes the writes as run steps and injects the results message.
ProtectedprocessProtectedProcesses the next step based on agent type determination.
Original execution parameters
The agent type
The prompt execution result
The execution result
Promotes media outputs to the agent's final outputs. Call this method to add generated images, audio, or video to the agent's outputs. These will be saved to AIAgentRunMedia and flow to ConversationDetailAttachment.
Array of media outputs to promote
// Promote images from action result
this.promoteMediaOutputs([{
modality: 'Image',
mimeType: 'image/png',
data: base64Data,
label: 'Generated Product Image'
}]);
// Promote from prompt run media reference
this.promoteMediaOutputs([{
promptRunMediaId: 'prompt-run-media-uuid',
modality: 'Image',
mimeType: 'image/png',
label: 'AI Generated Visualization'
}]);
ProtectedpruneProtectedPrunes and compacts expired messages in the conversation based on configured expiration rules. Processes messages in three phases: identification, compaction, and removal.
Agent execution parameters containing conversation messages
Current turn number in the agent execution
ProtectedqueueProtectedQueues a fire-and-forget UPDATE of a step entity whose fields the caller has ALREADY mutated.
Delegates to AgentRunStepSaveQueue.QueueUpdate — the agent flow never awaits this; the UPDATE
chains after the step's INSERT and force-persists (IgnoreDirtyState). Kept protected so driver
subclasses that finalize their own steps get the same non-blocking behavior.
OptionalapplyMutation: (stepEntity: MJAIAgentRunStepEntityExtended) => voidProtectedreadThe scratchpad strings already placed in the template data by the prep step (so the fragment shows exactly what the template would have). Null when the scratchpad is disabled or absent.
ProtectedreconcileAnswers any tool call the turn's own result path left dangling, immediately before the history goes back to the model.
appendNativeAssistantTurn replays the model's call turn for EVERY step that carries one,
but only Actions, Sub-Agents and the payload-only Retry append results for it. An
unknown-tool Retry, a protocol-violation Retry, an ask_user Chat, or a parallel dispatch
that could not pair one of its ids therefore leaves calls unanswered — which Anthropic
("Each tool_use block must have a corresponding tool_result block in the next message"),
OpenAI ("an assistant message with tool_calls must be followed by tool messages
responding to each tool_call_id") and Gemini all reject outright. validateToolConversation
in BaseLLM cannot catch it: it validates results→calls, never calls→results.
Reconciling here rather than in each branch means a new step type cannot reintroduce the bug. The synthetic result states only that the call did not run; the reason travels in whatever message the branch itself appended (the retry instructions, the chat question).
ProtectedrecordRecords a non-successful outcome for the run-scoped action circuit breaker. A fatal configuration error locks the action out for the rest of the run; any other failure updates the parameter-aware history behind the identical-arguments rule and the consecutive-attempt budget.
ProtectedrecordProtectedRecords a constant-folded task graph (D9) without submitting it.
Folding rewrites a one-node graph into an ordinary in-run sub-agent call, which means no Task row and no dispatcher hop. Writing the step anyway is what keeps that decision legible: run forensics show why a graph did not reach the dispatcher, a user who edits a two-node graph down to one can read the durability change off the run record instead of inferring it, and Save as Workflow (D17) can attach to the recorded spec — so the single-node case, the shape most likely worth promoting, is promotable like any other.
ProtectedrecordProtectedAccumulates token and cost usage from a completed prompt execution into the shared AgentTreeBudget for the execution tree.
Prefers rollup figures on promptRun (TokensUsedRollup / TokensPromptRollup +
TokensCompletionRollup and TotalCost) so hierarchical child prompts are counted,
falling back to direct promptRun columns (TokensUsed / TokensPrompt + TokensCompletion
and Cost) or the AIPromptRunResult summary fields when promptRun is not populated.
OptionalpromptRun: MJAIPromptRunEntityThe persisted or in-memory MJAIPromptRunEntity from the prompt execution, if any.
OptionalpromptResult: AIPromptRunResult<unknown>Optional AIPromptRunResult carrying direct token/cost metrics.
Optionalparams: ExecuteAgentParamsOptional execution parameters for the current run.
ProtectedrecordProtectedCreates and immediately finalizes the AIAgentRunStep (StepType='Skill') that records this
skill activation for observability/audit. The step's Skills column carries the single
AgentSkillInvocation performed (activation type, provenance of authority, and the
agent-stated reason when self-activated). Activation is not itself a failure mode today — it
always finalizes as successful — but subclasses can override to add richer InputData/OutputData
or to make activation conditionally fail (e.g. a licensing check).
Optionalinvocation: AgentSkillInvocationProtectedrecordStamps the step with which tool-calling path it took and how much the model used it (§8.5).
The mode is derivable through TargetLogID -> AIPromptRun.ToolCallingMode, but only for
steps whose target is a prompt run and only through a join that returns nothing for every
other step type. The call count is not derivable at all — it lives in the provider response,
which is not persisted per step. Both are recorded here so a comparison of the two paths can
group by them directly instead of reconstructing its own independent variable.
Instrumentation must never fail a run, so every field is best-effort.
ProtectedrecoveryProtectedRecovery Strategy 3: Aggressively compact ALL tool-result messages. Used when gentler strategies haven't freed enough space.
Agent execution parameters
Target number of tokens to free
Result with tokens saved and strategy description
ProtectedrecoveryProtectedRecovery Strategy 2: Compact old tool-result messages. Uses smart trimming to reduce size while preserving some content.
Agent execution parameters
Target number of tokens to free
Current turn/step number
OptionalminAge: numberMinimum age in turns for compaction (default: 3)
Result with tokens saved and strategy description
ProtectedrecoveryProtectedRecovery Strategy 0: drop stale runtime-state fragments.
Under append-only trailing-state retention (prefix-cache providers: OpenAI, xAI) every
iteration leaves its <mj-runtime-state> message in the history so the next request is an
exact prefix extension of the last. Those copies are cached tokens on the wire, but they are
context all the same, and they carry nothing the model needs: the CURRENT state always rides
as the fresh fragment appended to the outgoing request. So when the context overflows they
are the first thing to go, oldest first, all but the most recent. Keeping the newest one
matters for two reasons: it is the history's only fragment after this pass, so
restoreTurn1VolatileStateIfNeeded does not splice a turn-1 copy back in, and it
keeps the prefix intact from that point forward. The cost is one cache miss on the next call;
the alternative was a failed run.
A no-op under replace-in-place retention, where the history never holds a fragment.
Agent execution parameters
Target number of tokens to free
Current turn number, for the lifecycle event
Result with tokens saved and strategy description
ProtectedrecoveryProtectedRecovery Strategy 1: Remove oldest tool-result messages. Targets messages older than minAge turns for removal.
Agent execution parameters
Target number of tokens to free
Current turn/step number
OptionalminAge: numberMinimum age in turns for removal (default: 5)
Result with tokens saved and strategy description
ProtectedrecoveryProtectedRecovery Strategy 4: Preserve beginning of last user message (fallback). Keeps the first 200-300 tokens of the user's request (which usually contains the core ask) and adds a clear marker that content was trimmed due to context limits.
Agent execution parameters
Target number of tokens to free
Result with tokens saved and strategy description
ProtectedResolveLoads the hydrated user (with its UserRoles) for each audience ID, from the server's UserCache. When an
ID is missing the cache is refreshed once — a user created since it loaded is then found, as MJServer's
authentication does for a user it does not know — and looked up again. Returns the users found; an ID left
out refuses the run. Override to resolve readers from another directory; never build UserInfo by hand.
An inactive user is accepted as a reader, deliberately: a reader can only narrow what the run shows (each one is another permission check every result must pass), never widen it, so a deactivated participant still listed in the room restricts the run rather than failing it.
The reader IDs the audience adds beyond the caller (normalized, distinct).
The run's parameters (for the request-scoped provider the refresh reads through).
ProtectedresolveProtectedResolves the effective context budget for cross-turn compaction, validated against the most recent prompt's model when available. Logs the clamp warning once when a configured budget exceeded the model's MaxInputTokens.
ProtectedresolveThe date/time strings exactly as the system placeholders would render them into the template. Resolves only the three temporal placeholders (by name, through the same registry the template uses, so a registered override applies here too) rather than every system placeholder — this runs on every loop iteration.
ProtectedresolveResolves multiple {{expression}} placeholders embedded within a literal string.
Unlike the single-variable path (which can return objects/numbers), this always returns a string because the resolved values are interpolated back into surrounding text.
ProtectedresolveProtectedResolves whether Plan Mode is active for this run, and whether its approval gate is already
satisfied. Called once from initializeAgentRun, after _depth is set.
active: this is a root agent (_depth === 0) AND either agent.RequirePlanMode
(mandatory HITL — forces plan mode on every root run regardless of the per-request flag;
SupportsPlanMode is irrelevant when set) OR agent.SupportsPlanMode (capability,
default ON/opt-out) AND params.planMode (per-request, default OFF). Sub-agents never
gate on Plan Mode — only the top-level agent the user/caller invoked does.approved: only meaningful when active. True when params.lastRunId points to a prior
run whose Plan step's MJ: AI Agent Requests row resolved to Approved or Responded
(a Rejected plan — or no matching request at all — leaves the gate unsatisfied, sending
the agent back to present a revised plan).Override to change Plan Mode eligibility rules (e.g. gate on a specific agent category).
ProtectedresolveWhether a model, as served by a vendor, sits behind a byte-prefix prompt cache
(LLM.PrefixPromptCache), read from the model catalog's ModelConfiguration cascade —
AIModelType < AIModel < AIVendor.Configuration.ModelDefaults < AIModelVendor — via
AIEngine.GetEffectiveModelConfiguration. The most specific layer is the INFERENCE-PROVIDER
model-vendor row for vendor, whose vendor row supplies the host-wide default that beats the
model's own bag; when the vendor is unknown, or has no inference row for this model, the model
and type layers still answer. False when no layer declares it, which callers
treat as a block cache (replace-in-place).
Extension point: a subclass with out-of-catalog knowledge (an OpenAI-compatible gateway whose rows carry no flag, say) can override this rather than the mode decision above.
ProtectedresolveResolves the agent's EFFECTIVE realtime configuration — the agent TYPE's
DefaultConfiguration (base layer) deep-merged with the agent's TypeConfiguration
(per-agent layer; the server-bridged path has no runtime-override layer). Tolerant:
malformed layers contribute nothing and an unloaded type cache yields no type defaults.
See realtime/realtime-coagent-config.ts for the merge contract.
The session-driven (Realtime) agent.
The normalized effective configuration (possibly empty, never null).
ProtectedresolveResolves the realtime model + vendor driver + API key for a session-driven run.
Overridable seam. This is the single injection point that test subclasses override to return a mock BaseRealtimeModel, so executeRealtimeSession can be exercised without provider SDKs or DB metadata.
Production resolution: pick the highest-power active model of AIModelType Realtime; then
pick its highest-priority active vendor whose DriverClass has a resolvable API key, unless another keyed
vendor matches whether the run will show an avatar better (ChooseRealtimeVendorForAvatar); then
instantiate the driver via the ClassFactory. Returns null (never throws) if any step
can't be satisfied — the caller turns that into a clean FAILED result. (Per-agent realtime
model preference can later be wired through the agent's prompt-model config, the same path
loop agents use for ModelSelectionMode; the AI Agent entity has no direct model FK.)
The execution parameters (for the agent + context user).
OptionaloverrideModelID: stringThe resolved model instance plus its model/vendor identifiers, or null.
ProtectedresolveDecides whether this run's specialization (child prompt) rides in the trailing message, and if so pre-renders it and flags the template to render a stub in its place. Decided from the child template's UNRENDERED text via ResolveSpecializationPlacement, so the answer is the same on every iteration and the layout never flips mid-run. Returns the rendered specialization, or null when it stays in the system prompt.
ProtectedresolveDecides, from the system prompt's UNRENDERED template text, whether the trailing runtime-state fragment belongs on this request:
'trailing' — the template carries the <mj-runtime-state> pointer, so the model is told where
the state lives. The Loop and Flow agent system prompts.'embedded' — the template still renders the state blocks itself (an environment whose
TemplateContent has not synced the new system template, or a custom template that embeds the
payload). Emitting the fragment would deliver the same state twice.'unsupported' — the template has neither. The Harness system prompt, or a custom prompt run
without a system prompt carrying the pointer. The model would receive an unexplained block.'unknown' — no template text to inspect (no TemplateID, or the lookup failed). The caller
fails OPEN here: a Loop agent losing its payload from the model's view is far worse than a
non-Loop agent receiving an unexplained fragment, and in practice every agent type's system
prompt has a template, so this arises only from a lookup failure.ProtectedresolveProtectedResolves the LLM's requested skill names to MJ: AI Skills entities, restricted to what
AIEngine.GetSkillsForAgent allows for this agent (the AcceptsSkills gate + Status
chain). Names that don't resolve are silently dropped here — validateSkillNextStep
is responsible for rejecting unknown/disallowed names before execution ever reaches this
point, so by the time executeSkillStep runs, every requested name is expected to match.
Override to change resolution semantics (e.g. resolve by ID instead of Name). The result is
then passed through the application policy (filterAvailableSkills, purpose
'auto-activation') by executeSkillStep — a subclass calling this directly gets the
pre-policy set.
OptionalcontextUser: UserInfoProtectedresolveProtectedFinds a sub-agent by name, checking child agents (ParentID) first, then
related agents (AgentRelationships). Returns undefined when the name
doesn't resolve to an active agent reachable from params.agent.
Used by both the single and parallel sub-agent dispatch paths so name resolution is consistent and there's one place to fix lookup bugs.
Exposed as protected so driver sub-classes with custom routing logic
still resolve names through the same case-insensitive child-then-related
lookup the framework uses internally.
OptionalallowAnyActiveAgent: booleanProtectedresolveGeneric template resolver for loop iterations - extracts from LoopAgentType to make available to all agent types Resolves templates like {{item.field}}, {{index}}, etc. in action parameters and sub-agent payloads
ProtectedresolveThe tool_choice for this turn (§8.3).
'auto' normally; on the last turn this run will be allowed, a forced complete_task call.
Why the final turn is special. A tool call is a request to continue: the framework runs the action, feeds the result back, and the model decides again. On the last permitted iteration there is no "again" — the limit check fires the moment the turn returns, so the action is executed, paid for, and its result discarded, and the run ends with no answer for the user because the model spent its last turn asking a question instead of answering one. The final turn is forced to what the framework actually needs from it: a terminal answer.
Why complete_task and not 'none'. Under implicit control flow, 'none' forbids the
model's only way to store its result — payload_change_request and complete_task are tools
too — so a forced 'none' turn can end the run but never deliver the payload. Naming
complete_task forces the one call that does both. A model that is NOT on implicit control
flow never receives complete_task (the runner strips control tools), and the runner then
downgrades the named choice to 'none': the envelope turn the hybrid needs. Providers also
enforce a named choice more reliably than 'none'.
What this does NOT fix. Models call a tool on a measurable share of turns whose right
answer was chat, completion or delegation. Those are not predictable
from framework state — only the model knows the task is finished — so no tool_choice can
address them. That belongs to the prompt, and is why the native-mode Actions section names
the cases explicitly.
Subclasses may narrow this further; the base contract is that a forced terminal choice must
never be relaxed to 'auto' on a turn the framework has already decided is terminal.
The run parameters, for the per-run iteration override
{ name: 'complete_task' } on the final permitted iteration, otherwise 'auto'
ProtectedresolveResolves a value from context using variable references - extracts from LoopAgentType
This method handles both scalar and complex object iterations in ForEach/While loops.
Examples:
Scalar array iteration: candidateEntities = ["Users", "Companies", "Invoices"] itemVariable = "entityName" Template: "{{entityName}}" → resolves to "Users", then "Companies", then "Invoices"
Object array iteration: users = [{name: "Alice", age: 30}, {name: "Bob", age: 25}] itemVariable = "user" Template: "{{user.name}}" → resolves to "Alice", then "Bob" Template: "{{user}}" → resolves to entire object {name: "Alice", age: 30}
ProtectedrestoreIn append-only mode, restores turn 1's volatile state fragment if mode resolution was deferred until after turn 1 (e.g. dynamic model selection).
Restores the fragment at the exact message boundary where turn 1 executed, ensuring earlier turns in multi-turn conversations are not corrupted.
This is only correct for a replace → append-only flip between turn 1 and turn 2, when
_lastVolatileStateMessage still holds turn 1's fragment. shouldUseAppendOnlyTrailingState
freezes the mode at the first model selection precisely so that no later flip can occur.
ProtectedrunProtectedRuns one compaction pass and records it as a StepType='Compaction' run step —
TargetID = the summary prompt, TargetLogID = the summary AIPromptRun (the same ID
written to ConversationDetail.SummaryPromptRunID, closing the lineage chain).
Quiet no-ops (window under trigger) record no step; fired passes and failures do.
ProtectedrunWhether THIS run has already placed a volatile-state fragment in the history, i.e. at or after the turn-1 boundary. Fragments before the boundary belong to an earlier run whose history the caller reused; they must not suppress this run's turn-1 restore.
ProtectedrunProtectedExecutes post-run conversation message pre-compaction, scratchpad pruning, and
asynchronous memory extraction after an agent run completes (finalizeAgentRun).
Shifting compaction and maintenance to the end of finalizeAgentRun removes
compaction latency from the next user turn's critical path so subsequent turns
start with a pre-compacted message window.
params.compactAfterRun (boolean | AgentPostRunCompactionConfig).
When undefined, false, or { enabled: false }, returns immediately as a no-op.
When true, applies defaults { enabled: true, messageCountThreshold: 20, extractMemories: true }.params.conversationMessages.length >= messageCountThreshold,
compacts older messages in [1 .. length - preserveRecentCount - 1] while preserving
the initial goal message (index 0) and the most recent messages intact. Delegates to
compactMessage (and CompactToolResultContent for native tool turns) so
behavior and expansion metadata (wasCompacted, originalContent, canExpand) are
100% consistent with mid-run message compaction.maxScratchpadNotes is configured, prunes excess low-priority
scratchpad note lines (via ScratchpadManager.PruneNotes) and completed tasks
(via ScratchpadManager.EnforceTaskLimit) down to maxScratchpadNotes.extractMemories !== false and agentRun.Success is true,
triggers asynchronous note/example extraction via triggerPostRunMemoryExtraction.Every phase is wrapped in its own try/catch block so an error in one phase is logged
via LogError without aborting the remaining phases or failing the completed agent run.
The execution parameters for the completed agent run
The finalized MJAIAgentRunEntityExtended record
ProtectedserializeProtectedSerializes payload for logging to PayloadAtEnd Override in subclasses to customize logging behavior (e.g., summarize large payloads)
The payload to serialize
Serialized string or null to skip logging
ProtectedserializeProtectedSerializes payload for logging to PayloadAtStart Override in subclasses to customize logging behavior (e.g., summarize large payloads)
The payload to serialize
Serialized string or null to skip logging
ProtectedshouldDetermines whether the current prompt execution should use append-only trailing state retention.
Why: OpenAI and xAI prompt caching operate on an exact byte prefix match from token 0. Replacing the trailing runtime-state fragment turn-over-turn breaks the byte prefix after the system prompt, dropping cache hit rates significantly. In append-only mode, prior runtime state messages are retained in the message history so each turn is an exact prefix extension of the prior turn, achieving ~93% cache hit rate. Providers with block-level or sliding caching (Gemini, Cerebras) use replace-in-place to keep context compact.
Which providers are which is METADATA, not code: the PrefixPromptCache flag in the model
catalog's ModelConfiguration cascade (Model Types < Models < Vendors' Configuration.ModelDefaults
< Model Vendors), read through resolvePrefixPromptCache. true means append-only;
anything else means replace.
Decided ONCE per run. An explicit trailingStateMode or a runtime model override answers
immediately. Otherwise the answer is frozen at the first model selection and reused for every
later turn, so a failover to another vendor cannot flip the layout mid-run — a flip after turn 2
would leave stale fragments in the history or, worse, restore the wrong turn's fragment. On turn 1,
before any selection is known, the answer is replace-in-place: turn 1's fragment is kept and, if
turn 2 resolves to append-only, spliced back at the turn-1 boundary, which reproduces exactly the
bytes an append-only turn 1 would have sent. Nothing is lost by deferring, so the prompt's bound
models are deliberately NOT consulted — prompts commonly bind several vendors for failover, and
guessing from them mis-pins runs that end up selecting another vendor.
ProtectedsmartProtectedSmart trimming of message content based on detected format. Attempts to preserve structure while reducing size.
The message content to trim (must be string)
OptionalmaxLength: numberMaximum length for the trimmed content
Object with trimmed content and strategy used
Opens a raw IRealtimeSession for this agent — the duplex model connection a Realtime
Bridge hands to AIBridgeEngine.StartBridgeSession so the agent can talk + hear over a media
transport (a LiveKit room, a Zoom/Teams meeting, a phone call). The bridge engine owns turn-taking
and the transport seam, so this deliberately returns the session itself, NOT a
RealtimeSessionRunner (which is the client-direct topology's own orchestration loop).
It reuses the EXACT same resolution + assembly as executeRealtimeSession — model selection
(resolveRealtimeModel), agent configuration (loadAgentConfiguration), effective-config
persona/voice (resolveRealtimeEffectiveConfig), and the system-prompt + memory context
(buildRealtimeSessionParams) — then opens the session via
BaseRealtimeModel.StartSession. Tools are intentionally NOT pre-populated: the
invoke-target-agent + interactive-surface tools are a runner concern; a bridge that needs them
registers them on the returned session itself.
The execution parameters (agent + context user + the request-scoped provider). A fresh
bridge session typically passes an empty conversationMessages array.
The live realtime session.
ProtectedstartProtectedPost-turn hook (the primary path), called from finalizeAgentRun after the run row is saved. Fire-and-forget by design: the caller's completion event never waits on the summary LLM call. Fires for settled root runs with a conversation — settledRunStatuses: 'Completed' AND 'AwaitingFeedback', because a Chat final step (→ AwaitingFeedback) is the NORMAL ending of a conversational turn; gating on 'Completed' alone silently disabled post-turn compaction for exactly the long-chat scenario this feature targets.
Skipped under a history floor (ConversationHistoryFrom). A run with a floor must not
write the conversation's summary: the summary covers every row below its boundary, and
a run that may not read the rows before the floor can't produce that — nor, once reads
are narrowed to what the asker can see, can it tell which rows it was not shown.
ProtectedtemplateTrue when unrendered template text contains any of volatileTemplateMarkers — the legacy Loop layout, or any template that embeds the payload.
ProtectedtriggerProtectedTriggers asynchronous post-run note and example extraction via MemoryManagerAgent
when the executing agent has memory injection (InjectNotes or InjectExamples) enabled.
No-ops when the executing agent itself is the MemoryManagerAgent (preventing recursion)
or when no active MemoryManagerAgent is registered in AIEngine.
The execution parameters for the completed agent run
The finalized MJAIAgentRunEntityExtended record
ProtectedtryProtectedExtracts the vendor-specific MaxInputTokens from model selection info, returning null when it genuinely cannot be determined. Callers that need a hard number use getModelContextLimit (which falls back to a conservative default); callers for whom a guessed default would be WRONG — e.g. cross-turn compaction budget clamping, where a bogus 8000 would clamp a configured 200k budget — use this and handle null explicitly.
ProtectedupdateProtectedUpdates the compaction step with execution results and token usage.
The run step to update
The prompt execution result
The message that was compacted
ProtectedvalidateProtectedvalidateValidates that the actions next step is valid and can be executed by the current agent. Subclasses can override this method to implement custom validation logic if needed.
ProtectedvalidateProtectedValidates that the agent is active and ready for execution.
The agent to validate
Error result if validation fails, null if valid
ProtectedvalidateValidates that the Chat next step is valid and can be executed by the current agent. Implements ChatHandlingOption remapping logic - if the agent has ChatHandlingOption set, the Chat step is remapped to the specified value (Success, Fail, or Retry). Subclasses can override this method to implement custom validation logic if needed.
ProtectedvalidateValidates that the Failed next step is valid and can be executed by the current agent. Subclasses can override this method to implement custom validation logic if needed.
ProtectedvalidateValidates if the next step is valid, or not. If the next step is invalid, it returns a retry step with an error message that can be processed by the agent via a retry prompt to attempt to correct the issue. Alternatively, subclasses can handle this scenario differently as desired.
The BaseAgent class implements checking for sub-agents and actions to ensure that the next step is valid in the context of the current agent. If the next step is a sub-agent, it checks if the sub-agent is active and available for execution. If the next step is actions, it checks if the actions are valid and available for execution.
ProtectedvalidateProtectedValidates that a 'Plan' next step (Plan Mode) has plan text to present. Subclasses can override to add additional plan-quality checks (e.g. minimum length, required sections).
ProtectedvalidateValidates that the Retry next step is valid and can be executed by the current agent. Subclasses can override this method to implement custom validation logic if needed. The retry step is typically used to handle cases where the agent needs to re-attempt a step due to an error or invalid state.
ProtectedvalidateProtectedValidates that the requested skill(s) are known and allowed for this agent (resolved via AIEngine.GetSkillsForAgent, which enforces the agent's AcceptsSkills gate + the catalog/grant Status chain). Subclasses can override to implement custom validation logic.
Mirrors validateActionsNextStep's fuzzy-name-matching UX: an exact case-insensitive match is tried first, falling back to a CONTAINS match when exactly one candidate matches.
ProtectedvalidateProtectedvalidateProtectedvalidateValidates that the Success next step is valid and can be executed by the current agent. Subclasses can override this method to implement custom validation logic if needed.
ProtectedvalidateValidates a 'Tasks' next step before executeTasksStep submits the graph.
Verifies that every referenced Agent (including ForEach/While loop sub-agents) is
active and runnable by params.contextUser, and that every referenced Action is active.
When an agent or action name is unknown or inaccessible, converts the step to a self-correcting
Retry with the user's permitted runnable agent list instead of letting TaskGraphService.Submit
terminate the entire agent run as Failed. Never reveals whether an invalid agent name was
nonexistent versus restricted by permissions.
ProtectedwarnSays out loud what did not make it into a persisted column.
A silently dropped value is the failure one layer down from the crash this prevents: a reader hunting a field the writer discarded, or a condition reading absent-data and taking a branch nobody can explain. Warn, never throw — a value that cannot be written down is not a reason to fail a run that is otherwise fine.
StaticBuildThe MJ: AI Agent Runs predicate for this agent's previous settled root run on one
branch path: the run's reply row must be on that path, so a run made on a sibling
branch is never carried forward, and not replaced by a rerun of its turn, so a replaced
answer's tool results are never carried forward.
StaticBuildRenders prior-turn tool-result steps into the carried-forward message body.
Pure and static for testability: keeps only steps whose OutputData satisfies the
structured contract stamped by the tool executors — a carry-forward-eligible
toolFamily (see CarryForwardToolFamilies) AND a non-empty tool name.
Tolerant of missing/invalid OutputData JSON, keeps only successful results,
caps each result and the total under maxChars (adding an explicit truncation
note when results are dropped). Returns null when nothing usable remains.
StaticBuildRenders a stopped run's completed step results into the carried-forward message body.
Pure and static for testability. An Actions step renders as the action call (name and
params) with its output parameters, or its message when it produced none; a Tool step
renders under the same contract as BuildPriorTurnToolResultsMessage. Failed or
unparseable steps are skipped. Each section is capped at an even share of maxChars
(never below a readable floor) and the whole body at maxChars, with a note for anything
dropped. Returns null when nothing usable remains.
StaticCancellationMaps the reason an abort signal carried to the run's CancellationReason value: the
watchdog's stop relay raises AgentRunWatchdog.UserCancelAbortReason for a row marked
User Request; the wall-clock guard's reason starts with AgentTimeoutAbortReason;
anything else (an upstream caller's token, a shutdown) is System.
StaticExtractThe run's own turns: what follows the run's starting message in its last recorded stack, minus the per-request runtime-state fragments (the new run appends its own). The starting message is matched at its original position first, so a repeated message earlier in the history ("keep going" twice) cannot be mistaken for it. Null when it cannot be found.
StaticLastThe payload recorded at the end of the latest step that recorded one, as its serialized
string, or null when no step has. Steps are examined from the highest StepNumber down
(array order is spawn order, which a sub-step can interleave).
StaticSpliceInserts resumed turns ahead of the user's new message (and ahead of the stopped run's own reply to the user, when that sits right before it), followed by StoppedRunResumeNotice. The new message is found by identity, captured before the pre-execution phase added anything. Returns false, changing nothing, when it is not a user message still in the list.
Planning Designer Agent - Designs agent architectures by researching existing capabilities
This agent creates TechnicalDesign or modificationPlan by researching available actions, agents, and database entities. It overrides validateSuccessNextStep to enforce that required research actions are called before finalizing designs.
Key responsibilities: