What did the agent actually change?
How an agent platform computes a pull request from a registered base and a scoped view of the workspace, and keeps absent, unverifiable and unknown outcomes distinct.

On this page
Turning an agent’s work into a pull request sounds like the least interesting part of the job. On a laptop it is: git add, git commit, git push, and on to the next thing. On the distributed agent platform I was working on in the summer of 2026, that step first had to answer a harder question, which was what the agent had changed, when several parts of the system could give different answers. The authoritative workspace held the replicated state. The sandbox mount gave the agent’s tools their view of that state and could lag behind it. The gateway kept a local file index for its clients, which knew only about writes that had come through its own API. And Git and the network lived in the sandbox, while the agent process had no git of its own and saw the workspace at a different path. Each of those arrangements made sense by itself. Together they meant that a perfectly ordinary command could give a confident and wrong answer.
The replicated storage and the sandbox runtime were foundations I built on. I developed the integration layer with agents under my direction.
A miss is a question
The first symptom was a model rewriting files that already existed. A tool that looked on the agent-side mount for a file just written in the sandbox could miss it because replication hadn’t arrived yet, and it would respond helpfully: “Use CREATE to write a new file.” The model did as it was told, turning temporary lag into unnecessary rewrites of whole files.
The layer I built treated a miss as a question rather than an answer. It invoked the storage layer’s synchronization barrier, then probed the agent-side path again, and on another miss it backed off from 40 up to 400 milliseconds until a five-second deadline, long enough for the lag I had observed and short enough that a file that really was missing still failed fast for the model. The result was one of three verdicts. present meant the path had been seen on the mount, absent meant a clean miss had survived all of that, and unverifiable meant the barrier or the probe itself had failed, so the caller must not infer anything. The retry logic beneath it was just as particular: EAGAIN, EBUSY, EINTR, ENOTCONN, ETIMEDOUT, ESTALE and EIO counted as transient, and ENOENT deliberately did not. On a plain local directory with no replication behind it, a clean miss was reported as absent straight away, because waiting out the deadline there would have been a stall on every missing path.
The gateway had the opposite problem, a file that was there and a system that said it wasn’t. Its index saw only writes made through its own API, so a repository the agent had cloned straight into its workspace existed on disk and returned a 404 to the person trying to open it. The fallback I added ran on an index miss. It resolved the workspace only through threads the caller was allowed to access, checking tenant and owner, took at most three candidates, newest first, and read the real runtime file. The decode was strict UTF-8, so a binary file came back as metadata instead of invalid text. An inaccessible workspace stayed an indistinguishable 404, which meant a stranger learned nothing about what existed, and also meant that this 404 couldn’t say which of the two had happened.
Let Git say what changed
Once you accept that none of these views can simply be asked, the shape of the export follows. The pipeline didn’t ask the agent what it had changed, and it didn’t trust any Git state lying around in the workspace. It cloned the repository into scratch space and reset hard to the base revision registered for the workspace, so the clone matched exactly what the agent had started from. It laid the workspace’s tree for that repository on top, skipping support directories and any stray .git, ran git rm on tracked files that were absent from the workspace, ran git add -A, and took git status --porcelain as the authoritative set of additions, modifications and deletions. Then it committed, pushed, opened or updated the pull request through GitHub’s REST API and deleted the clone.
The value of this is that the change set is computed rather than remembered. If the agent believes it touched three files and Git sees four, the answer is four, and a log that was truncated on its way back can’t shrink it.
The design had a sharp edge, and it was absence again. In an overlay, a file missing from the workspace is a deletion, so a view with holes in it would become a commit that removes files. The pipeline gained a mass-deletion breaker that stopped when proposed deletions exceeded the larger of 50 files or 20 percent of the tracked tree. Branch history raised the same problem. --force-with-lease is only as good as the expected tip you hand it, and the pipeline’s expected tip came from the clone it had just made, so the lease protected against a push during the pipeline’s short life and against nothing older. A second breaker checked the branch against the last tip the pipeline knew it had pushed.
A repository can also contain more or less anything, and ordinary tree-walking assumes it won’t. The walk was NUL-delimited so a name containing a newline remained one path. Symlink resolution stopped at 40 hops. node_modules was pruned from the walk to avoid traversing dependency symlinks, and tracked files inside it were copied back using git ls-files. A directory symlink was copied as a symlink, which pnpm layouts need, and the pipeline’s own scratch binaries were filtered out.
When the work succeeds and the answer dies
The strangest failures were the ones where everything worked and only the result was lost. The pipeline’s result came back over a streamed exec capture, and both execution channels could lose that stream without a real exit code. On one, the runtime’s session cleanup could kill a long-running tmux window mid-pipeline. On the other, a transport kill during the command surfaced the same way. The signature was an exit status of -1, an empty stderr and stdout cut off at a fixed offset. No POSIX shell can produce -1, and this runner reported command timeouts as 124, so that signature identified a lost execution result rather than a shell exit status. Meanwhile the clone, commit and push might have finished perfectly well.
So the result had to stop depending on the stream. The wrapper captured stdout, stderr and the exit code in receipt files, and the pipeline wrote its terminal result atomically after the push. A recorded exit code meant the script had finished, so the wrapper recovered that result instead of running it again. Without a readable receipt, the wrapper made one retry in a fresh session, using a new clone and the same lease and branch-history checks. Those checks limited what the retry could overwrite, but a missing receipt did not establish that no push had happened: the process could die after the remote accepted the push and before the receipt was written. If neither attempt yielded a recoverable result, the caller got a typed capture error that told it to check the remote branch before retrying. Where the push outcome remained unknown, the agent was told in so many words: “do not report this as a push failure.”
The other export
People also want to download a workspace, and there the question of what counts as their work has a different answer. A full archive bundled the user’s own work with .rustup, .cargo and node_modules, making the download needlessly large for someone who just wanted the project. So I added a project scope that followed the same visibility rules as the file explorer and pruned during the walk rather than filtering afterward, with the full archive still available when asked for explicitly. Delivery used chunk, complete and error events that each echoed the scope, so an interrupted stream couldn’t pass for a finished archive.
An export started from a known base, overlaid a scoped view and let Git say what changed, with breakers for the cases where that answer looked wrong. When the system didn’t know something, it kept that distinct from knowing the opposite, so a failed probe stayed unverifiable and a push whose result was lost stayed an unknown outcome. The one deliberate exception was the inaccessible workspace, which looked like a missing one because telling them apart would tell a stranger too much. Several of the defects above came from some part of the system turning “I can’t see it” into “it isn’t there,” and that is the first thing I would look for in any distributed tool an agent depends on.
Chris Chabot, November 2026