Agent loop

Chat, code sessions, the kanban board and workflows all share one kernel: AgentLoopService.RunAsync. It turns messages, tools, approvals, compression and the event stream into a single turn; business semantics stay with each caller.

Order of a single run

  1. Resolve the model: a ModelId on the request creates that provider, otherwise the default chat model is used; if neither exists the run throws instead of silently substituting another model.
  2. Load tools: built-in / session tools first, then tools/list from the MCP servers bound to the session; the merged set refreshes the tool schemas and system prompt.
  3. Enter the loop (15 iterations by default): each iteration first drains steering into context, then compresses history per configuration, then calls the model.
  4. Handle output: prose and thinking stream out as events; tool calls go to the caller's decider, which either executes them or requests approval.
  5. Feed results back: tool results enter the context and are pushed to the UI; per-iteration prose, thinking, tools and usage accumulate into the run result.
  6. Finish: the run ends at the iteration cap or when no further tool calls arrive; the caller persists messages, records usage and appends the done frame.

Key parameters

ParameterDefaultMeaning
MaxIterations15Iteration cap; a value ≤ 0 falls back to 15 to prevent infinite loops
MaxToolCallsPerTurn5Per-turn tool budget, enforced through the system prompt rather than hard truncation
CompressHistorytrueSet false to skip history compression when debugging compression side effects
EnableToolstrueWhen off, no tool schemas are sent — plain conversation mode
CoreToolNamesnullWhen set, only these core tools are exposed; the rest follow session configuration

Mid-run steering

  • Injected at a fixed point — the start of each iteration — so it never interrupts a tool chain halfway and contradicts the context.
  • The injected text carries a fixed prefix (【运行中引导,请立即结合它调整当前工作】) so the model can tell it is a mid-run redirection.
  • Clicking "steer" while streaming queues the instruction in AgentSteeringHub; the next iteration picks it up and a steering event is echoed to the UI.
  • Queue vs steer: a queued message is sent after the current turn; a steer changes the running turn immediately.

History compression boundaries

BehaviourImplementation
ScopeThe boundary stops before the current user message: the message being answered and anything produced after it are never compressed, so the user's own words are not summarized away
DeduplicationAlready-compressed slices are remembered in compressedIndexes; long sessions never re-compress the same slice
MethodCalls the full compression pipeline entry (LLM summary allowed, subject to pipeline mode and threshold)
Cost visibilityThe first iteration estimates system prompt and tool schema character cost and logs it, separating "heavy prompt" from "long history"

Tool loading and MCP

  • MCP servers that are missing, disabled or not stdio are skipped with a warning; a single server failing to load logs an error and continues the turn instead of breaking it.
  • Successfully loaded tools merge with built-ins; name conflicts resolve in favour of built-ins, and the merged schemas feed permission decisions.
  • MCP tools are registered at runtime only and never persisted; server configuration lives in settings.

Event outlets

OutletPurpose
IAgentLoopSink.OnEventAsyncStructured event passthrough: SSE writes frames verbatim, non-streaming callers may ignore it
IAgentLoopSink.OnSteeringAsyncCallback when a steer is accepted
Hooks.AfterToolResultsAsyncPost-processing hook (code sessions persist the tool timeline here)
Caller dutiesPersist messages, record usage (ILlmUsageRecorder) and append the done frame; code sessions additionally persist subagent events for replay

Failure and degradation

  • Model unavailable: throws, and the UI receives an error frame — no silent model substitution.
  • A single MCP server fails: that server is skipped, other tools keep working.
  • Cancellation: OperationCanceledException is not swallowed; the caller decides whether it means "cancelled" or "requeue" (background tasks requeue).
  • Compression failure: the pipeline degrades internally only (missing provider or a non-shorter result returns the original text).

Non-obvious but important

  • The iteration and tool limits are guard rails, not business rules: the former stops loops, the latter is enforced through the prompt so the model keeps judgement.
  • Steering is injected only at iteration boundaries; several clicks within one iteration are drained as one instruction.
  • Chat and code sessions differ in their permission decider and tool set, not in the kernel (code sessions bring work_* tools and a workspace scope).
  • Kanban and workflows reuse the same kernel, swapping the event outlet for run-step / node-event recording.

Debugging hints

  • Start with the [AgentLoop] and [Compression] log lines: the first shows iteration counts and tool loading, the second shows before/after sizes of history compression.
  • Suspect compression is changing behaviour? Turn CompressHistory off and reproduce.
  • Tool unexpectedly unavailable: check whether the MCP server is disabled or non-stdio, and whether the tool is denied by the permission mode or a project rule.