Skip to content

Message Loop

Z-M-Huang edited this page May 4, 2026 · 4 revisions

Message Loop

The stage sequence every turn walks through, with per-stage responsibilities.

The why (turn boundaries, concurrency, cancellation, backpressure) lives in Execution Model. This page is the what-happens-at-each-stage.


Stage sequence

sequenceDiagram
    autonumber
    participant Input as User / Command
    participant Core
    participant CtxP as Context Providers
    participant Prov as Provider
    participant LLM as LLM backend
    participant Tool as Tool executor
    participant UI

    Input->>Core: RECEIVE_INPUT
    Core->>CtxP: COMPOSE_REQUEST
    CtxP-->>Core: context slices
    Core->>Prov: SEND_REQUEST
    Prov->>LLM: ai-sdk request
    LLM-->>Prov: streamed tokens + tool call
    Prov-->>Core: STREAM_RESPONSE
    Core->>Tool: TOOL_CALL
    Tool-->>Core: result
    Core->>Prov: SEND_REQUEST (continuation)
    Prov->>LLM: continued request
    LLM-->>Prov: final tokens
    Prov-->>Core: STREAM_RESPONSE
    Core->>UI: RENDER
Loading

Each stage has pre and post hook points. When an SM is attached, the message loop runs inside each SM stage execution — a sequential stage execution is itself a session turn; a parallel fan-out group (siblings + optional join) bundles multiple stage executions into a single compound turn (Execution Model § Reentrancy). In either case, every stage execution runs its own pass of the stages below: the stage's rendered body (produced at Init, see SM Stage Lifecycle) is the system prompt, allowedTools narrows the tool manifest, the completion channel is injected alongside, and turnCap bounds the continuation loop below. SM authority over this message loop is scoped to tool-call approval at TOOL_CALL, not a per-turn-stage gate; see Execution Model § Authority during a turn.


Stages

RECEIVE_INPUT

Intent. Accept user input (or a command) and start the pass.

Inputs. Resolved input payload from the UI / command (default-chat turn); or the stage's rendered body from Init (SM stage execution). UIs may render large pasted blocks as placeholders while editing, but the payload handed to core has those placeholders expanded back to the original pasted text in insertion order. Outputs. A normalized input record attached to the pass.

Responsibilities.

  • Assign a correlation ID.
  • Default-chat turn. Write the input into the session message history via the active Session Store.
  • SM stage execution. Feed the rendered body into the stage-local transcript only — the stage-local transcript is not written to session message history; see Stage Executions.
  • Emit the SessionTurnStart event on the event bus once per session turn and only for the default-chat case. For SM stage executions, SessionTurnStart is already emitted — at the stage execution's Setup for a sequential stage, or at the scheduler's fan-out spawn for a parallel fan-out compound turn — and is not re-emitted here. See Stage Executions § Turn boundary and persistence.

Hook opportunities.

  • transform: normalize input (e.g., strip trailing whitespace).
  • guard: refuse input on pattern (e.g., empty).
  • observer: log the raw input.

COMPOSE_REQUEST

Intent. Build the request the provider will send to the LLM: system prompt + history + tool definitions + Context Provider output.

Inputs. Session state, the active Context Providers, the active Tools, the active Provider's model capabilities. Outputs. A composed request record.

Responsibilities.

  • Run context assembly — pulls Context Provider outputs within budget.
  • Attach tool definitions the active provider supports.
  • Run compaction if the composed size would exceed the model's context window. See Compaction and Memory.

Hook opportunities.

  • transform: rewrite the composed request before dispatch (e.g., prepend a system message).
  • guard: refuse dispatch (e.g., request too large).
  • observer: audit what was composed.

SM contribution. When the pass runs inside an SM stage execution, the stage's rendered body (produced at Init) is the system prompt and allowedTools narrows the tool manifest (the completion channel is injected alongside). Composition does not poll the SM per turn stage; the stage's contributions were fixed at Init against the StageContext that Setup populated. See Stage Definitions and Stage Executions.

Invariant. Context assembly itself is core. Context Providers are the only sanctioned inputs. See LLM Context Isolation.


SEND_REQUEST

Intent. Hand the composed request to the active provider.

Inputs. The composed request record. Outputs. A handle on the provider's response stream.

Responsibilities.

  • Invoke the provider with the composed request.
  • Start the cancellation-token chain for this call.
  • Start the correlation span for observability.

Hook opportunities.

  • transform: modify the wire-shape at the last moment (rare — prefer COMPOSE_REQUEST).
  • observer: dispatch audit.
  • guard: block at the wire (e.g., retries beyond a limit).

STREAM_RESPONSE

Intent. Consume the provider's streamed response: tokens, tool calls, finish reasons.

Inputs. The response stream handle. Outputs. A sequence of token events and (possibly) one or more tool-call records.

Responsibilities.

  • Publish token events on the event bus as they arrive.
  • Buffer emitted tool calls until complete.
  • On finish without tool call → transition to RENDER.
  • On finish with tool call(s) → transition to TOOL_CALL.

Hook opportunities.

  • transform: filter or rewrite tokens in flight (use sparingly — affects user-visible output).
  • observer: stream-level analytics.
  • guard: rare; may cut off a response on policy.

Non-authoritative. Tokens ride the event bus. Any "stop this response" decision must route through the Interaction Protocol, the SM, or a guard hook — not through event subscription.


TOOL_CALL

Intent. Execute each tool call emitted by the provider.

Inputs. One or more tool-call records with arguments. Outputs. One tool result per call.

