Why a project worker needs to own the whole job

Why splitting implementation, review and follow-up into disconnected turns loses obligations, what belongs to the coordinator, the worker and its helpers, and the leases, anchored findings and content-bound checks that let ownership survive on a shared tree.

November 20269 min readAgent systemsOrchestrationCode reviewConcurrencyDirecting an agent fleet

A long stained deep ultramarine stroke runs down the left, with yellow ochre, sage and oxblood strokes of varied length thrown rightward into open chalk.

On 20 October I was reading through notes on what our project workers were costing, and they put a fair share of the prompt-cache misses down to workers waiting for reviews. That surprised me, because workers weren’t supposed to wait for reviews. The whole system was built around ordinary threads, with projects added afterwards, and in an ordinary thread a reviewing agent reads each change asynchronously as the coding agent makes it and sends its findings back into the running conversation: you forgot this, that part needs fixing. You tell a thread to go and build an Eiffel Tower and it builds an Eiffel Tower, handing pieces to sub-agents as it sees fit and taking review comments as it goes. I had never asked for a worker that does some work, goes idle and waits an hour for someone else to pick it up.

The audit that followed showed the project system had departed from that design a month earlier, on 24 September, through a commit with a perfectly sensible rationale: project review cycles cover a whole deliverable, so conserve them until the implementation is ready. The consequences then accumulated. Automatic review during the work was off in every project runtime. Workers got three review rounds instead of eight, an early review was skipped whenever two or fewer rounds remained, and open review scopes became a debt owed to the next turn, with an instruction to finish up and not wait. At the final barrier a serious finding got one remediation prompt and one rereview, and whatever was left was recorded on the turn as a caveat. The worker’s brief asked it for one final report after its last tool call.

So the rest of the job moved out of the worker and into the gateway, which drove it with follow-up turns: a supervisor that started a turn when findings remained or a review had been stuck for 45 minutes, a ladder of approaches running from rework through a different model, a smaller task and a fresh start to asking for an answer, and a continuation that fired after 30 idle minutes, up to three times. Each of these was a reasonable answer to the problem in front of it, and the deferred review even had passing tests, which asserted precisely the behaviour I was objecting to. What they added up to was a queue. The agent that held all the context about a change stopped working on it, its prompt cache, which lasts an hour at best, went cold, and the obligation to finish passed to machinery that could only see the job from outside. That is where the cost of splitting implementation, review and follow-up into disconnected turns shows up, at each boundary where the job briefly has no owner.

The definition I wanted back is easy to state. A worker owns its item until the item is delivered: its change is in the tree, every file of it has been reviewed, and its checks have passed against that content. It doesn’t end its turn and leave findings or checks for another turn, the gateway or the coordinator to pick up. Recovery machinery stays, for a worker that has actually died or gone silent, and continuing is otherwise the worker’s own job.

Three levels

A little later the same mistake appeared from the other end. Coordinators were treating workers like sub-agents, giving each one a tiny task and then ending it, which threw away its context, tied up the shared sandboxes and multiplied the review and landing paperwork. So I wrote out how work should be organised, and that text went into the coordinator’s prompt. There are three levels. The coordinator talks with the person, owns the outcome and directs threads; it decides what work exists and who does it, and does none of it itself. Threads are the workers: full, long-running agents with every tool and their own memory and compaction. A thread gets a whole area of work with its goals and a checklist, related goals go to the same thread in one brief, and it carries on across follow-ups instead of being ended and replaced, so that a thread that has finished can still take more with what it already knows. Sub-agents are short-lived helpers a thread starts for itself, for parallel searches, checks or side tasks, and the coordinator never manages them.

The part that takes judgement is how many workers, and the size of the list doesn’t decide it. A long to-do list on the core app is one worker. A marketing site and the code are two. A front end and a back-end database are two, while a back-end database and back-end memory usage are one. Designing a poster and adding some database fields is two, whereas adding a field and changing an expanding buffer to a bounded ring buffer is one worker doing both, because it is the same source. A second worker is for separate tasks that don’t overlap, and two hundred tickets are not two hundred workers.

This is a rule for our circumstances, in which every worker in a project shares one tree and a few scarce sandboxes, and each carries a large context that is expensive to rebuild: one worker in that audit made about 4,000 model calls at an average of 444,000 cached tokens each. I wouldn’t extend it into a general claim that bigger tasks beat smaller ones or that more agents beat fewer. What we have is specific evidence about overlap. During an overnight port, twelve workers running long tests at the same time spent about 80% of their effort on re-testing and resource contention and produced about two changes an hour. When each worker instead owned one recording, kept a one-line log per fix, re-ran only the step that differed and landed without waiting, the same work produced about 20 changes in 10 minutes. Several things changed at once there, so I wouldn’t assign that gain to ownership alone, but one recording per worker is what removed most of the need to coordinate.

