The engine gateway routes tasks to engines, and three of those engines edit repositories: two cloud CLI agents and a local coding harness. Until this week they edited the real working tree, and the human review gate at the end of a run was therefore reviewing history: here is what already changed, hope you like it. You can reject a diff you are looking at. You cannot un-happen edits to the tree you were working in.
Now the editing engines get a photocopy. Each run cuts a git worktree from the target repo’s HEAD onto a run-named branch, the engine edits that copy, and the tollgate at the end shows the human one diff with three honest options: apply it to the real repo, discard it, or leave the worktree in place and go inspect it properly. The real tree does not change until a human chooses.
Why “just use worktrees” was still a design problem
The idea is one sentence. Shipping it was a list of edges, and the edges are where the governance actually lives.
A run must yield exactly one diff, even when it escalates. The gateway’s escalation chain means a single run can start on the local harness, get a decline, and continue on a cloud CLI agent. If each engine cut its own worktree, the human would face two half-diffs from one logical run. The isolation step’s first line is a reuse guard: a same-run escalation lands in the worktree the run already has. One run, one reviewable diff, no matter how many engines touched it.
Apply must be safer than the edit was. Approval applies the worktree’s changes to the real repo as a patch, checked with git’s own dry run first, and lands them as plain working-tree changes, no commits. On a conflict, the apply aborts cleanly: the real repo untouched, the worktree kept for manual recovery, the tollgate not set. A conflicted apply that half-lands is exactly the mess isolation exists to prevent, so the failure mode had to be “nothing happened” rather than “something happened, good luck”.
Copies leak unless something reclaims them. Worktrees and their branches accumulate: finished runs, rejected runs, runs whose ledger entry vanished. A prune mode reclaims worktrees belonging to terminal runs and ledger-less orphans while leaving live ones alone, so the isolation layer does not slowly turn into a junk drawer of stale copies.
The default has an off switch, and the off switch is the interesting part. Isolation is on by policy for the three editing engines, and a single environment variable turns it off per run. That is not a convenience hatch. It is load-bearing, and the first real convergence run proved it.
The carve-outs: when the photocopy is the wrong thing to look at
A worktree cut from HEAD contains exactly what HEAD contains. That sentence sounds like a tautology and is actually a trap with two teeth.
First: untracked files and environments. The gateway’s first converged end-to-end coding run (EG-026) ran with isolation deliberately off, because its test fixture was an untracked file and the target repo’s virtualenv lives outside git entirely. In a worktree, the fixture would simply not exist and the test stage would degrade silently. The run that finally worked would have been impossible under the default.
Second, and sharper: review tasks must never be isolated. A task whose job is “review the current changes in this repo” needs the working tree as it actually is, uncommitted mess included. Give it a pristine copy of HEAD and it will review stale code, return a confident verdict about a tree nobody is working on, and no error will fire anywhere. Isolation converts an editing risk into a reviewing lie.
So the policy is not “isolate everything”, it is “isolate the writers, never the readers, and know which is which”: isolation: worktree is set per engine, only the three repo-editing engines participate, and the carve-outs are named in the policy comments rather than remembered.
Proof it holds: the tests are the spec
The suite grew twenty tests that read like the design document: the escalation-reuses-worktree case, the conflict-abort case with the real repo asserted untouched, untracked-file and engine-made-commit visibility in the diff, approve-applies-and-prunes and reject-prunes-without-applying over a real pty, and orphan pruning that spares live runs. A live smoke with a stubbed cloud engine walked the whole loop: run, edited worktree, clean real repo, diff at the tollgate, approve, edits landed as uncommitted changes, worktree gone, ledger updated.
What next
The isolation layer moves the human gate from reviewing history to reviewing a proposal, which is what a gate is supposed to be. It has since carried its first real task: a run against a sibling lab went through a worktree, landed as a reviewed diff, and merged as that repo’s own pull request, with the gateway’s ledger holding the worktree trail. It also sharpens the next question: the tollgate now sees exactly what changed, and the remaining trust gap is whether the human looking at the diff has enough context to judge it.
What Next
human_confirmed Grew Up
Next in Governance Without Killing the Joy: the approval field on the other side of this diff started life as one gameable boolean, and what it took to make it a real gate.