---
title: "Why Git Worktrees Aren't Cutting It: Designing PDB's Agent Environments"
description: "Two coding agents changed the same repository paths without seeing each other's dirty files. The filesystem design behind that result is smaller—and more separate from Git—than we expected."
date: "2026-08-10"
tags: ["coding-agents", "filesystems", "developer-tools"]
visibility: "public"
status: "draft"
---

# Why Git Worktrees Aren't Cutting It: Designing PDB's Agent Environments

Git worktrees are the obvious answer when two coding agents need to work on one
repository at the same time. Give each agent a worktree, put each worktree on a
branch, and merge the useful result. That answer works often enough that it is
easy to stop asking what problem Git is actually solving.

We needed a narrower guarantee: two trusted coding agents should start from the
same exact program state, receive ordinary writable directories, and never see
or overwrite each other's uncommitted files. Git could remain available, but it
could not be a prerequisite. Containers, privileged mounts, and cloud services
also had to remain optional.

The macOS-first proof passed. Codex and Claude Code each changed the same paths
from the same pinned base. They produced different files, deletes, renames,
symlinks, and executable bits. One environment was already dirty before the
second agent inspected its copy, yet neither agent saw the other's output and
the base remained unchanged. The winning first backend was not a new virtual
filesystem. It was a full directory copy with a precise contract.

## Git worktrees solve a larger, different problem

