State Machine
The agent loop has six states. A transition handler runs the work for each state and chooses the next one.
States
| State | Work | Next state |
|---|---|---|
thinking | Check loop limits, clear older tool results when needed, prepare the prompt and tool schemas, and start a model call. | streaming, or done if a limit is reached |
streaming | Stream text and collect requested tool calls. Eligible read-only tools can start during streaming. | executing when tools were requested; otherwise done |
executing | Validate and execute tools, then append results to the conversation. | executing, confirming, compacting, thinking, or done |
confirming | Wait for the user's approval of a tool call. | executing when approved; otherwise thinking with a denial result |
compacting | Summarize older messages and retain recent turns. | thinking |
done | Record the completion reason and accumulated usage. | None |
Limits, cancellation, and errors can end the loop between transitions. Subagent dispatch runs as a tool inside executing; each specialist has its own loop.
Streaming
Clients receive text deltas while the model generates them. Tool-start, progress, and subagent events can also be emitted while a tool is running. Final tool results are added to conversation history for the next model call.
State handlers share mutable execution context, including messages, usage, tools, and cancellation state. They can call providers and tools; they are not pure functions.
The ordinary chat endpoint sends these events as SSE data frames. See Runtime Server for the wire format and client integration.
Confirmation
A tool that requires approval emits confirmation_required. The client sends the decision to /chat/sessions/:id/ask-user-response using the event's correlation ID.
Ordinary chat confirmation waits within the active request. Durable workflows have separate approval and resume endpoints; see Agent Workflows.
Done reasons
| Reason | Meaning |
|---|---|
model_stop | The model finished without requesting more tools, or a tool ended execution. |
max_turns | The model-turn limit was reached. |
user_abort | The request was cancelled. |
error | An unrecoverable error ended the loop. |
budget_exceeded | Accumulated session tokens reached maxSessionTokens. This is checked between transitions and can overshoot. |
loop_detected | Optional repeated-tool detection reached its limit. |
Normal completion emits a done event with usage and the reason. A disconnected client cannot rely on receiving it; use persisted session history to inspect the result of an interrupted request.
Implementation
The loop lives in packages/runtime/src/agent/loop.ts. loop-types.ts defines the states and shared context; states/ contains the handlers. The transition dispatcher uses an exhaustive TypeScript switch so an added state must have a handler.
See The Core Loop for the user-level sequence and Context Management for compaction and loop detection.