Agent Loop
The agent is a state machine. One call to run_task walks the
diagram below exactly once.
States
┌──────┐
│ Idle │◀──────────────┐
└──┬───┘ │
│ │
▼ │
┌────────────┐ │
│ Planning │ │
└──────┬─────┘ │
│ │
▼ │
┌────────────┐ │
│ Executing │◀──────┐ │
└──┬─────┬───┘ │ │
│ │ │ │
│ │ ToolUse │ │
│ └───────────┘ │
│ │
▼ │
┌────────────┐ failure + │
│ Verifying │ retries left│
└──┬─────┬───┘─────────────┘
│ │
│ │ pass
▼ ▼
┌────────────┐
│ Reporting │
└──────┬─────┘
│
└──────────▶ Idle
Each State
Idle — Awaiting input. Initial and final state of every task.
Planning — Memory recalled, specs loaded, user message added to context. No LLM call yet.
Executing — Streamed LLM call. If the response stops with
ToolUse, the tool calls are dispatched and the loop re-enters
Executing. If the response stops with EndTurn, transition to
Verifying (or Reporting if verification is disabled).
Verifying — fledge lanes run verify --ni runs as a gate.
- Pass →
Reporting. - Fail with retries left → re-enter
Executingafter appending the verifier output as a user message. - Fail without retries left → return an error.
Reporting — Save the task summary as ephemeral memory; build the
final TaskResult.
Invariants
The agent spec (specs/agent/agent.spec.md)
enumerates them:
- State always starts and ends at
Idle. run_taskreturnsOkwithverified=trueonly when verification passed (or was skipped). Cancelled tasks returnOkwithverified=false, cancelled=true.- Tool calls are sequential within a single LLM turn.
- Events fire for all state transitions, text deltas, tool calls.
- Context and
files_changedare cleared at task start. max_retriesis respected.files_changedlists only paths mutated by successfulfiles-write/files-editcalls.- On cancellation,
run_taskreturns promptly viatokio::select!.
Events
Subscribe to events via Agent::on_event:
agent.on_event(|event| match event {
AgentEvent::StateChanged(state) => println!("→ {state:?}"),
AgentEvent::Text(delta) => print!("{delta}"),
AgentEvent::ToolCall { name, args } => println!("▸ {name} {args}"),
AgentEvent::ToolResult { name, success } => println!("{name} → {success}"),
AgentEvent::VerifyResult { success, .. } => println!("verify: {success}"),
AgentEvent::Usage(u) => println!("tokens: {}/{}", u.input_tokens, u.output_tokens),
_ => {}
});
AgentEvent is #[non_exhaustive] — pattern-match it with a _
wildcard arm to remain compatible across versions.
Streaming
call_llm_streaming opens an mpsc channel and forwards
StreamEvent::TextDelta immediately as AgentEvent::Text. Tool
arguments are accumulated across ToolUseInputDelta events and emitted
as a single AgentEvent::ToolCall at ToolUseEnd (or, for providers
that don’t emit Stop events, on Done).
Cancellation
A registered CancellationToken is checked at every state transition
and wraps the LLM call in tokio::select!. See the
Cancellation page.