The Agent Control Loop & State Machine
Chapter 4 gave us a substrate that survives failure. This chapter defines what actually runs on it. The agent loop of Chapter 1 was a circle drawn on a whiteboard; to run it for real we must model it as an explicit state machine, decide precisely what to checkpoint at each transition, and make pausing for a human a first-class state rather than an afterthought.
5.1 The loop, rendered as a state machine
An ad-hoc while loop is the wrong abstraction for a production agent: it conflates control flow with state, hides where failures can occur, and offers nowhere natural to persist progress or insert a human. The right abstraction is an explicit finite-state machine whose transitions are the only points at which the run advances — and, not coincidentally, the only points at which we checkpoint. Each turn of the agent is one lap through the machine; each transition is a durable step in the sense of Chapter 4.
5.2 What to checkpoint, and what not to
The temptation is to serialize the entire process and call it state. The discipline is to persist the minimum that lets the run resume correctly — and to treat large or reconstructable artifacts by reference. The state object is the run's source of truth; keep it lean, versioned, and serializable.
| Field | Holds | Why it must survive a crash |
|---|---|---|
| run_id, parent_id | Stable identifiers, lineage to spawning run | Idempotency, tracing, fan-in correlation |
| status | Current FSM state (planning, acting, awaiting_human…) | Tells the engine where to resume |
| messages[] | Conversation / scratchpad transcript | The model's working context; lost = amnesia |
| step / turn | Loop counter | Enforces the iteration budget (Ch. 2) |
| pending_action | Tool name + validated arguments not yet executed | Lets a half-issued call retry idempotently |
| budget | Tokens/cost/time consumed vs. ceiling | Prevents runaway spend across a resume |
| artifact_refs[] | Pointers to object storage, not blobs | Keep the state row small; rehydrate on demand |
Model the run as an explicit, serializable, versioned state object mutated only at named transitions. This is what makes a run resumable, replayable, auditable, and testable. A loop whose state lives only in local variables on one machine is none of these things.
5.3 Human-in-the-loop is a state, not an interrupt
Most consequential agents must pause for human judgement — to approve a refund, confirm a destructive action, or disambiguate an instruction. Bolting this on with a blocking call that holds a thread (or a server) for hours is ruinous at scale. The durable-execution substrate (Ch. 4) makes the correct design cheap: Await human is an ordinary state in which the workflow blocks on a signal (an external event carrying the human's decision) or a durable timer (a timeout that escalates). While waiting, the run consumes no compute — it is a row in Postgres, not a held connection. Approval can arrive in seconds or days; the cost is identical.
Three patterns recur: an approval gate before high-risk tool calls; an edit-and-resume where a human corrects the proposed action; and a timeout-to-escalation where no response within an SLA routes the run to a fallback or a person. All three are expressed as transitions out of the Await-human state.
5.4 The graph model in practice
Frameworks make this state machine concrete. In LangGraph, the run is a StateGraph: nodes are functions that read and write a shared typed state; edges (plain or conditional) encode transitions; reducers define how each node's output merges into state (e.g. append to the message list rather than overwrite). A checkpointer persists state after every node — to Postgres in production — giving exactly the durability of §5.2. An interrupt (or interrupt_before on a node) suspends the graph and surfaces it for human input, implementing §5.3 directly. Whether you adopt a framework or build the FSM by hand atop your durable engine, the contract is the same: typed state, named transitions, persistence at every edge, interrupts as states.
If a node mutates something outside the state object — a module global, a file on local disk, an un-checkpointed cache — that mutation will not survive a resume and will silently diverge on replay. The rule mirrors Chapter 4: all run-relevant state flows through the state object; side effects live in steps and are made idempotent.
5.5 Steering, interruption, and the feedback loop
Because the run is durable state advanced by signals (§5.3, Ch. 4), the machinery that pauses for approval also lets a human steer a run in flight — injecting a correction or new instruction as a signal the loop reads on its next turn — and cancel one cleanly. Cancellation is cooperative: a cancel signal sets a terminal intent, the current step is allowed to finish or unwind, side effects already committed are left intact or compensated, resources are released, and the partial result and reclaimed budget are recorded. A long agentic run that cannot be steered or stopped is both a cost hazard (Ch. 20) and a safety one.
The same loop is where feedback is captured — the fuel for evaluation (Ch. 19), prompt optimization (Ch. 7), and model customization (Ch. 28). Two kinds: explicit signals from humans (a thumbs-up, an edited answer, an approval or rejection at a gate) and implicit signals from the run itself (did the tool call validate? did the user accept the output? did the run finish within budget?). Capturing both, tagged to run and tenant, and routing them to the gateway's feedback store (Ch. 6) and the eval set (Ch. 19) is what closes the improvement loop the rest of the book depends on.
Model human steering and cancellation as ordinary signals into durable state, so any run can be redirected or halted without losing work or leaking side effects. And treat every run as a source of feedback — explicit and implicit — captured by default and routed to evaluation and optimization. Autonomy you cannot interrupt is unsafe; autonomy you cannot learn from cannot improve.