Git worktrees are good. The [`git worktree`
manual](https://git-scm.com/docs/git-worktree.html) describes a mature way to
attach multiple working trees to one repository. If a workflow is already
Git-native, they are often the quickest isolation tool available.

But a worktree also imports Git's model into workspace lifecycle. Creation is
tied to repositories, refs, indexes, locks, and administrative state. That is
useful when version control should own the workspace. It is coupling when the
program's canonical state may come from a content-addressed database, a
generated snapshot, or another source entirely.

The important split is this:

```text
workspace:    what files can this agent see and change?
checkpoint:   what exact result did the agent produce?
publication:  who may make that result canonical?
```

A directory should not gain publication authority merely because an agent can
write to it. Conversely, creating a private workspace should not require
creating a branch.

## An environment is an ordinary directory with an extraordinary parent

The design starts by scanning the source tree while excluding administrative
roots such as `.git`, `.pdb`, and `.pdb-host`. It records an exact manifest,
materializes a base, scans again, and fails if the source changed during the
operation. The manifest is content-addressed.

Each environment is then materialized from that exact base:

```text
                       content-addressed base
                        a8cd…807ff
                       /           \
                 materialize    materialize
                    /                 \
       ordinary directory       ordinary directory
          Codex changes            Claude changes
```

The agent does not need an SDK. Its editor, shell, package manager, language
server, watcher, and build tool see a normal native path. The isolation boundary
comes from separate directory trees and lifecycle rules, not from convincing
every tool to use a custom file API.

This is intentionally a workspace boundary for trusted, cooperative agents. A
same-user process can still open another absolute path, inspect host processes,
or use host credentials. Hostile-code containment needs a container, VM, or
another operating-system security boundary. Preventing accidental cross-agent
edits is valuable without pretending it is a sandbox.

## Why full-copy won the first round

The proposed macOS bake-off compared three backends:

| Backend | Role | Result |
| --- | --- | --- |
| Full copy | Correctness oracle and portable fallback | Selected |
| APFS clone | Native copy-on-write optimization | Promising, still experimental |
| AgentFS 0.6.4 | External SQLite/NFS candidate | Rejected on the tested profile |

Full-copy is not clever. It copies bytes, so startup and disk use scale with
the source. That weakness is visible and measurable. Its advantage is that the
result is an ordinary native directory with native watcher behavior and no
daemon, mount, Git repository, or kernel extension. More importantly, it gives
every optimized backend a reference result. If a clone or virtual filesystem
cannot reproduce the full-copy tree and checkpoint exactly, the optimization
loses.

APFS cloning remains the likely performance path. Apple's
[`clonefile(2)`](https://keith.github.io/xcode-man-pages/clonefile.2.html) gives
copy-on-write semantics, which should reduce startup time and physical growth.
The unresolved issue is observability: a production backend must be able to
request a real clone and report a typed failure. A command that silently falls
back to copying cannot support honest backend selection or performance claims.

AgentFS was deliberately kept outside the data model and tested as a bounded
external backend. Version 0.6.4's single-file SQLite state and session model are
attractive, but its mounted command timed out twice before the first mutation on
the tested arm64 macOS host. That rejects one version on one profile. It does
not prove the project is generally unusable. A future checksum-pinned release
can re-enter the same oracle without changing the environment contract.

## Prior art points to a layered design

PDB-env is not the first system to make duplicate-looking trees cheap. The
useful precedent is spread across filesystems, build systems, and package
managers, with each system drawing the sharing boundary in a different place.

| Prior art | What it shares | What remains private | Lesson for PDB-env |
| --- | --- | --- | --- |
| [Nix](https://releases.nixos.org/nix/nix-2.24.5/manual/store/index.html) | Immutable content-addressed inputs and build outputs | The writable source workspace | Reuse exact toolchains without pretending the store is a workspace |
| [Cargo target directories](https://doc.rust-lang.org/stable/cargo/reference/build-cache.html) and [sccache](https://github.com/mozilla/sccache) | Cargo can reuse artifacts in one mutable `target`; sccache shares keyed compiler results | A safer agent design keeps `target`, incremental state, and final output local | Share an action cache, not a live mutable output directory |
| [pnpm](https://pnpm.io/motivation) | Package files in a content-addressed store; optionally resolved graphs in its [global virtual store](https://pnpm.io/settings/node-modules#enableglobalvirtualstore) | The project's links and unsupported or mutable cases | Physical package bytes and even immutable graph closures can be shared without sharing a writable `node_modules` |
| [Bun](https://bun.com/docs/pm/global-store) | Cached packages and eligible graph-addressed install entries; APFS clones on macOS and hard links on Linux and Windows | Patched, scripted, workspace, `file:`, and other mutable dependencies fall back to project-local installs | Share only entries whose complete identity and immutability are known |
| [Hyperspace](https://hypercritical.co/hyperspace/) | Byte-identical files across existing APFS directories, replaced with independent copy-on-write clones | Directory lifecycle, pinned bases, active changes, and checkpoints | Post-hoc deduplication can rescue old copies, but proactive cloning preserves more sharing and avoids rescanning |
| [OverlayFS](https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html) | An immutable lower tree | Each mount gets a writable upper tree | The lower-plus-private-upper model is right, but mount requirements and copy-up semantics must remain backend-specific |
| [Btrfs subvolume snapshots](https://btrfs.readthedocs.io/en/stable/dev/dev-btrfs-design.html) | An entire subvolume root and its unchanged extents | Each writable snapshot receives copy-on-write changes | On Btrfs, snapshotting a pinned base may be the best Linux backend |

Btrfs is especially close to the desired environment abstraction. A writable
snapshot is created by sharing the base subvolume's tree rather than traversing
and copying every file. It remains a native directory with native Linux watcher
behavior, persists across process crashes, and consumes new physical storage as
files diverge. This is stronger than merely placing Git worktrees on Btrfs:
PDB-env can snapshot its immutable base directly and avoid Git's branch and
worktree administration entirely.

It is still a conditional backend. The base and environments must be on the
same Btrfs filesystem, an ordinary pre-existing directory must first be
materialized as a subvolume, and [nested subvolumes are not recursively
included](https://btrfs.readthedocs.io/en/latest/btrfs-subvolume.html). Initial
filesystem setup is also a host concern. On macOS, APFS clones remain the
corresponding native optimization; on machines without either facility, full
copy remains the portable oracle.

The combined lesson is that source trees, dependency graphs, package bytes,
compiler caches, and generated output should not share one storage policy. A
PDB-env can use a copy-on-write snapshot for the whole starting tree, let pnpm
or Bun reuse their immutable global stores, keep `node_modules`, Rust `target`,
databases, sockets, and build output private, and still expose one ordinary
directory to the agent. Full-copy winning the first correctness round does not
mean every production environment must duplicate every dependency forever.

## The boring filesystem operations mattered most

A useful proof could not stop at “two agents wrote different text files.” The
core matrix covered create, read, edit, append, truncate, delete, rename,
relative symlinks, executable-bit changes, nested directories, large files,
many-small-file trees, re-entry, cleanup, and pinned-base checks. It compared
the resulting manifests with the full-copy oracle.

The Node workload then exercised the parts that usually expose filesystem
abstraction leaks: npm and pnpm installs, private `node_modules` graphs,
TypeScript compilation, `tsserver`, Vite, Next.js, and updated watcher events
from Jest and Vitest. Thirty fresh npm environments and thirty fresh pnpm
environments passed. The npm lane recorded a 3.7-second median install and a
9.2-second median workload; pnpm recorded 3.5 and 10.5 seconds respectively on
the tested host. Those numbers describe this fixture and machine, not a general
performance promise.

Two earlier npm attempts stalled while waiting for Jest's first watcher event.
A diagnostic run and all 30 isolated sequential cases then passed. We kept the
failure as an unreproduced concurrency-sensitive harness flake. Filesystem
research gets dangerous when every timeout is labeled a storage bug—or quietly
deleted from the story.

## Real agents found adapter failures before filesystem failures

The final proof created two environments from the same base digest and launched
real agent CLIs. Codex CLI 0.142.5 and Claude Code 2.1.201 each inspected their
base, changed the same shared file differently, deleted and renamed the same
inputs, made a relative symlink, changed an executable bit, created nested
output, and verified its current directory and environment name.

The first launch did not pass. Codex's configured model required a newer local
CLI. Claude's local credential needed a login refresh. Both failures happened
before a filesystem mutation. After pinning a locally supported Codex model and
refreshing Claude authentication, both agents completed the same operation
contract.

That distinction matters. An agent environment crosses several independent
systems:

- **source isolation** controls the visible writable tree;
- **dependency isolation** gives each environment a private mutable package
  graph while allowing shared immutable caches;
- **runtime isolation** scopes processes, ports, sockets, temporary paths, and
  local databases;
- **checkpointing** extracts an exact immutable result; and
- **publication authority** decides whether that result advances a canonical
  head.

Calling all five “the sandbox” hides which layer failed and which layer needs a
stronger guarantee.

## The command surface should stay smaller than the model

The research implementation originally used `pdb env`. As a standalone idea,
the smallest useful interface looks more like this:

```sh
pdb-env create alpha
pdb-env run alpha -- codex
pdb-env create beta
pdb-env run beta -- claude
pdb-env path alpha
pdb-env discard alpha
```

`pdb-env` by itself can list environments and identify the one containing the
current directory. `enter` can open a nested shell, but `run` is the reliable
automation primitive because a child process cannot change its parent shell's
working directory. The command is only a proposed interface today; the
standalone repository publishes research and evidence, not an installable
binary.

## What this can replace—and what it cannot

This environment model can replace Git worktrees as the local concurrency
mechanism for trusted coding agents. It can give each agent a private view,
retain that view after the process exits, and extract exact changes without
requiring Git.

It does not replace commit history, signed provenance, remotes, code review,
merge algorithms, reflogs, or the rest of Git's recovery ecosystem. A practical
agentic workflow can use `pdb-env` for working state and Git for publication.
Separating them lets each system keep the authority it is good at.

The next implementation slice is deliberately small: turn the research
switcher into a standalone executable that creates, lists, enters, runs, prints,
and discards full-copy environments; preserve the exact base manifest; and
rerun the same file-operation proof outside the PDB repository. APFS can become
an optimization only after forced-clone capability is observable. Checkpoint
publication and hostile-code security remain later, separate decisions.

The durable lesson is not that copies beat worktrees. It is that an agent's
workspace is a product boundary of its own. Once workspace, runtime, security,
and publication stop sharing one name, the first useful version becomes much
smaller—and much easier to test honestly.

## Research and evidence

- [Complete isolation research](../research/agent-environment-isolation.md)
- [Proposed decision record](../research/decision-record.md)
- [Real Codex and Claude receipt](../evidence/pdb-env-real-agents.json)
- [npm 30-run receipt](../evidence/workspace-service-node-npm-30.json)
- [pnpm 30-run receipt](../evidence/workspace-service-node-pnpm-30.json)

_Draft reconstructed from the experiment artifacts with AI assistance. All
substantial claims are bounded to the checked-in receipts and the named host,
versions, and dates._
