← writing
2026-07-06Agents

An operator's loop for running coding agents unattended

I built forge over about five weeks of free time: a harness for running coding agents unattended. You hand it a spec, walk away, and come back to a draft PR that a second model has already reviewed. It's roughly 24k lines of TypeScript on Bun, still pre-release, and no longer just mine: a few people at work use it now, and the PR-review piece has been pulled out into work's own tooling.

The thing I keep coming back to is that none of the individual pieces are clever. A git worktree is just git. "Two critics" is two model calls. A synthesizer merges text. What makes the system useful is the composition of those boring pieces into a single loop you only touch once. This post walks through how the loop fits together, and the one place it is still weak.

What actually bites you when an agent runs unattended

Two failure modes cause nearly all of the pain, and neither of them is the model writing bad code.

The first is the vague spec. A vague spec doesn't fail loudly: the agent builds the wrong thing, correctly, and you don't find out until you're already reading the diff. You can't lint your way out of bad requirements.

The second is trusting the result. An agent runs, declares victory, and opens a PR praising its own change. If the same agent that wrote the change also decides the change is good, you haven't removed the review work; you've moved it onto yourself, after the fact, which defeats the reason for walking away in the first place. It's the same separation-of-duties argument auditors make: the person who does the work shouldn't be the one who signs off on it. Don't let an agent grade its own homework.

So the loop is built around those two problems specifically: review the input before it runs, and never let the writer be the reviewer.

The shape of the loop

The human touches this once, at the spec. Everything after that runs on its own.

Rendering diagram…

Two details matter more than the diagram suggests. The spec review happens before any code exists, which is the cheapest place to catch a mistake. And the post-PR review is a separate step run by a different model than the one that wrote the code. By the time a PR reaches me it has been reviewed twice, once as a spec and once as a diff.

Every part is deliberately boring

If you pull the loop apart, there is no single impressive component:

  • The worktree is plain git worktree. Each run gets its own branch and working directory.
  • The two critics are two model calls with different models behind them. They reliably catch different things: one flags the edge cases I left out, the other flags where I over-scoped the work. Different models have different blind spots.
  • The synthesizer reconciles those two critiques into one improved spec, so I approve a single sharpened version instead of refereeing a pile of conflicting notes.
  • The reviewer and the fixer are, again, just model calls in a short loop.

Most of those 24k lines aren't the agents at all. They're the unglamorous part: state that survives a crash, atomic writes so two runs can't corrupt shared files, and a safety machine that decides what's safe to clean up. That safety machine taught me the one rule I'd carry to any version of this: ground truth is the real system state, not your database. forge reads git worktree list to decide what exists, and the database is only annotation. A database drifts and git doesn't; the moment a harness trusts its own bookkeeping over reality, it starts deleting things it shouldn't.

The part I didn't expect is where the leverage concentrated. The highest-value place to spend attention turned out to be the input, not the output: a spec two models have already argued over is worth more than any amount of cleanup after the fact. The parts are commodity; the loop is what's worth building, because it puts your attention in exactly one place.

The gap: full agent isolation

This is the part I'm not happy with yet, and the reason forge is still a tool I watch rather than one I'd point at anything sensitive.

A worktree isolates the repository: the branch and the files for that run. It does not isolate the agent. The process still runs with my machine's access: my filesystem outside the repo, my network, my credentials. "Isolated run" today means "isolated git state," not "isolated blast radius."

Rendering diagram…

In the meantime I run the agents behind a fairly extensive set of hooks that intercept what they try to do and block the dangerous moves before they land. The hooks catch a lot, and I wouldn't run unattended without them. But hooks are guardrails rather than a boundary: they stop the mistakes I predicted, and a sandbox has to stop the ones I didn't.

To actually trust unattended runs, especially more than one at a time, the agent itself needs that boundary: a sandbox with scoped network and resource limits, so a run can't reach past its own task even if it tries. That's what I'm building now. Until it's there, "walk away and trust it" has an asterisk.

What I'd tell someone building this today

Three things, in order:

  1. Spend your first effort on the spec-review step, not the agent. Front-loading the review onto the input was the highest-leverage change I made, and the cheapest to build.
  2. Make ground truth the real system state. Read git (or whatever the real source is) and treat your own database as a cache that can lie.
  3. Don't ship unattended execution until isolation is real. Reviewing the work is the easy half; bounding what a run can touch is the half that decides whether you can actually leave. Hooks buy time, but they don't close the gap.

The loop is the interesting part. The isolation decides whether the loop is safe to walk away from, and I'm still building that.