Ownership on a shared tree

Long-lived workers hold more for longer, which makes collisions more expensive, and you can’t get out of that by isolating them. We tried. Full-copy worktrees gave us fourteen 490 MB copies that killed a sandbox’s link to the workspace store, took the coordinator’s process to 26 GB and required a host reboot. A clone per worker gave us 92 clones in a day, each lost with its sandbox and invisible to the other workers and to review. A sandbox per worker let one project hold 9 of its team’s 16 and starve the rest. So every worker in a project writes to one tree, deliberately, and collisions have to be solved by collaboration.

In late October we reviewed every message and platform log from about a day and a half of that arrangement: two dozen threads, about 150 sub-agents, some 43,000 messages. Workers and sub-agents were overwriting each other’s edits without knowing. A sub-agent published the shared copy of a file that contained its parent’s unfinished edit, and the main branch stopped typechecking. A parent’s repository update rewrote files under its running children. Units were ported or fixed twice because nothing recorded who had claimed what. Two of the five distinct critical problems in that review came down to the same thing: writes that could not be attributed to the right owner.

Part of the trouble was that claims belonged to the wrong thing. A claim on a file lasted until the turn ended, lived only in memory and vanished when a worker was parked, suspended or restarted, which is exactly wrong for a worker that is supposed to persist. In the redesign that followed, a lease belongs to the work item and is recorded outside the tree, covering the item’s declared area and every path it writes. A sub-agent works under its worker’s lease and may take a sub-range of it, and the host keeps siblings out of each other’s sub-ranges just as it keeps workers out of each other’s areas. A refused write says who holds the path and what they are doing, and offers three answers: wait in a queue with check-ins, ask the holder, or record that you are working around it. A lease that outlives its holder’s attention would be a permanent lock, so when the holder makes no progress on the path and two fresh readings say it is idle, the first waiter takes it over and both the holder and the coordinator are told, while a long build or model call counts as activity and keeps its lease. Publishing then takes the worker’s own hunks from the ledger of who wrote what and names anyone else’s, which is attribution rather than isolation.

Review has to work on the same terms. I have written elsewhere about what an approval means when the code keeps moving under the reviewer. On a shared tree a reviewer also has to read at a recorded position, so that it doesn’t see a sibling’s half-finished edits, and its automatic diff covers only the worker’s own change set. A finding is identified by its path, its kind and an anchor, the position of the version the reviewer read plus the line span it named, and its wording isn’t part of the key, because the previous key treated every rewording as a new objection. That identity is what makes the rule I set on 20 October workable: a finding the worker disputes or can’t fix gets two attempts, after which the work continues to delivery with the finding recorded as unresolved, along with the worker’s reasoning. It counts as delivered work and as unapproved review, both visible to the coordinator and the person, and it comes up again only if the content at its anchor changes. Without that, a worker that owns its job could be held up for ever by a reviewer it disagrees with, or the disagreement could quietly vanish.

Checks are claims about content as well. Recording a check with an exit code and a number from the worker’s own sequence of writes leaves you guessing whether it still holds after someone else’s edit, so in the design it records the tree position and input digests it ran against, which makes “the tests pass” something the host can verify. Results keyed by those inputs also let six workers asking for the same suite share one run. A helper’s result goes through a similar contract, so a reviewing sub-agent can’t return “done” having covered only part of what it was asked.

All of this machinery has a failure mode of its own, because agents take to coordination readily. A change prompts messages to eight threads, one of those threads asks about a file, that prompts nine more, and everyone re-runs the suite to be sure. So the design carries a rule: a unit of coordination is spent only when it unblocks a change that can’t otherwise proceed. Ownership removes most of the need, since work cut into units that don’t overlap has nothing to negotiate. Facts go into the shared record instead of being announced, a message goes to one recipient and only when the sender is blocked, and a message doesn’t license further messages.

How far it got

The shared-tree design went in package by package, with a reviewer on a different model going through each package and its findings then being fixed, and not every piece got equally far. In live use a lease was taken over after 15 idle minutes and leases were released at turn end, with no legitimate work refused. The same period showed attribution going wrong one level up. One lane’s whole-package typecheck failed on another lane’s unlanded, leased edit, and the attention observer twice proposed that to the person running the project as a defect found outside the task. The fix attributes a failure in a leased file to the lane holding it.

The organising rule has a test of its own, which gives the coordinator requests and counts the workers it starts. Poster plus database fields gave two workers and field plus ring buffer gave one, three runs each, and all six came out right. That shows a coordinator can follow the rule on cases like these, and it doesn’t settle decomposition. The test’s next step was to continue a finished worker with a follow-up, and the small worker it meant to continue was still working when its ten minutes ran out, so that step never ran. That is the step that matters most to this argument: handing a finished worker more work on its own thread, with what it already knows, is the part this test left unproved.

Chris Chabot, November 2026