Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,49 @@ For each entry, **Upgrade impact** classifies the change against the [three-buck

## Unreleased

### Claude Code's own worktrees are workers too (parallel-agents)

([0015](docs/decisions/0015-tool-worktrees-are-workers.md), amending
[0008](docs/decisions/0008-dispatcher-and-worker-worktrees.md))

Teams that work in the desktop app start tasks in a new session with its worktree option. Two
projects with the module had such worktrees in use while the framework treated them as foreign.

#### Changed
- **Every linked worktree is a worker.** A task gets its worktree one of two ways:
- **Claude Code's worktree, by default:** a new session with the desktop app's worktree option,
or `claude --worktree`.
- **The scripts (`/dispatch`):** when the worktree needs a port, setup or start commands, or a
base branch other than the default.
- **The session-context hook names the route and the gaps.** In the main checkout, it says which
route this project's tasks take. In a worktree the scripts didn't set up, it says what that
worktree lacks: a generated branch name to rename after triage, the env file, the scripts' setup,
or the right base branch. The role no longer depends on where the worktree sits.
- **`worktree-new.sh` marks the worktrees it sets up,** with a file in the worktree's own git
directory. Worktrees from before the marker are recognized by their folder name.
- **`worktree-ls.sh` lists Claude Code's worktrees as workers.** It flags the ones on a generated
branch or a detached HEAD, instead of asking to move task work out of them.
- **`/dispatch`** opens with which route a task takes. `protect-hub.sh`'s message names both routes.

#### Added
- **`.worktreeinclude`** (with the module): the env file Claude Code copies from the main checkout
into each worktree it creates.

#### Upgrade impact
- **Overwrite:**
- Core: `.claude/hooks/session-context.sh`, `.claude/hooks/protect-hub.sh`,
`.claude/hooks/README.md`.
- With the module: `scripts/agent/{_worktree-lib,worktree-new,worktree-ls}.sh` and
`.claude/skills/dispatch/SKILL.md`.
- **Merge** (with the module): `docs/PARALLEL-AGENTS.md`. Take the new § Two routes, the worker row,
and § Claude Code's worktrees, which replaces § Claude Code's built-in worktrees. Keep your
§ Shared services.
- **Additive** (with the module): `.worktreeinclude`. List the same file as `ENV_FILE` in
`scripts/agent/worktree.conf`; if the project already has a `.worktreeinclude`, add that line to
it.
- **No migration:** existing script worktrees are recognized by their folder name, and rerunning
`worktree-new.sh` on one adds the marker.

### `/upgrade` keeps the hub clean when it installs the dispatcher hub

Choosing `parallel-agents` moves the upgrade into a worktree created from the last commit, so the
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ flowchart TD
| The requester needs an update on a task | `/stakeholder-update` ("update the client") — in the client's terms, shown in chat, posted on the pull request for the team to relay; on the tracker only on your yes to the exact text | `sonnet` | [Tracker integration](skeleton/docs/TRACKER-INTEGRATION.md) |
| Passing work on — a teammate, another machine, a fresh session | `/handoff` — the state committed to the record first, then a short message of pointers to it | session model | [Skills catalog](docs/SKILLS-REFERENCE.md) |
| A change to how the team works | `/record-decision` → a PDR in `docs/process/` | `sonnet` | — |
| Several tasks at once | `/dispatch` from the main checkout; each task in its own worktree (`parallel-agents` module) | per task | [Parallel agents](docs/scenarios/parallel-agents.md) |
| Several tasks at once | Each task in its own worktree (`parallel-agents` module): a new session with Claude Code's worktree option, or `/dispatch` from the main checkout when the worktree needs the project's setup | per task | [Parallel agents](docs/scenarios/parallel-agents.md) |
| High stakes or a broad sweep | `/deep-review`, `/deep-spec-analysis`, `/deep-context-audit`, `/deep-drift-sweep` | each agent its own | [Skills catalog](docs/SKILLS-REFERENCE.md) |

### Effort, model, and cost
Expand Down
9 changes: 6 additions & 3 deletions docs/ONBOARDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,11 +310,14 @@ if there's no tracker task). Check for business language, verified claims, and y

Each agent session gets its own git worktree — never two in one checkout. With the
[`parallel-agents` module](../modules/parallel-agents/MODULE.md), the main checkout **only
dispatches**: `/dispatch` creates the task's worktree and branch and hands off a three-line prompt; the
worker there does everything from triage on. The main checkout is shared — an edit or a dev server
dispatches**. A task starts in a new session with Claude Code's worktree option (the desktop app's
worktree toggle, or `claude --worktree`), or — when its worktree needs the project's setup, such as a
port — `/dispatch` creates the worktree and branch and hands off a three-line prompt. The worker there
does everything from triage on. The main checkout is shared — an edit or a dev server
there collides with every other session — and analysis there is wasted: the dispatcher can't run the
app or the tests, so the worker re-reads everything where it can verify it.
([decision 0008](decisions/0008-dispatcher-and-worker-worktrees.md))
([decisions 0008](decisions/0008-dispatcher-and-worker-worktrees.md) and
[0015](decisions/0015-tool-worktrees-are-workers.md))

