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).

Verifyingfledge lanes run verify --ni runs as a gate.

  • Pass → Reporting.
  • Fail with retries left → re-enter Executing after 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:

  1. State always starts and ends at Idle.
  2. run_task returns Ok with verified=true only when verification passed (or was skipped). Cancelled tasks return Ok with verified=false, cancelled=true.
  3. Tool calls are sequential within a single LLM turn.
  4. Events fire for all state transitions, text deltas, tool calls.
  5. Context and files_changed are cleared at task start.
  6. max_retries is respected.
  7. files_changed lists only paths mutated by successful files-write / files-edit calls.
  8. On cancellation, run_task returns promptly via tokio::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.