The pitch that got me into this was simple: define what you want, go to sleep, wake up to working code. In October 2025 I wrote that from where I was standing you would need to hire someone to babysit the process around the clock for that to work. I then spent most of a year building the machinery that removes the babysitter.

Much of that work has been outside the model. Runs died when I closed a browser tab. Agents edited files they had been told to leave alone. I also killed a healthy long run because I mistook it for a hung one.

Two things have to be true before walking away is reasonable. The agent should not edit the live checkout while it is working, and I should be able to tell without watching whether it is alive, finished, stuck, or waiting on me. Those controls need separate verification of the output. They also leave the process with whatever access its host account permits; I cover that boundary below.

Give every run its own checkout

For a long time, agents edited my live working tree.

On June 18, 2026 a routine verification run did a git reset against my live working tree and threw away an in-flight commit of mine. I got it back with git merge --ff-only; the commit object was still recoverable. The incident settled a question I had been putting off. An orchestrator with git powers needs the same checkout separation I had already been demanding of the coding agents underneath it.

Worktree mode landed the next day. It remains opt-in in aidd v3: the web and Director launchers use the live checkout unless I enable Use isolated worktrees in Settings, while a CLI coding run can request --worktree directly. When enabled, aidd creates a linked git worktree on a branch named aidd/run-<id>. The run gets a separate checkout rather than my live one. The normal outcomes are:

  • Merge. The work is clean and the live tree is clean, so the run branch is merged back into the project. aidd tries a fast-forward first, then a normal merge when the branches have diverged.
  • Park. The merge would conflict with something that changed in the meantime, so the branch is held as waiting_approval with its own exit code, and I look at it when I get to it.
  • Discard. The run failed, so its evidence is copied to the canonical run store before the checkout is removed. If that copy fails, aidd preserves the checkout rather than losing the evidence.

The merge path is automatic. A successful run does not wait for me to inspect its branch before aidd merges it into a clean live tree; I review the landed result afterward. A dirty live tree or a merge conflict turns that into a pre-merge human decision by parking the run instead.

Each run gets a separate checkout and normally ends in one of three outcomes: merged back, parked for review on conflict, or discarded after its evidence is saved project live checkout checkout linked worktree separate checkout one per run merge clean work lands in the project park a conflict waits for review discard failed checkout removed
The run works in a separate checkout. Merging into the live checkout waits until the work is done and the live tree is clean.

For this repository boundary, worktrees are a better fit than full clones. They share the object database, so creating one is cheap even on a large repository, and the branch is a real branch I can inspect, diff, and merge with ordinary tools. I can use the Git tools I already know when a run goes wrong.

Separate checkouts also removed the obvious working-tree collision when two runs target the same project. That roughly doubled the throughput of an overnight window, but it did not make parallel work free of conflicts. aidd still needed feature leases and conflict-aware metadata write-back so two runs could not quietly claim or overwrite the same work.

This is also where my design converged with the wider tooling. Codex now uses dedicated background worktrees for parallel and scheduled work, with a human reviewing the result. I arrived at the same shape independently in June.

A linked worktree separates checkouts, not machines. It still shares Git metadata with the main repository, and the process can reach whatever its host account and backend permissions allow: SSH keys, API credentials, other directories, network access. aidd currently invokes several backends in approval-bypass modes so they can work unattended, which I treat as trusted-input automation. The worktree gives me reviewable merge, park, and discard behavior. An OS sandbox or microVM limits what the process can reach. As of August 2026, Claude Code can sandbox Bash commands with filesystem and network limits on macOS and Linux, Codex has native sandboxing on Windows as well as macOS and Linux, and Docker Sandboxes can put the entire agent inside a microVM. For untrusted repositories or prompts, add that outer boundary and narrowly scoped credentials; do not substitute a worktree for it.

Restricting writes

A separate checkout protects the live working tree from ordinary run edits. A second, narrower control protects specific parts of the checkout from specific kinds of run.

When my orchestrator adopts a codebase it did not write, the promise is that it does not modify the application’s own code. It examines the project and writes metadata and reports. Metadata-only runs get a write allowlist of exactly one directory, and the orchestrator snapshots the worktree before each step and mechanically reverts any write outside it.

I tried that lane on an outside project the same day it was built. The first run found a hole within the hour. The guard watched the working tree for uncommitted changes. So the agent committed the forbidden file, and the guard never saw a thing.

I had not considered that path. The guard needed to examine staged changes and commits as well as the working tree. An agent routinely moves changes between all three, so checking only uncommitted files was never going to hold.

Test each of those paths, then try the guard on a real project. That first outside run found two more gaps in an hour.

Keeping runs alive

For months, closing a browser tab could take live work down with it. A web-process hiccup could do the same. That is a silly way to lose an hour of progress and fatal to the idea of walking away.

The CLI now owns the run and writes heartbeats to disk. The panel reads that state when it is open. You can start a run from any surface, terminal or panel or scheduled job, and the panel will find it and show it. This follows the Processes and Disposability guidance from the twelve-factor app: the worker owns its lifecycle and the process showing its state can disappear without taking the work with it. It is easy to skip when the panel is the thing you built first, and painful to retrofit afterwards.

