Everyone says “AI agents will write code.” Most demos show one agent in a sandbox finishing a toy task. The interesting question isn’t can-it-write-code — it’s how do you put it into production without it eating your codebase.
For the last six weeks I’ve been running a system that does. Three different AI agents, each with a narrow role, mediated by a Linear state machine and a 15-minute polling monitor. Tickets flow from Todo through Human Review to Done on their own. I wake up to merged PRs.
This piece walks through the six pieces that make it work. Each section has its own diagram you can read in isolation. The pattern is the deliverable, not the specific tools — so I’ve kept it generic. Build your own.
01 · ground floor
The Linear state machine is the source of truth
The system runs on Linear. Every piece of work is a ticket. The ticket’s statetells the agents what to do — there’s no separate queue, no separate orchestrator state, no Redis. The state IS the orchestrator.
Tickets flow left-to-right: Backlog → Todo → In Progress → Human Review → Merging → Done. The Rework state catches failures and loops back. Backlog auto-promotes to Todowhen the previous wave finishes — so I can plan months of work without manually feeding the queue.
Three details worth noting. One: Human Review isn’t where I sit — it’s where the monitor sits. The 15-minute poll runs the merge gate, not me. Two: Rework doesn’t close the PR. The same branch gets force-pushed with line-level fixes, which is the single biggest token saving in the system. Three: after 3 failed Codex reviews on a PR, the monitor escalates to me. It refuses to burn tokens forever on a non-converging loop.
02 · the cast
Three agents, narrow roles
The strongest design choice in the system: each agent does exactly one thing. No agent reviews its own code. No agent decides when to merge. The roles are deliberately incompatible.
- Symphony — picks up
Todotickets, reads the spec, writes code, opens a PR, moves the ticket toHuman Review. Never merges. Never reviews. - Human Review Monitor— a launchd job, polls every 15 min. Checks PR mergeability, label, status checks, comments. If gates pass, hands off to Codex for the actual merge decision. Never writes code.
- Codex Review — runs
codex reviewagainst the diff. First line of output must beMERGE_OK: yesorMERGE_OK: no. Never writes code, never decides what to build.
The monitor is the unsexy hero. It’s a 700-line bash script. It reads Linear via GraphQL, GitHub via gh, runs codex review as a subprocess. No frameworks. No queues. The whole system is git + Linear + a cron-style poll.
Why three roles, not two? Because reviewhas to be done by something that didn’t write the code. The first version of this system had Symphony review its own diff — which sounds like a quality gate but is just narcissism with extra steps. Codex’s only job is to refuse to merge things that look wrong. It will block its own work without hesitation.
03 · the failure mode i fixed
Mandatory context loading before any code
For the first month this system was running, the most common failure wasn’t bugs. It was “the agent built exactly what the ticket said, but missed the bigger picture.”The ticket would say “add a health endpoint” and the agent would add one — but at the wrong path, returning the wrong shape, missing the auth wrapper everything else uses.
The fix isn’t smarter prompts. It’s mandatory pre-loads. Before the agent reads FILES TOUCHED and starts coding, it reads four sources of context. If any are missing, the agent stops and asks.
The agent prints [CONTEXT LOADED]with what it read before writing the first line of code. If any source comes back empty when the ticket implies it shouldn’t, the agent stops and asks the orchestrator. This single change cut the “missed the plan” failure rate to near zero.
The right context is more important than the right model. A weaker model with the right four documents in its prompt outperforms a stronger model that’s guessing.
04 · the policy that saved the most money
Incremental rework, not full reset
The naive design for “agent failed code review” is: close the PR, branch fresh, throw away the workpad, restart from the spec. Every iteration starts clean. It feels right. It’s also expensive — every cycle re-pays the full implementation token cost, just to apply a 4-line fix.
The new policy is the opposite: same branch, force-push the fix, append to the workpad. The PR stays open. The conversation accretes. The agent reads the latest review comment, makes line-level changes, pushes. The monitor re-reviews on the next cycle. Token spend per rework cycle dropped roughly an order of magnitude.
There’s a second-order benefit: the workpad becomes a training corpus. Every PR has a thread of “Codex flagged X → Symphony fixed at line:42 → Codex reflagged Y → Symphony explained out-of-scope wire-up → human approved.” That history is retrievable, searchable, and points at the patterns my agents most commonly miss. I read these on Sundays. Most of them get fixed by tightening the ticket template, not the agent.
05 · the rule that prevented merge hell
Phase parallelism caps
If you let agents run in parallel without coordination, they will fight over the same files. routes.js, rootReducer.js, package.json, the global router config — these are hot files. Two agents touching them in parallel is a guaranteed merge conflict.
The system uses a per-phase parallelism table. Phases are grouped by dependency relations on the tickets themselves. Each phase has a concurrency cap.
The trick: tickets are explicit about which files they touch. Each ticket has a FILES TOUCHED list that the agent must respect. When two tickets share even one file, they’re not independent — they belong in a sequential phase. The orchestrator checks this before promoting tickets to Todo. Drift is far more expensive than waiting.
06 · the ops gates
Three guards I added after burn-events
Every gate in this system was added afterthe system did something I didn’t like. None of them are theoretical. Each one is a scar.
- Repo allowlist— after agents pushed to a deprecated repo I’d forgotten to remove from configs. Hard guard now: monitor refuses to process PRs whose repo isn’t on the explicit list.
- Max review attempts— after a non-converging Rework loop burned ~$40 of Codex tokens overnight on a single PR. Cap is 3 head_sha rejections; then it escalates to me.
- Wave gates— after the orchestrator promoted Wave 2 tickets while Wave 1 was still bleeding, leading to broken inter-wave dependencies. Now: Wave N’s children stay in Backlog until every Wave N-1 child is Done or Canceled.
The lesson isn’t “design defensively from day one.” It’s the opposite — let the system fail in cheap ways, then build a guard for each failure. Every gate above started life as an invariant I assumed. Then it broke. Then it became code. The system gets sharper every week without me getting smarter.
07 · what i’d tell you to steal
The pattern, not the tools
None of the pieces here are novel in isolation. State machines are old. Code review bots are old. The 15-minute polling pattern is old. What’s new is the composition.
The four invariants worth stealing:
- State is the orchestrator.No separate queue, no separate scheduler. The ticket’s state tells the agents what to do. Linear, Jira, GitHub Projects — pick one. Don’t build your own.
- Agents have incompatible roles. The agent that writes code never reviews it. The reviewer never decides what to build. Roles must be deliberate, not coincidental.
- Mandatory context, not smarter prompts. If the agent is missing the bigger picture, more parameters won’t help. Pre-load four explicit sources before the agent reads
FILES TOUCHED. - Iterate, don’t reset.Rework on the same branch with the same workpad. The accreted history is the agent’s training data.
The agents don’t need to be smart. The system needs to be small enough to debug at 11pm. Mine is two scripts and a bash file. Yours can be smaller.