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 were worktree-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):

  1. SubagentStart writes .agent-worktrees/.sessions/{session_id}.json mapping session → worktree path
  2. PreToolUse reads session_id from hook input, looks up session file, blocks any Write/Edit outside the mapped worktree
  3. 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."