Personal project
Agent Harness · An Autonomous Dev Team with a Deterministic Gate
Sixteen Claude Code agents working a self-hosted Paca board in parallel lanes — and a minute-by-minute harness that is the only thing allowed to mark a task Done.
16 role agents · 218 harness tests · Done means gated
The problem
Coding agents mostly follow process. Mostly is the problem. Given a board, a team of role agents and a set of automations, they will occasionally skip a review, forget to merge, mark their own work Done, or edit a test until it passes. Each slip is small; together they mean nothing on the board can be trusted, which defeats the point of having a board.
The question I set out to answer was narrow: what does it take to let a team of agents run a real backlog for days, unattended, and still be able to believe the word Done?
What I built
A self-hosted Paca board drives sixteen Claude Code agents connected over ACP, each with one role: implementers, spec reviewers and code reviewers in parallel build lanes, plus shared Security, UX, Planner, Architect, QA, DevOps and Technical Writer roles. Hand-offs that only touch the task that fired them — assign the builder when a task reaches Ready, send a bug to the Planner — are Paca automations, so they happen the instant the event does.
Everything else lives in a deterministic harness that runs every minute:
- A release gate that is the only setter of Done. A task is Done when the required reviews are on it, its own commits are merged into the integration branch, and CI is green on that task’s commit — pushed to a throwaway ref so one red task can’t break the trunk for every other lane. Which reviews are required comes from the files the diff actually changed, plus markers in the lines it adds, because paths alone let an authorization change in a service provider skip Security.
- A router instead of tag automations. Paca’s
tag_addedtrigger fires on any tag edit while the tag is present, so tag-routed reviews ping-ponged between agents. An idempotent router derives who should hold each task from its stage and reassigns only when nobody is mid-session on it. - Self-healing with a circuit breaker. Bridges can drop their socket while the process stays alive, restarts leave “ghost” conversations running forever, and a slow server makes every agent look disconnected. The first version of self-heal mass-restarted eleven bridges and killed live work; now any failed read means take no action this run.
- Locked acceptance tests and an anti-gaming scan. QA writes failing tests from a story’s
scenarios before the build starts, and the gate rejects the story if those files change. The scan
blocks added
.skip/.onlyand suppression directives in the task’s own diff — calibrated by replaying it over already-reviewed history, where the first version blocked 22 of 85 good commits until it was narrowed to zero.
The result
The harness is 218 tests and the board runs unattended across parallel lanes, with an ops dashboard of SQL panels and a daily digest that reaches me by email. The most useful output, though, is the list of incidents, each turned into a rule:
- Success flags lie; read back. Paca reported an automation as activated while it stayed inactive, and accepted an agent’s system prompt while storing nothing. Every write is verified by reading it back, and every automation with a live probe.
- A failed read is not “empty”. An independent review found 18 bugs in the harness; the worst treated a timed-out read as “nothing there” and then wiped alerts or filed duplicate fixes.
- Cumulative cost is cumulative. Conversation cost is a running session total; summing it doubled the apparent cost per task.
- Busy is not productive. Two days of nonstop agent work once produced about 650 lines of product, because reviewers kept filing hardening follow-ups on a skeleton. The fix was to measure features shipped, restrict follow-ups to real defects, and put something visible on screen first.
The same idea runs through kantan: an agent’s report about its own work is a claim, and claims need a check that can’t be argued with.