Chapter 03 · Section I · 10 min read
3.1 Isolation: sandboxes so parallel runs do not collide
The first structural safeguard for unattended work. Where a cycle runs — and how a failed cycle can be thrown away without damaging anything real.
Isolation is a workspace the loop runs in that isn’t your real workspace. If a cycle produces garbage, you discard the sandbox. If two cycles run at the same time, they don’t fight over the same files. If the model does something unexpected, the blast radius is one throwaway environment, not your production repo.
Three practical shapes of sandbox
A git worktree. For loops that touch a code repo, git worktree add gives you a fresh checkout of any branch in a separate directory. Two cycles working on the same repo run in two worktrees; neither sees the other’s uncommitted changes; either can be thrown away with git worktree remove.
A scratch directory. For loops that touch files (extraction, generation, batch processing), each cycle gets its own tempdir. When the cycle finishes, the successful output moves to its destination; the tempdir is deleted. Failed cycles leave behind nothing but a log.
A container. For loops that run untrusted commands or need environment isolation (specific Python version, weird system dependencies), each cycle spins up a container from a known image, does its work, and shuts down. State that needs to survive is written to a mounted volume.
Same idea, three implementations. Which one you use depends on what the cycle is doing. Loops with code use worktrees. Loops with files use tempdirs. Loops with untrusted commands use containers.
Why isolation matters more when nobody’s watching
In a live loop (2.1), if a cycle produces bad output, you close the window. In an unattended loop, if a cycle writes bad data to your real database, the next cycle inherits the bad data. And the next. And the next. By morning you have twelve hours of compounding damage instead of one bad cycle you would have caught.
Isolation is the containment layer between “one cycle failed” and “the loop is on fire.” A cycle can safely fail if failure means throwing away a sandbox. It cannot safely fail if failure means corrupting shared state.
The parallel-runs problem
Even in a well-tested single-cycle loop, running two cycles simultaneously will surface bugs the single-cycle case never showed:
- Two cycles write to the same file at the same time — corrupts the file.
- Two cycles hold the same lock — deadlock.
- Two cycles read stale data from a shared cache — inconsistent output.
The fix is not “add locks everywhere.” The fix is don’t share writable state between cycles at all. Give each cycle its own sandbox; merge successful results into shared state at the end, deliberately, with the do-and-verify step gating the merge.
What lives outside the sandbox
Not everything moves into the sandbox. The immutable inputs stay shared:
- The reusable knowledge from 3.2 (your CLAUDE.md, your playbooks, your reference docs).
- The reference data the cycle needs to read (product catalogs, address books, past decisions).
- The credentials the cycle uses to reach APIs.
What moves into the sandbox is the mutable working state — the scratch files being generated, the branch being modified, the temporary database being populated. Nothing outside the sandbox changes until the cycle succeeds and the verifier approves and memory update runs.
The clean-up problem
Sandboxes proliferate. A daily loop for a year creates 365 sandboxes. Without a cleanup policy, you’ll wake up one morning to a full disk and a stuck loop.
Two options:
- Delete on success. Successful cycles clean their sandbox after merging results. Failed sandboxes stick around for a debug window (say, 7 days) then get garbage-collected.
- Delete on start. Each cycle deletes any sandbox older than N days at the beginning of its run. Cheap, self-correcting, no separate cleanup job needed.
Pick one. Do not skip both. “It’s fine, disks are cheap” is what people say six months before disks are full.
When you can skip the sandbox
Almost never — but sometimes:
- Pure-read cycles. If a cycle only reads (a monitoring probe, a status check, a report generator) and writes only its output to a fresh destination file, you don’t need a sandbox for the reads. The write destination itself is the “sandbox.”
- Trivial live loops. A live loop you’re watching that runs
pytestin your open repo doesn’t need a separate worktree; you’d just interrupt if something went wrong.
Everything else — anything unattended, anything scheduled, anything event-driven, anything with two cycles that might overlap — gets a sandbox. Non-negotiable.
Check your understanding
Quick check
—