Responsibilities.

  • Run the authority stack for each call in order:
    1. SM decision. If an SM stage is driving, the proposed call is checked against the stage's allowedTools and matching grantStageTool tokens. In-envelope or token-consuming calls are SM-approved (mode gate bypassed); out-of-envelope calls auto-issue a grantStageTool interaction (approval via the active interactor set is part of this step). If the grant is denied, defers, or no interactor is active, the proposal is blocked as GrantDenied — the mode gate does not see it.
    2. Security-mode gate. Applies only when no SM is attached. The mode gate may itself prompt the active interactor set — this is where ask mode and allowlist-miss prompts happen. Interaction fan-out is part of the mode-gate step, not a separate later stage.
    3. Guard hooks. Run on every approved call, after approval has been obtained and before execute. Guard-deny wins in any mode.
    4. Execute via the Tools contract.
  • Execute approved calls via the Tools contract. Local and remote (MCP) execute through the same stage.
  • Serialize approval prompts in call order before fanning each prompt out through active interactors.
  • Collect results; emit them back to the provider for continuation.

Hook opportunities.

  • transform: rewrite tool arguments pre-execution.
  • guard: pre-execution deny (e.g., path-based policy).
  • observer: pre/post tool audit.

Invariants.

After tool execution, the loop returns to COMPOSE_REQUEST with the results appended to the session, unless the provider signalled a terminal tool call (which is rare and provider-specific).


RENDER

Intent. Deliver the final response to the user.

Inputs. The final response text / structured output / tool summary. Outputs. Rendered output to the active UI.

Responsibilities.

  • Default-chat turn. Persist the assistant response in the session message history.
  • SM stage execution. Do not persist the stage-local transcript to session message history; the stage's Exit writes whatever the SM authors durably into the SM's state slot. The render payload may still be forwarded to the UI for progress display.
  • Emit the SessionTurnEnd event once per session turn and only for the default-chat case. For SM stage executions, SessionTurnEnd fires at the stage execution's Exit (sequential) or after the last sibling's Exit / the join stage's Exit (parallel fan-out compound turn) — not here. See Stage Executions § Turn boundary and persistence.
  • Hand the render payload to the UI subscriber set via the event bus.

Hook opportunities.

  • transform: rewrite the render payload (e.g., mask sensitive spans).
  • observer: final audit of the turn.

Continuation loop

Stages COMPOSE_REQUEST → SEND_REQUEST → STREAM_RESPONSE → TOOL_CALL may iterate any number of times within a single turn. Each iteration starts from COMPOSE_REQUEST with the new tool results appended. A turn terminates when STREAM_RESPONSE finishes without emitting a tool call and transitions to RENDER.

A loop bound applies per turn:

  • For a default-chat turn, core enforces the session-resolved settings.json.runtime.continuation.maxIterations bound. Default: 50. Reaching the bound fires a terminal error whose message makes it explicit that prior tool calls may already have completed successfully.
  • For an SM stage execution, the stage's turnCap is the loop bound. Core checks the counter at the top of each continuation iteration, immediately before calling COMPOSE_REQUEST for the next LLM turn. If turnCap is reached, no new LLM turn is dispatched: in-flight tool calls from the most recent LLM turn complete normally and their results are appended to the stage-local transcript, but those results are not sent back to the LLM. Act ends with capHit: true; Assert receives the finalized transcript and decides ok, retry, or fail. See SM Stage Lifecycle — Act.

The counted unit is a continuation round (TOOL_CALL -> COMPOSE_REQUEST), not a tool count. Multiple tool calls emitted by one provider response consume one round together.

turnCap is a normal Act-ending branch, not a turn cancellation. Strict cancel (triggered by sibling failure or user interrupt) is separate and does not run Exit; see Stage Executions — Strict cancellation.

See Error Model.


Turn-stage events

Every turn-stage transition emits an event on the event bus:

  • StagePreFired{stage, turnId, correlationId} — just before the stage body runs.
  • StagePostFired{stage, turnId, correlationId, outcome} — after the stage body completes.
  • SessionTurnStart, SessionTurnEnd — bracket the whole turn.

These turn-stage events are distinct from the SM stage-execution events (StageEntered, StageExited, StageCheckGateResult, StageAssertOutcome, StageGranted, StageCancelled, WorkflowExit) which are emitted by a driving SM's stage pipeline — see Stage Executions § Audit and Event Bus § event kinds.

Provider streaming emits ProviderRequestStarted, ProviderTokensStreamed, ProviderRequestCompleted, ProviderRequestFailed. Tool lifecycle emits ToolInvocationProposed, ToolInvocationStarted, ToolInvocationSucceeded, ToolInvocationFailed, ToolInvocationCancelled. Tool-approval decisions are audited separately as ToolCallApproved / ToolCallDenied / ToolCallModeInvoked — see Tool Approvals § audit and Security Modes § audit.

These events are projection only — see Event Bus § non-authoritative rule and Event and Command Ordering for guarantees.


Stage ownership at a glance

Stage Core does Extensions may
RECEIVE_INPUT Normalize, persist, event Transform / guard / observe input
COMPOSE_REQUEST Assembly, compaction, tool list Transform composed request; feed via Context Providers
SEND_REQUEST Invoke provider, start cancel chain Observe dispatch; transform wire-shape
STREAM_RESPONSE Publish tokens, buffer tool calls Filter/observe tokens
TOOL_CALL Approval gate, dispatch, serialize prompts Execute the tool; transform args; guard; observe
RENDER Persist, event, hand to UI Transform render payload; observe

Core owns the shape of every stage. Extensions attach through contracts; they never redefine stage boundaries. See Extensibility Boundary.

Introduction

Reading

Core runtime

Contracts

Category contracts

Context

Security

Runtime behavior

Operations

Providers (bundled)

Integrations

Reference extensions

Tools

UI

Session Stores

Loggers

Providers

Hooks

Context Providers

Commands

Case studies

Flows

Maintainers

Clone this wiki locally