DL-004: The Containment Problem
The orchestrator discovers its agents have been running without walls, and designs a prison.
Session Notes (Raw Material for Article)
The Discovery
A debugging session revealed that the entire worktree governance system — designed across three sessions and five scripts — had never actually worked. The root cause: two worktree systems fighting each other.
Claude Code's built-in isolation: "worktree" creates worktrees named agent-{uuid} at .claude/worktrees/, with branches worktree-agent-{uuid}. No .agent-identity file. No framework awareness.
Our framework's prepare-agent-worktree.js creates worktrees named {role}-{crId} with branches wt/{role}-{crId} and writes .agent-identity for hook governance.
The orchestrator had been calling Agent(isolation: "worktree"), which invoked Claude Code's mechanism — completely bypassing our script. The result: 10 orphaned worktree directories, 16 stale branches, zero .agent-identity files (except one manually created), and run logs permanently stuck at status: "running".
The Forensic Evidence
- 10 worktree directories on disk, 6 tracked by git, 16 branches
- 1 out of 10 worktrees had
.agent-identity(manually placed) - Zero
wt/*branches (our naming) — all wereworktree-agent-*(Claude Code's naming) - ui-developer and backend-developer run logs:
crId: null,completedAt: null,status: "running" - agent-orchestrator runs DID complete — because they emitted SDLC-OUTPUT and didn't depend on
.agent-identity
Key Insight: Behavioral vs Mechanical Enforcement
The conversation crystallized a fundamental principle: you cannot trust agent behavior for security boundaries. An LLM agent told "only write to your worktree" is following a suggestion, not obeying a constraint. The agent might:
- Hallucinate a path from training data
- Follow a pattern that references the main repo
- Simply ignore the instruction under context pressure
The enforcement must be mechanical — a hook that intercepts every Write/Edit call and blocks it before execution, with no agent cooperation required.
The Symlink Trap
Initial design proposed symlinking node_modules/ from the main repo into agent worktrees for compilation speed. Marco's correction: "If I and another dev work on the same project using branches, how much do we care about the other developer's node_modules folder? We don't."
A symlinked node_modules breaks isolation:
- Agent runs
npm install→ mutates main repo's node_modules - Agent corrupts the folder → orchestrator's compilation breaks
- Two parallel agents share the same symlink → race conditions
- The worktree IS a complete working copy. It gets its own
npm install. Period.
The Architecture: Sovereign Worktree with Session-Correlated Enforcement
The worktree is the agent's entire world. After creation, framework artifacts (.claude/, dev/ai/, CLAUDE.md) are stripped from the working tree. The agent only sees:
TASK.md— its complete instructions.agent-identity— for hook detection- Source code — what it needs to modify
- Build tools —
package.json,tsconfig.json,node_modules/(own copy)
Enforcement mechanism (session-correlated, 3-hook chain):
- SubagentStart writes
.agent-worktrees/.sessions/{session_id}.jsonmapping session → worktree path - PreToolUse reads
session_idfrom hook input, looks up session file, blocks any Write/Edit outside the mapped worktree - SubagentStop cleans up session file
The session_id comes from Claude Code's runtime — the agent can't spoof it. Every Write/Edit is intercepted before execution. Parallel agents each have unique session IDs mapped to separate worktrees.
Broader Observations
For the research project (RQ3 — governance component contribution): This is direct evidence that agent isolation is a necessary governance component, not optional. Without mechanical enforcement, agents operate in a shared-state environment where one agent's mistake can corrupt another's work or the orchestrator's state. The .agent-identity + session-lock pattern is a minimal viable containment protocol.
For the research project (RQ4 — self-referential evolution): The framework failed to detect its own containment gap for multiple sessions. The worktree scripts existed, the hooks existed, but the integration was broken. This raises the question: can a governed pipeline detect integration-level failures, or only component-level ones?
For the ablation study (RQ3): The worktree system is now cleanly separable. With the new architecture, disabling worktree isolation is a single config toggle — run agents in the main repo without session locks. This gives a clean ablation axis: governed-with-isolation vs governed-without-isolation.
Design Decisions Log
| Decision | Who | Why |
|---|---|---|
Use our own worktrees, not Claude Code's isolation: "worktree" |
Human | Less reliance on Claude Code internals = more control, portability |
| No symlinks for node_modules | Human | Complete isolation — worktrees are independent working copies |
Move worktrees out of .claude/ to .agent-worktrees/ |
Human + Agent | Framework portability, ablation study, avoid Claude Code namespace collision |
| Strip framework artifacts from worktree | Human | Agents should not know about the framework; reduces autoloaded context |
| Agent cards become thin metadata shells | Agent (human approved) | All behavioral instructions in TASK.md; agent card is just Claude Code's metadata contract |
| Agents MUST commit, enforced by pre-commit hooks | Human | Git commits = traceability, enforcement, auditability |
| 2-day worktree retention | Human | Post-mortem capability without unbounded disk growth |
| Session-correlated boundary enforcement | Agent (human approved) | session_id from Claude Code runtime is unforgeable; enables parallel agent containment |
| Higher code quality bar — modular scripts, proper error types | Human | "We are here because of improper exception handling, low code quality, bad sw modularization" |
Quotable Lines (for article drafting)
- "Trust but verify is wrong here — it's don't trust, enforce."
- "The agent card says 'use absolute paths to your workspace' but that's a suggestion. The enforcement must be mechanical, not behavioral."
- "A worktree IS a complete working copy. It gets its own npm install. Period."
- "Two worktree systems, zero integration."
- "The framework failed to detect its own containment gap for multiple sessions."
Latest Entries
From Single Project to Starter Kit: Extracting a Governed Framework
From Single Project to Starter Kit: Extracting a Governed Framework The hardest part of open-sourcing an internal framework is separating the generic from the specific. [...]
DL-025: Progression Is a Graph, Not a List
DL-025: Progression Is a Graph, Not a List Most games store progression as a list — level 1, level 2, level 3. This one stores [...]
DL-024: The Editor Is the Compiler
DL-024: The Editor Is the Compiler A node graph you wire on a canvas, then press Run and watch the output render live inside the [...]
DL-023: The Same Algorithm Made Three Different Things
DL-023: The Same Algorithm Made Three Different Things A 2007 Eurographics paper on growing trees. A 1964 Japanese paper on water transport in plant stems. [...]
DL-021 Part 2: The Rule That Caught Itself
DL-021 Part 2: The Rule That Caught Itself Everything went wrong, all at once, and every single failure was the pipeline catching itself doing the [...]
DL-021 Part 1: The Content Engine
DL-021 Part 1: The Content Engine We set out to publish yesterday's devlog. The website caught a compliance gap, the wrong fix took the site [...]





