Skip to content

Processor and LLM adaptation

One model call splits into two layers: the LLM adapter issues streamText to the provider and emits raw stream events; Processor consumes those events, writes Message / Part, and returns continue, stop, or compact to runLoop. A few paths such as title generation may call the LLM adapter directly. Three terms come first:

TermMeaning
LLM adapterThe layer that translates conversation semantics into one provider’s protocol: assemble system / messages / tools, apply plugin params and headers, pick a language adapter, start the stream
Language adapterA concrete provider implementation of the unified model interface: it translates the unified request into that provider’s HTTP payload and translates the provider’s stream events back into unified events (text deltas, tool-calls, step boundaries, finish reasons)
StepOne model generation. A single streaming call may span several steps: the model emits tool-calls, tools execute, and the SDK starts the next step with the results; each step is bounded by step-start and step-finish
flowchart LR
RL[runLoop] --> P[processor.process]
P --> L[llm.stream]
L --> E[Stream events]
E --> H[handleEvent persist]
H --> O{Return}
O --> C[continue]
O --> S[stop]
O --> K[compact]
LayerResponsibility
LLM adapterAssemble system / messages / tools, apply plugin params and headers, call the provider stream API
Processorbusy state, Part deltas, tool state machine, step boundaries, compact signals, cleanup of unclosed fragments
  1. Assemble system (role prompts, environment, skills, plugin transforms).
  2. Fire param and request-header plugin hooks.
  3. Use this turn’s already-filtered tool table.
  4. Pick a language adapter by model, call streamText.

Provider protocol differences stop in the adapter layer and do not leak into Processor:

  • Message shape: empty messages, allowed toolCallId characters, reasoning field names, prompt-cache markers, tool JSON Schema spelling, and sampling defaults all differ; they are fixed before the request leaves.
  • Processor therefore only sees unified event shapes (text deltas, tool-calls, step boundaries, finish reasons) and needs no per-vendor branches.
  • Swapping providers means swapping the language adapter and model metadata: the implementation below the adapter changes, while Processor and the outer loop stay put.

Model id priority: explicit caller input → model on the session’s latest user message → default model. Special workflow models follow provider load rules; they are not inferred from user natural language.

A step boundary is both the start and end of one model generation, and the anchor for usage, snapshots, and governance:

BoundaryWhat Processor does
start-stepRecord the snapshot anchor, write a step-start Part
finish-stepSettle this step’s tokens and cost into the assistant message, end the generation observation, write a step-finish Part, trigger session summary and file patch when applicable, and decide whether compaction is needed
flowchart LR
A[start-step snapshot anchor] --> G[Model generation]
G --> T[tool-call and execute]
T -->|SDK starts next step with results| A
G --> B[finish-step settle and persist]

Why the boundary matters:

  • Billing and usage accumulate per step; tokens / cost on the assistant message are the sum of steps.
  • File-change patches and tool-execution snapshot ranges are scoped to a step, so steps do not blur together.
  • Collaboration cost hard-stops land on step boundaries instead of interrupting in-flight tasks: by the time execution reaches here, this step’s Parts, patch, and accounting have closed, so nothing is left half-written.
  • The near-limit context check also completes at finish-step; once flagged, runLoop switches to compaction.

When a fixed JSON shape is required, the caller passes format: { type: "json_schema", schema } explicitly on the prompt input. The runtime does not inject format because the user described fields in prose. On hit, a dedicated structured tool closes the loop; the result writes into the assistant message and the outer loop may end early.

flowchart TD
Start[start] --> Busy[Session busy]
Busy --> Text[text / reasoning deltas]
Busy --> Tool[tool input / call / result]
Busy --> Step[step start/end + tokens]
Step --> Sum[Optional SessionSummary]
Tool --> Doom[Loop-call detection, etc.]
Text --> End[finish / error]
Tool --> End
Step --> End

Closeout:

  • Needs compaction → compact
  • Blocked or error → stop
  • Otherwise → continue

Context-overflow errors may convert into a compaction need instead of failing out immediately. Cleanup closes unfinished text/reasoning, marks unfinished tools aborted, and writes completion time.

Processor returnrunLoop
continueEnter the next while iteration
stopEnd this loop
compactCreate or queue compaction; next iteration handles it first

Details: runLoop and context compaction and End-to-end request lifecycle.

SymptomCheck first
Provider error / timeoutModel config, adapter, header and param hooks
Call happened but no PartWhether Processor events arrived; whether storage writes failed
No follow-up after tool resultReturn was stop vs continue; whether a tool-call is still pending
Usage or cost does not add upWhether every step wrote a step-finish; whether retries or model fallback opened a new generation
Expected JSON but unstructuredWhether json_schema format was passed explicitly

Related reading: