Operating a Fleet of Coding Agents

The Control Plane

The bug that scared me most did not crash anything. It told the truth about a lie.

In the operator control plane, the local service that gates every outward action my agents take, there is a state machine that tracks each pending action through its life. It's the thing standing between an agent's intention to send an email and an email actually leaving the building. An approval is pending, then approved, then sending, then sent. Clean, legible, the kind of state diagram you draw on a whiteboard and feel good about. And buried in the recovery path was a sequence that could turn one of those labels into a confident lie.

Here is the sequence, traced through the real code in app/src/service/review-approval.ts. When the service sends an approved draft, sendApprovedDraft writes the state "sending", then awaits the actual Gmail call, then, on success, writes "sent". Three steps. Now kill the process in the gap between step two and step three: Gmail has accepted the message, the email is genuinely on its way to a real human, but the "sent" write never lands. The approval is frozen mid-flight in "sending", and no provider message-id is persisted until the success write, and that is what makes it dangerous: there is no idempotency key, nothing to dedupe against. Fifteen minutes later a janitor sweep, recoverStaleSendingApprovals, finds the stuck record and relabels it: state="send_failed", last_error_code="send_interrupted". And then the action gate, assertApprovalActionAllowed, cheerfully permits an approve from send_failed, because of course it does, a failed send should be retryable.

Read what just happened. The state says failed. The reality is delivery unknown, the email may very well have been sent. An operator looking at "failed" does the obvious, correct-seeming thing: re-approve, re-send. And the recipient gets the same email twice. Real money if it's a billing notice, real reputation if it's a client. The state machine did not crash. It produced a label, the label was false, and a human trusting the label took an unsafe action on its authority. That's the whole chapter in one bug: a control plane's deepest job is not to act; it is to never lie about what it did, because everything downstream trusts the label more than the truth.

Gate every outward action

Step back to the principle, because the bug only matters in light of it.

An agent that can send email, open pull requests, post to channels, or call third-party APIs has outward reach, it can make things happen in the world that you cannot un-happen. Chapter 3 was about containing what an agent can do to your machine; this chapter is about containing what it can do to everyone else. And the move is the one the whole book keeps making: you don't ask the agent to be careful about sending. You put a gate between the agent and the outside world, the gate is enforced in code the agent cannot talk past, and every outward action flows through it.

In the control plane that gate is a four-step flow, and I want to walk the clean path, not just the broken one, because the architecture of the happy path is what makes the broken one fixable. An agent drafts: mail_draft_create writes a draft artifact, nothing leaves. The agent requests approval: approval_request_create mints an approval_id and moves the record to pending. Then the two steps that an agent cannot perform, approve and send, are operator-only, and each is protected by a confirmation token whose construction is the point.

The token isn't a password. It's a cryptographic binding. The digest is SHA-256(approvalId : action : draftDigest : token), which commits, in one hash, to which approval, which action (approve is not send), and which exact draft content. That binding is what makes replay impossible: a token minted to approve cannot be replayed to send, a token for one approval cannot be reused on another, and if the draft text changes after the token is minted, the draftDigest no longer matches and the token is dead. The service enforces that last property directly: mutating a draft nulls the digest and expires the approval, so there's only ever one outstanding token per approval, bound to exactly the bytes the operator saw when they decided to trust it. The token has a ten-minute TTL enforced in SQL, not in application code, and it is consumed by an atomic UPDATE-as-compare-and-swap against single-writer SQLite: single-use, no time-of-check-to-time-of-use window, no way for two sends to both believe they won the token. (The book's evidence table elsewhere ascribes a specific bit-length to that token; I could not confirm the exact entropy figure in the session sources for this chapter, so I describe the binding, which is the load-bearing security property, and leave the bit-count marked [unverified] until I read it out of the token-generation code directly.)

That's the positive path: draft, request, approve-with-bound-token, send-with-bound-token, every irreversible step requiring a fresh single-use credential the agent can neither mint nor reuse. It's a good design. And a good design is exactly the kind of thing that lulls you into not auditing it, which is how the double-send bug lived in the recovery path of an otherwise-solid state machine.

The state machine's one job

So I audited it, the way Chapters 3 and 5 taught me to audit my own controls: not by reading the code for elegance, but by reasoning adversarially about where the labels could lie.