Detaching a process sounds like a one-line change. On Windows it was four separate bugs, and I proved each fix with a minimal reproduction before trusting it.

  1. taskkill /F delivers no catchable signal. Graceful shutdown cannot exist unless you build it yourself. I added a POST /admin/shutdown endpoint so the process can be asked politely from the outside, instead of pretending a kill signal is a request.
  2. Child processes inherit the parent’s listen socket. On Windows under Bun 1.3.14, calls through the node:child_process.spawn compatibility API let children inherit the parent’s HTTP listen socket. A launched app pinned the orchestrator’s own port; after a restart the port stayed LISTENING, attributed to a dead PID, until reboot. Every child of the web backend now spawns through Bun.spawn, which restricts inherited handles. I had fixed individual call sites twice before moving all backend child launches to the same API.
  3. Children live in the parent’s job object, and parent exit kills the job. unref() does not help. A “detached” run died the instant the panel restarted, proven with a repro where the child wrote zero lines and died with an empty stderr, which is exactly as fun to debug as it sounds. The fix is ugly but it holds: a transient hidden PowerShell bridge using Start-Process breaks the new process out of the job object, the real argv travels in a JSON payload file so user arguments cannot inject shell metacharacters, and the run’s PID is reconciled from its first heartbeat.
  4. A healthy long run and a hung one look identical. This caused a real misdiagnosis during beta. I killed something that was working.

On Windows, test what happens when you close the panel, end the terminal, and restart the backend. A child that survives one of those may still die during another.

For the fourth problem, I added a heartbeat. The run writes a timestamp at a known interval, and the panel compares it with the current time. That gives me an indication of whether the process is still reporting, which is much more useful than guessing how long a task ought to take. I still need the run’s output to tell me whether it is making progress.

I added failure-reason recording to the coordinator one release before automatic retries. I wanted to be able to see why an attempt failed before letting the next one start on its own.

Give the run a way to stop and ask

Not every blocker is a bug. Some are decisions no agent should make on your behalf, and the correct behavior is to stop with a written explanation rather than guess.

A waiting-approval work item showing the agent’s written analysis of an ambiguous product decision and the approval condition.
This item remains parked with the question and its approval condition recorded beside it.

My runs can mark work as waiting_approval and file a written analysis of the ambiguity. The first real workout of that loop produced three product decisions across template work, all genuinely ambiguous. The agents wrote up the options and stopped. Two weeks later my decisions came back and were implemented in a day.

The most interesting one was a security tradeoff. An attacker could lock a victim’s account with a few bad password guesses, and the lockout also ejected the victim from sessions they were already signed into. Three fixes were possible at different costs, and I picked the surgical one: a lock that blocks new sign-in attempts but leaves live sessions alone. That is not a decision I want inferred from a prompt, and it is not one worth stalling an entire overnight run over either.

The run needs a way to record that question and continue with other work until I answer it.

Watch the budget, warn before you enforce

Runs get cost and token budgets. I shipped them warn-only on purpose: they record and they warn, but they do not kill a run.

The reason is that I did not yet trust the numbers, and I was right not to. My benchmark harness was double-counting one backend’s cached tokens, quietly handing that stack composite scores it had not earned.

Here is the corrected snapshot. The aidd leaderboard was generated July 2, 2026 from five agentic task types: interview, audit, remediation, validation, and quiz. Its composite weights were 45% correctness, 25% reliability, 20% time, and 10% cost.

Benchmark stackComposite scoreFive-task suite cost
native-glm51-high0.972$0.63
kilocode-glm52-low0.955$0.50
claude-code-opus-max0.947$3.01

The $0.63 native GLM 5.1 run topped that composite ranking. KiloCode with GLM 5.2 was the cheapest of the three at $0.50 but did not rank first, while the Claude Code Opus run cost nearly five times as much as the leader. Those prices and scores are properties of that run set, manifest, and pricing table; fixing the accounting changed the ranking. Had budget enforcement been live on the bad numbers, it would have been killing the wrong runs for weeks, and the symptom would have looked like flakiness rather than like a metering bug.

I left enforcement off while checking those measurements against the underlying runs.

Know what your agents can actually reach

After freezing my template, I built a read-only scanner to inventory what my agents could reach, including remote endpoints, credential paths, dynamic tools, and broad permissions. It reports that access; it doesn’t restrict it.

I had added access over several months and couldn’t reliably remember the full set. I wanted to read the configuration together before leaving those agents running.

You do not need a product for this. A script that walks your agent configuration and prints every endpoint, key path, and permission scope, run occasionally, will tell you things you did not know.

What unattended still does not mean

I run agents unattended for hours at a time. Some things have not moved:

  • Merges are gated on a clean live tree. If I have work in progress, the run parks rather than merging. That keeps the automatic merge from landing over my unfinished changes.
  • Review still happens. I read the result after the run.
  • The first run of anything new is watched. Every guard hole described on this page was found by watching a run I could have walked away from.

Changelog

  • August 31, 2026: Corrected the aidd v3 worktree default and review timing: isolation is opt-in, and successful runs merge back automatically when the live tree is clean.
  • August 5, 2026: Separated worktree isolation from OS confinement, added the sandbox and container boundary, scoped the Windows process behavior, corrected the twelve-factor citation, and dated the benchmark snapshot.
  • July 31, 2026: First published.

Where this came from