Git Worktree Isolation#
Status#
Field |
Value |
|---|---|
Status |
Implemented |
Version |
1.0 |
Date |
2026-03-31 |
Problem#
Marcus runs multiple worker agents in parallel during experiments. All agents produce code that ultimately lives in a single implementation/ directory on the main branch. Without isolation, parallel agents overwrite each otherβs files β a failure mode we call Type A failure.
A naive fix would be to let agents work in isolation and merge everything at the end. But Marcus decomposes projects into a DAG of tasks with fan-in dependencies. For example, βTest Time Widgetβ depends on βImplement Time Widgetβ and needs to see its code before it can run. Integration-only-at-the-end does not work because dependent tasks need to see completed work mid-experiment.
The system needs a mechanism that provides:
Isolation β agents never interfere with each otherβs in-progress work
Visibility β when a task completes, its code becomes visible to dependent tasks immediately
Attribution β every line of code is traceable to the agent that wrote it
Solution#
Three mechanisms work together to solve this:
1. Per-agent git worktrees#
Each worker agent gets its own git worktree checked out on a dedicated branch named marcus/{agent_id}. The worktree is a separate directory with its own working tree but shares the same .git object store as the main repository.
2. Per-worktree git identity#
Each worktree has git config user.name set to the agentβs ID. This ensures git blame and git log attribute commits to the correct agent without relying on global git configuration.
3. Merge-on-completion#
When a task passes validation and is marked DONE, Marcus merges the agentβs branch back to main. Dependent tasks then start from an updated main that contains all completed work. If the merge produces conflicts, Marcus aborts the merge and sends the agent back to resolve them.
Responsibility Matrix#
Actor |
Responsibility |
|---|---|
|
|
|
Creates worktrees per worker via |
Marcus MCP ( |
|
Worker agent |
Works in its worktree, commits to its branch, resolves conflicts if sent back |
Integration agent |
Works on |
Epictetus |
Uses |
Directory Structure#
experiment_dir/
βββ config.yaml <- NOT in git
βββ project_info.json <- NOT in git
βββ prompts/ <- NOT in git
βββ logs/ <- NOT in git
βββ implementation/ <- .git HERE, main branch
β βββ .git/
β βββ CLAUDE.md
β βββ docs/architecture/... <- design artifacts
β βββ package.json <- scaffold
β βββ src/ <- scaffold + merged code
βββ worktrees/ <- dedicated worktree directory
βββ agent_unicorn_1/ <- worktree, branch marcus/agent_unicorn_1
β βββ .git -> ../../implementation/.git
β βββ (agent 1's isolated work)
βββ agent_unicorn_2/ <- worktree, branch marcus/agent_unicorn_2
βββ .git -> ../../implementation/.git
βββ (agent 2's isolated work)
The worktrees/ directory is outside implementation/ to avoid nested
tracking issues, and separate from experiment infrastructure so agents
only see code files. Each worktreeβs .git is a symlink to the shared
object store β merges are local operations with no network overhead.
Stale .git protection: run_experiment.py removes any .git at the
experiment root on startup. Only implementation/.git should exist. A
stale root .git (from rm -rf * not removing hidden files) would cause
worktrees to branch from the wrong repo.
Branch Structure#
main ββββββββββββββββββββββββββββββββββββββββββββββββββ->
| | | | |
| design artifacts merge1 merge2 |
| (Marcus) integration
|
βββ marcus/agent_unicorn_1
| ββββββββββ
|
βββ marcus/agent_unicorn_2
ββββββββββ
The main branch accumulates completed work. Each agent branch diverges from main at the point the agent started its current task and is merged back when the task passes validation.
Task Lifecycle with Worktrees#
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TASK LIFECYCLE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 1. Agent gets task β
β βββ> Works in worktree, commits to marcus/{agent_id} β
β β
β 2. Agent reports 100% β
β βββ> Validation gate runs β
β β
β 3. Validation fails? β
β βββ> Agent fixes, stays on branch, NO merge β
β β
β 4. Validation passes? β
β βββ> Marcus merges branch to main β
β β
β 5. Merge conflicts? β
β βββ> Marcus aborts merge β
β βββ> Sends agent back: "git merge main, fix conflicts, β
β commit, report again" β
β β
β 6. Merge succeeds? β
β βββ> Task truly DONE, code on main β
β βββ> Dependent tasks can now start β
β β
β 7. Agent gets next task β
β βββ> Runs `git merge main --no-edit` in worktree β
β βββ> Sees all previously completed work β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Per-Task Visibility (GH-302)#
Worktrees are created once at spawn time, not per-task. When an agent
picks up a new task, it runs git merge main --no-edit to incorporate
all previously merged code. This is enforced in two places:
Shell script (belt): before Claude starts, the worker script merges main into the worktree
Worker prompt (suspenders): step 5 of the task workflow says βFIRST: run
git merge main --no-editto get latest completed workβ
This ensures that when Agent 2 picks up a task that depends on Agent 1βs completed work, it sees that code β because Agent 1βs branch was merged to main on completion, and Agent 2 merges main before starting.
Interface Comparison#
Marcus has three interfaces. Each handles worktree creation differently:
Interface |
Who creates worktrees? |
Where? |
|---|---|---|
|
|
In experiment runner |
Posidonius |
|
Same code path |
MCP direct |
User (manual |
Userβs responsibility |
For the /marcus skill and Posidonius interfaces, worktree creation is fully automated. When using the MCP server directly (without the experiment runner), the user is responsible for setting up git worktrees manually.
Whatβs NOT Handled (Deferred)#
Feature branches β There is no Feature entity. Tasks branch directly from
main. A feature branch abstraction may be added later if experiments grow to need it.Worktree cleanup β Worktrees are not cleaned up after experiments. The entire experiment directory is disposable, so cleanup is unnecessary.
Cross-agent worktree reuse β Each task gets a new worktree from the current
main. Worktrees are not reused across tasks.
Implementation Files#
dev-tools/experiments/runners/spawn_agents.pyβ_create_agent_worktree(),spawn_worker(),create_worker_prompt()src/marcus_mcp/tools/task.pyβ_merge_agent_branch_to_main()inreport_task_progress()
Prerequisite: Project Scaffolding#
Worktree isolation requires project scaffolding (Phase A.5) to be effective. Without scaffolding on main before worktrees branch, each agent independently scaffolds the same project β duplicating work and creating merge conflicts on every shared file. See 16-project-scaffolding.md.
See Also#
Project Scaffolding β shared infrastructure prerequisite for worktrees
Design Autocomplete β Phase A, produces architecture doc that scaffolding reads
Workspace Isolation and Feature Context β earlier design notes on isolation