The double-send fix is small and it is the right kind of small. The recovery sweep already had a message, "inspect Sent mail before retrying," but that message was advisory. It was a sentence printed for a human, and nothing in the state machine enforced it. (You will recognize this from Chapter 2: a human instruction is not a code contract. "Be careful" addressed to a machine is the same non-control as "be careful" addressed to an agent.) The fix turns the sentence into a transition rule: an approval marked with the send_interrupted error code, which signals delivery-unknown rather than a real Gmail rejection (real rejections carry the provider's actual error code), can no longer be silently re-approved. It must pass through the operator-only reopenApproval checkpoint, which forces a human to look at the Sent folder and clear the marker by hand before the send path reopens. Genuine failures, which carry a real error code, stay directly retryable. Only the lie gets a guardrail.

And the verification is the part I insist on, because a security fix you didn't prove both directions is a mood, not a fix (Chapter 3's law, applied to state instead of shell). The regression test drives an approval into sending with a stale timestamp, runs the normalize sweep so it becomes send_failed/send_interrupted, and then attempts a re-approve. On the unpatched code the re-approve succeeds, the test fails with "Missing expected rejection," which is the bug reproduced as a red test, the double-send path provably open. On the patched code the re-approve is rejected, the state holds at send_failed, and the sanctioned reopen-then-send path still works and sends exactly once (sendCount === 1, the both-directions assertion): the lie is blocked and the legitimate recovery still functions. Red before green, full suite green before the PR.

There was a second bug in the same audit, smaller but the same species. rejectRequest, the verb that terminally rejects a pending approval, had no operator gate. Its siblings did: cancelApproval and reopenApproval both call assertOperatorOnly first. rejectRequest checked only that a note was present. Which meant an assistant-tier caller, holding only an assistant bearer token, could reject an approval the operator had already approved and was about to send, unilaterally overriding operator intent through a verb that forgot to ask who was calling. One line closed it: assertOperatorOnly(identity, "reject this approval"), slotted in ahead of the note check, proved red-then-green the same way. Two real holes, two merged fixes, each with a regression test, and not one real email sent during the entire audit; the tests run against a fixture send implementation, because you don't verify a double-send guard by double-sending.

No unified write store, ever

The architecture under all of this deserves its own beat, because it's a deliberate refusal that most people building a "personal dashboard" get wrong.

The control plane is a hub, and it is explicitly not a database of everything. The design is hub-and-spoke: the hub owns the read-and-present layer and the approval-gated action layer, and it owns no source data. Six spokes, bridge-db, the notification hub, the portfolio auditor, the eval suite, the knowledge vault's exporter, the Notion snapshot, each remain the system of record for their own data, and the hub reads from them through declared seams. There's no unified write store, ever. That phrase is load-bearing. The moment you build one database that everything writes into, you have built a single point where every producer's bugs commingle, where a schema migration touches all of them at once, and where "who last wrote this and can I trust it" becomes unanswerable.

Instead, every artifact the hub reads is wrapped with provenance and a freshness verdict. A SpokeArtifact carries when it was generated, when it was read, and a computed freshness derived from the spoke's own declared policy: fresh, aging, stale, or unavailable. And there is a hygiene rule binding it all, which is itself a small instance of the book's thesis: alert-class outputs may derive only from fresh payloads. A stale spoke cannot page you about a risk score, because the score is computed from data you've already flagged as untrustworthy; the only alert a stale spoke is permitted to raise is its own staleness. This is the state machine's "don't lie" rule generalized to the whole hub: a control plane is not allowed to dress up stale data as a live signal, because a human who acts on a confident-looking alert built from week-old inputs is the double-send operator all over again, one layer up.

That rule wasn't free, either. A pre-commit review of the freshness machine caught a defect on its way to shipping: computeFreshness had a NaN leak, an unparseable generatedAt fell through to stale with an age of NaN, which is to say the function would silently mislabel data whose freshness it actually could not determine. The fix routes unknown age to aging with a warning, never silently to stale: degrade loudly, never suppress, and two regression tests pin it. Even the control that enforces honesty had a small dishonesty in it, caught only because someone reviewed the control with the same suspicion it was built to apply.

The class, not the instance

The two bugs were instances. The class closed only after the audit swept the whole service layer for dropped guards and fire-and-forget promises, then proved every flagged site resolved to an awaited call, a Promise.all, or a delegate the caller awaited. The control plane now gates outward action, mints fresh action-bound tokens, refuses a unified write store, wraps inputs in provenance and freshness, and treats the honesty of its own state labels as a security property. A stale spoke cannot page you about a risk score; the only alert a stale spoke may raise is its own staleness. The human should never be asked to act on a confident label built from data the system already knows is old.