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
- 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.
- 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.
- Enter the loop (15 iterations by default): each iteration first drains steering into context, then compresses history per configuration, then calls the model.
- Handle output: prose and thinking stream out as events; tool calls go to the caller's decider, which either executes them or requests approval.
- 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.
- 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
| Parameter | Default | Meaning |
MaxIterations | 15 | Iteration cap; a value ≤ 0 falls back to 15 to prevent infinite loops |
MaxToolCallsPerTurn | 5 | Per-turn tool budget, enforced through the system prompt rather than hard truncation |
CompressHistory | true | Set false to skip history compression when debugging compression side effects |
EnableTools | true | When off, no tool schemas are sent — plain conversation mode |
CoreToolNames | null | When 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
| Behaviour | Implementation |
| Scope | The 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 |
| Deduplication | Already-compressed slices are remembered in compressedIndexes; long sessions never re-compress the same slice |
| Method | Calls the full compression pipeline entry (LLM summary allowed, subject to pipeline mode and threshold) |
| Cost visibility | The 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
| Outlet | Purpose |
IAgentLoopSink.OnEventAsync | Structured event passthrough: SSE writes frames verbatim, non-streaming callers may ignore it |
IAgentLoopSink.OnSteeringAsync | Callback when a steer is accepted |
Hooks.AfterToolResultsAsync | Post-processing hook (code sessions persist the tool timeline here) |
| Caller duties | Persist 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.