---
title: The Work That Was Never Mine
author: Roger Fleig
date: 2026-06-26
canonical: https://rogerfleig.substack.com/p/the-work-that-was-never-mine
source: Substack
license: http://fleig.us/license/
---

## 1 — Where I started

If you are anything like I was when I started, you used a coding agent by prompting it. You typed what you wanted, read what came back, typed something else, and kept going. The effort stayed flat the whole way through — about as much thought at the end as at the start — and you trusted that the loop would land somewhere good.

That approach has a ceiling, and the ceiling is low. It works for a demo. It works for a script. It falls apart the moment the work is large enough that the model runs out of context before the work runs out of problems. A context window is finite, and as it fills the model loses its grip on what came first — compression keeps the gist of the early work while its resolution falls away. What that feels like from the outside is re-explaining the same design to an agent that gets a little more confused each time you do.

Worked this way, a coding agent makes a single engineer a little faster — a faster single thread, and not much more. The gain is real but modest, far short of the multiples people reach for when they talk about agents, and it sits at the low end of the automation scale I wrote about in [The Narrow Pipe](https://rogerfleig.substack.com/p/the-narrow-pipe). That is roughly where I was in October of 2025.

This piece is about where I ended up, and I am writing it for two reasons. One is that it might help you get more out of these tools. The other is more selfish. I want to learn from other people who are working out how to do this well — how they run their own process, what they are finding — because it will make mine better. If that is you, I would like to hear from you.

A quick note on words. When I say agent in this piece, I mean a coding agent — in practice Claude Code or Codex, which I use in much the same role, because for this kind of work I have not found much that separates them. I do not mean the other sense the word now carries, an autonomous agent running in production and acting on someone's behalf. The two get tangled easily, not least because I use coding agents to build production agents, so I want to be plain. Here, an agent is the tool I write code with.

What I have now is a method. It moved my own role away from typing code and toward designing the work and overseeing the agent that does it. None of the pieces are complicated. The discipline is in having them at all, and in keeping them when the temptation is to skip straight to the prompt.

## 2 — Design before code, and write it down

I start with a design, and the design is a markdown document. I iterate on it before any code exists. Depending on what I am building, that can take an afternoon or it can take days of back-and-forth with a strong frontier model, working out what I want, why I want it, and what the requirements actually are. The point of that time is to leave the conversation with the requirements sharp enough that the building agent does not have to guess.

One thing to watch for in that conversation is the model's sense of cost. In my experience, coding agents are often pessimistic estimators. They tend to assume that building almost anything takes a long time, and that pessimism leaks into the design, because a model that believes a component is expensive to build will steer away from it before the two of you have even discussed it. Ask one to design a system that needs a vector database and it may reach for whatever it reads as the cheapest thing to stand up, pruning the more capable option on a cost intuition it never surfaces. I think the intuition is usually wrong. These models learned from an enormous amount of human-written code, and from our estimates of how long code takes to write. They inherited that sense of time, and it stops holding once the building itself is this cheap. The estimate is a memory of how long this used to take a person.

For that reason I keep one directive in the file every one of my coding agents reads on startup — the AGENTS.md and CLAUDE.md in my home directory — and it amounts to one instruction, to disregard the cost of writing and building code. I am not claiming the work is free. I am removing a bias I do not want the model optimizing against, so that it picks a design on the merits instead of on an unspoken guess about how much effort I am willing to absorb. I picked this up from [Kun Chen](https://linktr.ee/kunchenguid), who [describes the bias](https://www.youtube.com/watch?v=iQyg-KypKAA) and keeps a rule like mine against it. He did not prove it and neither have I — it is a practice we both maintain because it has paid off, not a result either of us has measured.

Once the design is set, I treat it as fixed. It lives in the repo. If the design has to change — and it does — the change is not a quiet edit. It is recorded in a Design Change Record that lives in markdown next to the design. The agent does not get to redefine the goal on its own and move on as though the goal had always been that. When I come back a week later, the record tells me what changed and why.

This is the first place the work stops being a chat and starts being a project. The design is an artifact. The next agent reads it instead of pulling it back out of my head.

> *A note on how I do this now.* I no longer type most of what I give an agent. I speak it into a microphone and let dictation carry it in. The change sounds cosmetic and is not. Typing rations context, because typing is slow and I trim my own intent toward brevity as I go. Speaking removes that tax. I will say two or three paragraphs of background out loud that I would never have sat and typed, and that surplus context is often exactly what lets the model decide something well instead of guessing or coming back to me. And the cheaper it is to get my intent into the agent, the fewer times it has to stop and come back to ask me for it. Like the cost directive above, this habit came from Kun Chen, who points to a [Stanford study](https://arxiv.org/abs/1608.07323) that clocked speech at roughly three times the speed of typing.

## 3 — The handoff file

The single most useful artifact I keep is the agent handoff. Before any agent ends a working session, it writes down what it did, what it learned, and what should happen next. It is a plain markdown file, and I keep it in the repo on purpose. Partly that is traceability. I am interested in how these agents actually work, and versioning their handoffs alongside the code means I can go back later and read what they were thinking. Partly it is portability. Once the file is committed and pushed, the work is no longer tied to one machine — I can stop on my laptop, pick up somewhere else, and the agent that resumes has everything the last one left behind. A handoff kept only on local disk would give me neither, and it would leave me no good place to manage the file as it grows.

I did not build the handoff for elegance. I built it because I was burning through tokens. I would work with one harness for the first hour and a half, exhaust its budget, and need to continue in another. The handoff is what let me hand the work from Claude Code to Codex and back without losing the thread, because it does not depend on any one product's memory system. Proprietary memory ties the work to the tool. A markdown file in the repo belongs to the project.

Once that file existed, it did something I had not planned for. It made the agent restartable. I could stop an agent on purpose, start a fresh one, point it at the handoff, and have it continue with almost no prompting from me. The handoff carries the state, so the next agent can pick up the work.

## 4 — Keeping the memory small

A handoff file solves one problem and creates another. The moment an agent is writing down what it learned at the end of every session, the file starts to grow, and a memory that grows without bound stops doing its job. The whole point of the handoff is to save context. A handoff that has swollen to thousands of lines spends the context it was meant to save.

On one long-running project — a Roblox build that ran forty or fifty agent turns — this got away from me, so I put rules in the repo to hold it back. The handoff could not exceed a line count I picked more or less arbitrarily, a few hundred lines. Before a turn ended, the agent had to scan the file and compress it. Older entries collapse to a single line, and anything that has become a standing pattern rather than a one-time note gets lifted out of the handoff and into AGENTS.md, where the rules that should hold for every session live. A pattern that proves durable enough can be promoted again, out of AGENTS.md and into a skill. That last step is less developed than I want it to be, but the direction is right — memory moving up a hierarchy as it earns permanence, from the session log to the project's standing rules and eventually to a reusable skill.

That framing, guidance earning its way up as it proves durable, is real, but it is not the whole reason to make the last move. The other reason is context cost. Everything in AGENTS.md is resident; it loads into the model's context on every session, whether the task at hand needs it or not. A skill is cheaper. Only a short description of the skill stays resident, enough for the model to know when to reach for it, and the instructions themselves load only when it does. So pushing a section of AGENTS.md down into a skill is its own kind of compression — it takes guidance that used to sit in context all the time and makes it present only when it is used. The hierarchy is really a matter of fit. The handoff holds what this session learned, AGENTS.md holds what every session needs, and a skill holds what only some sessions need.

None of this happens on its own. Left to itself an agent will overfill these files, because writing more down feels like diligence, and AGENTS.md bloats the same way the handoff does. The repo rules are backpressure against that. I have not gone as far as enforcing the length of AGENTS.md with a test, the way I enforce the other things that have to hold, but I think that check would be reasonable to build. A memory file that fails CI for being too long sounds strange only until you account for what the bloat is already costing you.

## 5 — Checks the agent can break against

Early in a project — not always on the first day, but very soon after — I add the boring infrastructure. Linters, formatters, type checking where the language allows it. I am not doing this for style. I am doing it to give the model something deterministic to fail against.

When an agent writes bad code and there is a check waiting for it, the check catches the mistake and the agent fixes it without coming to me. The feedback loop closes inside the agent. Without those checks the agent either ships the mistake or gets stuck and stops, and either way the problem lands on my desk. The hygiene tooling turns a class of failures into something the agent resolves on its own.

Tests are the version of this I lean on hardest, and I am deliberate about when they get written. I have the agent write them at the edge of every milestone, so that finishing a milestone means leaving behind a suite that proves the work rather than a claim that it happened. There is a failure mode to watch for. Tests written milestone by milestone tend to calcify around the boundaries they were written at, encoding the work as it stood at the moment it was built rather than the system as it settled. On a long project I would reach milestone twelve and watch an agent report that the milestone six tests were failing — a statement that made no sense about the system and perfect sense about how the tests had been written. So when a milestone closes, it is worth a pass back over the older tests to bring them up to what the system has become, instead of leaving each one frozen at its boundary.

This is also where I learned a distinction I lean on constantly. A rule written in an `AGENTS.md` file is soft. The model reads it, generally respects it, but routes around it the moment it decides to, and nothing stops it. That is good enough for productivity but not for anything that has to hold. A check in CI is a hard constraint. It is not hard to write — most checks are trivial — but with branch protection on, the check has to pass before anything merges, and the model's opinion does not enter into it. The rule is enforced by the machine, not by the model's goodwill. When something has to be true, it goes in a tool, not in a prompt.

That sentence is the short version of an argument I made at length in *[The Case for Architectural Linting](https://rogerfleig.substack.com/p/the-case-for-architectural-linting)*. There the rules in question are structural — dependency direction, module boundaries, the conventions a senior engineer used to hand down one review comment at a time — and the point is that once an agent is writing the code, those rules have to move out of memory and into checks a machine can enforce, because an agent can forget or trade away anything that lives only in guidance. The hygiene I add at the start of a project is the same move at the opposite end of the scale. Linters and type checks enforce the smallest, dullest rules there are, and I still refuse to leave even those to the model's goodwill — for the same reason the essay gives about the large ones.

One qualification, because the tool-not-prompt rule can be read too strictly. Putting something in a tool does not mean keeping it out of the prompt — I usually do both, for two different reasons. The check is the guarantee. It is what makes the rule hold on the day the agent would otherwise break it. The same rule written into AGENTS.md is what keeps the agent from breaking it in the first place, because an agent that already knows the rule tends to follow it the first time, instead of writing the violation, tripping the check, and fixing it on the rebound. That line in AGENTS.md is a kind of repo-wide memory, and the check is the backstop for the days it does not hold. But that memory stays useful only while it stays small. What belongs in AGENTS.md is the high-level expectation — run the linter, it has to pass — not the linter's rulebook, which belongs in the tool and costs no context until it runs.

## 6 — Do not make a model do a script's job

One of the worst ways I ever used a coding agent was for deployment. For a long time my production system was updated by an agent in YOLO mode acting as my hands. I would point a coding agent at the VPS and let it copy the build over, start the service, and poll until the thing reported healthy. It worked often enough that I kept doing it, and it was the wrong way to spend an agent. None of that work needed a model. Copying a file, restarting a process, and waiting on a health check are deterministic steps with one correct outcome, and I was paying a frontier model to reason its way through them every time — burning tokens, filling its context with deployment output, and now and then watching it get a step out of order because it was improvising a procedure instead of running one.

Moving the production environment into Ansible put that work where it belonged. The deploy is a playbook now. The agent does not reenact it, it invokes it, and the steps run the same way every time whether or not a model is in the room. The system got cheaper to operate and simpler to hold in my head, because the description of how it is provisioned lives in one place I can read, instead of in the working memory of whichever agent happened to touch it last.

Integration testing was the same problem again. I would sometimes tell an agent to "integration test this," and the instruction had no teeth. There was no schema behind it and no fixed place for the test to run, nothing for the words to mean beyond whatever the agent decided to improvise in the moment. So I was once more using generative reasoning to stand in for a procedure that did not exist. Building the staging environment — the same Ansible work, pointed at local containers instead of the production VPS — gave those words something to land on. Now I can say "integration test it end-to-end in staging before you show me the PR," and that is not a problem for the agent to solve. The agent knows where staging is and what end-to-end means there, because the environment is what defines them.

This is the deterministic-procedure-against-generative-agency line from *[If You Give an Agent a Token](https://fleig.us/writing/if-you-give-an-agent-a-token/)*, seen from the operations side. The deeper version of the argument lives in that essay. The working version is smaller and very practical. Before I hand an instruction to a model, it is worth asking whether the instruction has somewhere to land. If it does not, the model will invent one, and I will pay for the invention every time.

## 7 — Bounded sprints and clean restarts

I run the agent in short sprints. Most are five to thirty minutes. Some run longer when the work needs a lot of build time, or when the agent has to look at the product of its own work — render a piece of UX, then read back what it made — which, like an integration test, adds real time to every loop. The length is not the point. The boundary is the point. Each sprint ends with the agent updating the handoff, committing it, and exiting before its context degrades, leaving a clean worktree behind.

Then I restart it. A fresh agent reads the handoff and picks up the work. I pay close attention to when an agent starts to wear down, because an agent late in its context is a worse engineer than the same agent was at the start — it has lost detail, it summarizes where it used to reason, and it makes mistakes it would not have made an hour earlier. Knowing when that has happened is more art than instrument. I cannot really see an agent's context window — whether a harness reports a number at all is hit or miss — so I go by the agent's behavior and how long it has been running, and when I am unsure I ask the agent itself whether it would do better to restart. It usually has an opinion, though I cannot always tell whether the opinion is real. Restarting before the decline sets in is cheaper than cleaning up after it.

## 8 — Build the prototype, then live with it

For anything I can actually experience, I build a prototype first and I use it. When I was building an orchestration tool of my own, I stood up a quick version and then kept a coding agent parked on it the whole time I ran it. Every failure I watched the orchestrator hit, I pasted over to that parked agent. The paste was only a starting point — the agent could go into the repo, read the logs, and reconstruct exactly what had happened, then write the lesson into a running file. Because it worked from the real evidence rather than my secondhand account, the lessons it recorded were specific and accurate. Now and then we fixed something and reloaded; most of the time we just recorded the lesson and kept going. That file became the single most important input to the real design — it held things about how the system actually behaved that I could never have guessed at the whiteboard. It is still there, committed, and the design that came out of it traces its decisions back to it, lesson by lesson.

I can work this way now because building has become cheap — cheap enough that a prototype is no longer a commitment. The word used to carry a debt — it implied that someday I would have to productionize the thing, with all the work that entailed, so a throwaway version never felt entirely throwaway. That debt is mostly gone. When code is this cheap to produce, I can stand up a proof of concept, let it hold together just well enough to carry me for a while, take the lessons it hands me, and discard it without ceremony. Learning this way has never been more accessible, because the price of the experiment has never been lower. That orchestration prototype was not a small thing, either. It grew big, probably overbuilt, and I never read a line of its code, because I never meant to ship any of it. The only thing I wanted from it was to learn what building the real thing would take.

That prototype did more than teach me the design. It also showed me where the single-agent method stopped being enough.

## 9 — Where the method broke

The method I have described works inside one project. For me each project lived in its own git repository, so I will keep talking in terms of repositories, but the boundary is the project, not the repo — in a monorepo it is the same line drawn a different way. It broke the first time a single effort spanned several projects at once.

On the last platform I was building, I had four or five repositories in play, each depending on the others. My instinct was the obvious one, to run the same method several times over — a coding agent to a repository, each in its own terminal. These agents were not blind to one another. I ran them in YOLO mode, which gave each broad read access to my machine and my git, so any one of them could look into a sibling repository when it needed to. What kept them in separate terminals was collision, not blindness — two agents turned loose on the same repository trip over each other — so each drove its own. That division kept them out of each other's way. It did not handle the coordination. When an agent in one repository made a decision the others needed, no agent owned carrying it across. I did. I had made myself the bus.

The tell was the copy and paste. An agent would produce something another agent needed, and I would copy it out of one window and paste it into another, unchanged. Or one agent would ask me to do a small thing only I could do, and the small thing was a chore a script should have owned. The moment I am passing something from one agent to another without changing it, I am doing a tool's job by hand. Carrying a decision from one place to another, untouched, is exactly the work a machine does perfectly and a human does slowly and with errors.

While I was building [Autotrack](https://github.com/grubbyhacker/autotrack), the project I wrote up in *[Agents Building Agents](https://fleig.us/writing/autotrack/)*, my coding agent kept asking me to copy the rest of a test log out of one console and paste it back so it could keep working, and I refused. The moment I became the data path between the agent and its tools, the point of having the agent would collapse. Carrying a decision from one repository into another was that same reflex at a larger scale. Being the wire is not oversight. It is the job I had already decided, on a different project, not to take.

For independent tasks across repositories, running several agents in parallel still works as well as it ever did. The break only appears when the work itself is shared — when the repositories have to move together, in order, and someone has to hold the whole picture. I was holding it in my head and relaying it by hand, and that does not scale past a couple of agents.

## 10 — One orchestrator, many workers

The fix was to stop being the bus and put a tool in my place. I moved up a level. Now I work with one orchestrating agent, and it delegates tasks to as many worker agents as the job needs. Each worker is a frontier model in its own right, working in its own isolated git worktree. The workers are ephemeral. They take a bounded task, carry it to a clean stopping point, and exit.

The orchestrator holds the intent and the sequencing. It does not fill its context with build output and debugging the way a working agent does, so it stays clear-headed across a long effort. The workers absorb that cost, each one spending its context on a single task and then going away. I no longer manage the context windows of the agents doing the building, because every system I described above is still running underneath — the design, the handoff, the hygiene, the bounded loops. The orchestrator simply sits on top of all of it.

That is the inflection. Before, I corralled several agents and I was the only thing that knew the whole picture. After, I talk to one agent, and it talks to the rest. I did not get faster at any single task. I stopped doing the part of the work that was never mine to do, and the part I stopped doing was the relay.

## 11 — How Codex Fleet works

[Codex Fleet](https://github.com/grubbyhacker/codex-fleet) is the tool I built to hold that shape. It is not a smarter coding agent but a coordination layer around ordinary coding agents, so one orchestrator can start them, observe them, and collect their results without making me the message bus.

A fair question is why I built a tool at all, when Claude Code and Codex both already spawn sub-agents. I leaned on that first, and almost nothing Codex Fleet does is impossible to coax out of those harnesses on a good run. The trouble is that the parallelism is the model's to grant, not the tool's to guarantee. Sometimes the harness delegates, sometimes it decides against it and runs everything itself, and sometimes it delegates a task and then sits and waits on it, which buys nothing — and when a worker fails for a small reason, it tends to give up and serialize the rest. From one run to the next I could not predict how much would actually run in parallel. I wanted delegation, isolation, waiting, and model choice to be explicit properties of a tool, not behavior a harness might or might not choose that day.

In use, the flow is simple. I talk to one orchestrator. The orchestrator calls Codex Fleet over MCP and delegates a bounded task to a worker. Codex Fleet starts that worker, gives repo work its own isolated git worktree, tracks the task, and keeps the task state outside the orchestrator's chat session. The worker does the same kind of work any good coding agent does: read the repo, make a plan, edit files, run checks, revise, and leave behind evidence. When it finishes, the orchestrator reads the result, inspects the evidence that counts, and decides what should happen next.

Early orchestrators improvised their own waiting — I once caught one running a literal `sleep 44 && echo done` in a shell — and that smell was the tell that waiting should be something the orchestrator can ask for rather than fake. So waiting is one of the calls Codex Fleet exposes over MCP. The orchestrator can hand out as many tasks as it wants, to as many workers, and wait on all of them at once; the moment any one changes state, it wakes up. No improvised timers, no polling in the dark.


A dashboard lets me watch what every worker is doing without interrupting any of them. The workers themselves run with broad access on my machine — I have not sandboxed them yet — which is exactly why I want their lifecycle and state owned by a durable service, not by whichever chat session happened to start them. What this buys me is not mainly speed at any one task but concurrency with the relay removed. Each worker can spend its context on one bounded task and then disappear. The orchestrator can spend its context on the larger effort as a whole. I can run more than one orchestrator when the work itself separates cleanly. I am doing more work than I could before, and less of the work that a machine should have been doing all along.

I made this argument once already, about coding agents, in *[The Parallelism Thesis](https://rogerfleig.substack.com/p/the-parallelism-thesis)*. The point then was that their real gain is concurrency rather than speed, and that the engineers who struggle most to take it are the ones who cannot let go of the keyboard. I was making a smaller version of that mistake here, holding the keyboard at the level of coordination long after the work had outgrown a single thread. Putting an orchestrator in that seat is what finally let me stop.

## 12 — What the fleet stands on

It is easy, once the fleet is the part that looks new, to treat everything before it as a phase I grew out of. The opposite is true. The single-repo discipline is what keeps a fleet from becoming a crowd of agents drifting in parallel, and a worker drifting at the end of a delegated task is harder to catch than one drifting in front of me. Each worker is only well-behaved because the same practices are running inside it that I worked out one agent at a time. The design anchors it, so it builds toward a goal it cannot quietly redefine. The handoff makes it restartable, so the orchestrator can stop it and start a fresh one without losing the thread. The hygiene makes it fail predictably on its own bad decisions, so it corrects them before they travel. Orchestration did not replace any of that; it multiplied it, and multiplying something unsound only gives you more of it, faster.

The clearest proof of all this is a repository where I skipped the foundation entirely and watched the drift arrive on schedule. In June 2026, I published an article to [Thoughts](https://fleig.us/), a small static site I keep in one public repository that builds and deploys to GitHub Pages. I added the Thoughts repo to the Codex Fleet config and let the orchestrator run the publish. I gave the orchestrator a high-level instruction to publish the piece, get the front matter right, and follow the practices and layout of the two articles already there. What I did not do was any of the groundwork I have been describing. The repo had an old CLAUDE.md, no AGENTS.md for Codex to read, and no skills to find. Those norms lived in my head and nowhere a machine could see them.

So the agents drifted, exactly where I had left the path undefended. One of my standing rules for that repo is the classic distinction between static source and generated content. Static source belongs in the tree; a PDF is generated content, so it never does. The site is built with Hugo, and each article's PDF is rendered from it through a chain of Pandoc and LaTeX heavy enough that I deliberately kept it out of pre-submit CI — the build runs after the merge and before deploy, so the PDFs are produced fresh every time, the way build output should be. Somehow the fleet agents generated them locally and committed them, something I had never seen an agent do and had never thought to defend against. With no check to stop it and no close review from me, it slipped in, and I did not notice for a release or two — until an unrelated change put PDFs in front of me in a PR from one of my own agents. When I asked the agent what it was doing, the answer was that an earlier agent had already done it, so this one was following the pattern it found. The mistake had become the example the next worker copied. The fix was not to ask the agents to think harder. I had to write down the repo norms they were supposed to follow, and I had to add a check that rejected committed PDFs outright.

I should also be precise about what the workers are, because "orchestrator and workers" can suggest one mind parceling chores out to simpler ones, and that is not the design. A worker is a frontier model, and the orchestrator delegates real work to it, including work that takes judgment. A design task is fair game — the orchestrator can hand one to a worker and let it work the design out, and the worker is as capable of that as any frontier model brought to the same problem. The architecture also lets the orchestrator send a narrow, well-specified task to a cheaper model to save tokens, though so far that is more a capability than a habit. I have not confirmed how often an orchestrator reaches for it, and the guidance for when it should, probably a skill, is work I have not written yet.

When the work fits inside one repository, I still often work with one agent in that repository. For a local refactor or a single feature, I open Codex or Claude Code and talk to it directly, using the same design, handoff, checks, and restart discipline I have been describing. That is not a lesser version of the workflow but the right tool for that kind of problem. The orchestrator earns its place when someone has to hold several moving pieces at once, and that is most obvious when the work spans repositories. It may also be true inside one large repository, when a change breaks into several broad, mostly independent tracks that can move in parallel. I have tried that a few times and have not made it work as cleanly as I want yet, but I am not ready to rule it out. It may need better support from Codex Fleet before it becomes a reliable pattern. Part of what I have actually learned is where each tool belongs — sometimes it is a single model in a single repository, sometimes it is an orchestrator driving a fleet — and the real skill is recognizing which problem I am in before I choose.

## 13 — Closing

None of this began as a theory about agents. It accumulated one fix at a time, and every fix answered the same problem — a place where I was doing work the tools should have been doing. The pattern is not that I do less, but that my attention keeps moving toward the part that needs me while the rest moves down to where it belongs. The question underneath is the same every time. What here actually needs my judgment, and what am I doing only because nothing has been built yet to do it for me? Every time the second question has an answer, there is a tool waiting to be written.