**Exercise (module installed):** dispatch the typo and the topics estimate. The main checkout's
`git status` stays clean, each worker's session context says WORKER, and
Expand Down
2 changes: 1 addition & 1 deletion docs/SKILLS-REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ In order, for a change with something to decide. Commits: `spec:` → `docs:`

| Skill | Module | Purpose |
|---|---|---|
| `/dispatch` | `parallel-agents` | In the main checkout: name the task, create its worktree (`worktree-new.sh --no-start`), and hand off to a worker session with a three-line prompt. Reads only; no analysis, no edits. |
| `/dispatch` | `parallel-agents` | In the main checkout, for a task whose worktree needs the project's setup (a port, setup or start commands, a non-default base branch): name the task, create its worktree (`worktree-new.sh --no-start`), and hand off to a worker session with a three-line prompt. Other tasks start in Claude Code's own worktree. Reads only; no analysis, no edits. |

## Dynamic workflows

Expand Down
2 changes: 1 addition & 1 deletion docs/decisions/0008-dispatcher-and-worker-worktrees.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 0008: The main checkout dispatches; worktrees do the work (optional module)

- **Status:** accepted
- **Status:** accepted; amended by [0015](0015-tool-worktrees-are-workers.md) (Claude Code's own worktrees are workers too)
- **Date:** 2026-10-01

## Context
Expand Down
110 changes: 110 additions & 0 deletions docs/decisions/0015-tool-worktrees-are-workers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# 0015: Worktrees that Claude Code creates are workers too (parallel-agents module)

- **Status:** accepted
- **Date:** 2026-10-02
- **Amends:** [0008](0008-dispatcher-and-worker-worktrees.md) — how a task gets its worktree

## Context

Decision 0008 gives each task its own worktree, branch, pull request, and session, and keeps the main
checkout as a hub that only dispatches. It creates worktrees one way: `/dispatch` runs
`scripts/agent/worktree-new.sh`, which seeds the env file from the main checkout and, when a project
needs it, reserves a port and runs setup and start commands. Worktrees that Claude Code creates
itself are treated as foreign. In the desktop app those come from a new session with the **worktree**
option; in the terminal, from `claude --worktree`. The session-context hook gives a session in one
"no role" and asks for a dispatch, and `worktree-ls.sh` flags task branches in them.

Two projects show that developers use those worktrees for task work anyway:

- **The project the module came from** had four of them parked, one on a task branch the scripts
never set up. That finding is recorded in 0008 and was met by the "no role" message.
- **A second project — a marketing site, upgraded to `d5934b3` with the module chosen —** had four
more under `.claude/worktrees/`: two on task branches and two on detached HEADs. The team works in
the desktop app. Its worktree option already gives one task its own worktree, branch, and session.
With the hub on, the framework would send those developers to a second tool for the same result.

Claude Code now covers most of what the scripts were added for:

- **`.worktreeinclude`** copies gitignored files, such as `.env`, from the main checkout into every
worktree Claude Code creates — the desktop app's included. Seeding from the main checkout is the
same rule 0008 set for the scripts.
- **Project-scope plugins and the main checkout's local settings** reach every worktree of the
repository (Claude Code 2.1.200 and 2.1.211).
- **The desktop app removes its worktrees** when a session is archived, or on its own once the pull
request merges, with auto-archive on.

What it doesn't cover:

- **Ports, env overrides, and setup or start commands** — what a worktree that runs a server needs.
- **A base branch other than the default.** App worktrees branch from the default branch; the
`worktree.baseRef` setting takes `"fresh"` or `"head"`, never a branch name. That's wrong for a
project whose tasks start from an integration branch (Model B).
- **The branch name.** It is generated (`worktree-<name>`, or the app's prefix plus a name), so it
doesn't carry the task's `<type>/<slug>`, which is how a branch joins its spec folder (0001).

The "no role" rule also depends on where the worktree is. It matches `.claude/worktrees/`, and the
desktop app's **Worktree location** setting can move them anywhere, where the same kind of worktree
is called a worker.

## Decision

With the `parallel-agents` module installed, **every linked worktree is a worker**, whichever tool
created it. A task gets its worktree by one of two routes:

- **Claude Code's worktree** — a new desktop session with the worktree option, or
`claude --worktree`. This is the default when the project's worktrees run no server and tasks start
from the default branch. The worker renames the branch to `<type>/<slug>` after triage
(`git branch -m`), before its first commit.
- **The scripts** — `/dispatch` and `worktree-new.sh`, as in 0008. Use them when a worktree needs what
only they provide: a port, `ENV_OVERRIDES`, `SETUP_CMD` or `START_CMD`, or a `BASE_BRANCH` other than
the default branch.

What changes:

- **`session-context.sh`** (core) gives a session in any linked worktree the WORKER role. In a
worktree the scripts didn't create, it adds one line for each thing that worktree lacks:
- a generated branch name: rename it after triage;
- no env file: `.worktreeinclude` should list it;
- a project whose worktrees need a port or start command: dispatch through the scripts for task
work that runs the app;
- a `BASE_BRANCH` other than the default branch: this worktree started from the wrong base.
- **`worktree-new.sh`** marks the worktrees it sets up, with a file in the worktree's own git
directory, so the hook and `worktree-ls.sh` can tell them apart wherever they sit. Worktrees from
before the marker are recognized by their folder, which is named after the branch.
- **The module** ships a `.worktreeinclude` listing `ENV_FILE`, so Claude Code's worktrees get the
env file. Its `MODULE.md` adds a customize step to keep the two in step.
- **`worktree-ls.sh`** lists Claude Code's worktrees as workers. It flags the ones on a generated
branch or a detached HEAD, which are candidates to archive in the app.
- **`docs/PARALLEL-AGENTS.md` and `/dispatch`** describe the two routes and when each fits.
- **`protect-hub.sh`** still stops every edit in the main checkout; its message names both routes.

## Consequences

- **Positive:**
- Developers dispatch with the tool they already use, and the desktop flow needs no pasted prompt.
- For the common case it needs no dispatcher session at all: a new session with the worktree
option replaces the second session 0008 counted as a cost.
- The role no longer depends on where the worktree sits.
- **Negative / cost:**
- Branch naming moves from creation to after triage. A worker that forgets leaves a branch that
joins no spec folder. The session-context line is the reminder, and `/open-pr` already
stops on a branch that isn't `<type>/<slug>`.
- Two routes to explain instead of one, and the choice depends on facts in `worktree.conf`.
- A project that runs a server still needs the scripts for that task work. Starting it from the app
gives a worktree without a port, and the hook can only say so.
- Claude Code's worktrees pile up unless sessions are archived; the pilot had two on detached HEADs.
`worktree-ls.sh` flags them, and auto-archive in the desktop app removes them.

## Alternatives considered

- **Keep "no role" (today).** Two projects worked around it, and the cost lands on desktop users: a
second tool, a dispatcher session, and a pasted prompt for what the app already does.
- **Route Claude Code's worktree creation through the scripts with a `WorktreeCreate` hook.** Every
worktree would get the full setup, but the hook replaces creation for every worktree Claude Code
makes, including subagent and background-session worktrees. Those would each reserve a port and run
setup. It also turns off `.worktreeinclude`. Its input is a generated name like `bold-oak-a3f2`,
since the task isn't known yet, so the scripts' `<type>/<slug>` contract breaks. Cleanup moves to a
`WorktreeRemove` hook. Worth revisiting for a project that wants server setup on every worktree, as
an opt-in.
- **Drop the scripts.** A project whose worktrees run servers needs ports, env overrides, and start
and stop commands, and Claude Code has no equivalent.
3 changes: 2 additions & 1 deletion docs/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ projects.
| [0005](0005-outward-actions-and-draft-prs.md) | Outward actions only on request; pull requests stay drafts until a human QCs them | accepted |
| [0006](0006-guardrails-as-configuration.md) | Guardrails that must hold are configuration and code, not prose | accepted |
| [0007](0007-process-decision-records.md) | Process decisions are recorded as PDRs; the constitution is amended through them | accepted |
| [0008](0008-dispatcher-and-worker-worktrees.md) | The main checkout dispatches; worktrees do the work (optional module) | accepted |
| [0008](0008-dispatcher-and-worker-worktrees.md) | The main checkout dispatches; worktrees do the work (optional module) | accepted; amended by 0015 |
| [0009](0009-optional-modules.md) | Host- and team-specific harness ships as optional modules | accepted |
| [0010](0010-model-aliases.md) | Configure models with version-less aliases | accepted |
| [0011](0011-lanes-ceremony-follows-risk.md) | Three lanes — ceremony follows risk and uncertainty, not size | accepted; partly superseded by 0014 |
| [0012](0012-choose-the-model-by-the-work.md) | Choose the model by the work — Sonnet for well-specified work, Opus for judgment | accepted |
| [0013](0013-adapt-practices-not-a-second-workflow.md) | Adapt practices from other skill collections into our skills — never a second workflow | accepted |
| [0014](0014-test-first-in-every-lane.md) | Test first in every lane | accepted |
| [0015](0015-tool-worktrees-are-workers.md) | Worktrees that Claude Code creates are workers too (parallel-agents module) | accepted |

Changes that follow from these records are listed, with their upgrade impact, in
[`CHANGELOG.md`](../../CHANGELOG.md).
23 changes: 13 additions & 10 deletions docs/scenarios/parallel-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ a second session starts, it gets its own worktree.

| | Main checkout — **dispatcher** | A worktree — **worker** |
|---|---|---|
| Does | Names the task, creates the worktree, hands off (`/dispatch`) | Everything from triage on: spec folder, gate, TDD, commits |
| Does | Routes each task to its worktree — Claude Code's own, or the scripts' through `/dispatch` | Everything from triage on: spec folder, gate, TDD, commits |
| Never | Reads code, analyzes, edits, starts environments | Goes back to the main checkout to work |

The `session-context` hook tells each session its role when it starts.
Expand All @@ -35,7 +35,9 @@ again anyway, where it can check it. The dispatcher's context stays cheap: a bra

### In the main checkout — dispatch

`/dispatch <tracker link>` does four things and nothing else:
A task whose worktree needs the project's setup — here, anything that runs the app — goes through
the scripts. (Other tasks can start in Claude Code's own worktree: see below.) `/dispatch <tracker
link>` does four things and nothing else:

1. **Names the task** — its title and type only; no code reading, no requirements analysis. A short
kebab-case slug. A task that names a delivered feature starts its slug with that feature's
Expand Down Expand Up @@ -92,15 +94,16 @@ running a content-model migration against the shared development environment cha
every other worker's app. Content-model work gets its own Contentful environment, set in that
worktree's env file.

### Tools that create their own worktrees
### Claude Code's own worktrees

Claude Code can create worktrees itself — `claude --worktree`, subagents with `isolation: worktree`,
and some desktop flows such as suggested-task chips — under `.claude/worktrees/`, on branches it
names. They're fine for read-only exploration and isolated subagent work. The project's scripts never
set them up — no copy of its env file, no branch convention — so **don't use them for task work**:
dispatch instead. A session that starts in one is told it has no role, and `worktree-ls.sh` flags
task branches found in them.
`.claude/worktrees/` stays in `.gitignore` and `.claudeignore`.
A task can also start in a new session with Claude Code's worktree option — the desktop app's
worktree toggle, or `claude --worktree` ([0015](../decisions/0015-tool-worktrees-are-workers.md)).
It's a worker like any other: `.worktreeinclude` copies the env file, and the session renames its
generated branch to `<type>/<slug>` after triage. On the newsletter site that's how copy changes and
investigations start. Tasks that run the app go through `/dispatch`, because their worktree needs a
port and the dev server's start command. The session-context hook names what a worktree is missing
when it starts, and `worktree-ls.sh` flags the ones left on a generated branch. Claude Code's
worktrees live under `.claude/worktrees/`, which stays in `.gitignore` and `.claudeignore`.

### Cleanup

Expand Down
4 changes: 3 additions & 1 deletion evals/static/check-skills.sh
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,9 @@ check_practices() {
file_contains "$SKILLS_DIR/triage/SKILL.md" 'Declined before' || missing+=("/triage: declined-before check")
file_contains "$SKELETON/docs/COST-MODEL.md" '^## Between phases' || missing+=("COST-MODEL.md: between phases")
file_contains "$SKILLS_DIR/handoff/SKILL.md" 'never a copy' || missing+=("/handoff: pointers, not copies")
file_contains "$HOOKS_DIR/session-context.sh" 'Role: NONE' || missing+=("session-context.sh: no role in Claude Code's own worktrees")
file_contains "$HOOKS_DIR/session-context.sh" 'branch name is generated' || missing+=("session-context.sh: Claude Code's own worktrees are workers, told what they lack (0015)")
[ -f "$MODULES_DIR/parallel-agents/files/.worktreeinclude" ] || missing+=("parallel-agents: .worktreeinclude")
file_contains "$MODULES_DIR/parallel-agents/files/.claude/skills/dispatch/SKILL.md" '## Which route' || missing+=("/dispatch: which route a task takes")
file_contains "$MODULES_DIR/parallel-agents/files/scripts/agent/worktree.conf" '^PORT_SLOTS=0 ' || missing+=("worktree.conf: ports off by default")
file_contains "$AGENTS_MD" 'write or update the test that asserts the new behavior and watch it fail' || missing+=("AGENTS.md: the fast lane is test-first")
file_contains "$SKELETON/.claude/rules/testing.md" '^## Red, then green — every change, in every lane' || missing+=("testing rule: red then green in every lane")
Expand Down
Loading
Loading