Agents Builders
← Back to Docs

modules/worktrees.md

Worktrees

Parallel agents should use git worktrees rather than sharing one checkout.
The default for any code or active-doc edit is to work in an isolated worktree
with an allocated port.

Current Direction

Avoid visible sibling directories such as turf-monster-feature-name as the long-term default. They make /Users/alex/projects hard to scan at scale.

Preferred layout:

/Users/alex/projects/<repo>                 # primary checkout
/Users/alex/projects/<repo>/.worktrees/<task-slug>

Do not include an agent id in the path. Tasks can transfer between agents, and multiple agents may collaborate on one branch.

Think of worktrees as desks and primary checkouts as loading docks. Agents do
feature work at desks. The primary checkout stays stable for reading,
integration, final merge, and deploy.

Startup Rule

When a new agent session starts actual implementation work:

  1. Identify the target app and the feature being requested. Accumulate acceptance criteria until Mr. McRitchie and the agent are aligned on the goal. If implementation starts with any remaining ambiguity, call it out in the task and handoff.
  2. Create or update the production McRitchie Studio task-board item before editing. The production Task.slug is immutable and generated by the app; use metadata["devops"]["worktree_slug"] for the human-readable feature handle. The task should include acceptance criteria, affected repos, risk tags, expected test_plan, and release-slug metadata when relevant.
  3. Inspect the primary checkout only for status and context.
  4. Run bin/agent-worktree plan <app> <task-slug>.
  5. Run bin/agent-worktree new <app> <task-slug>.
  6. Bind the production task URL with bin/agent-worktree bind-task <app> <task-slug> <task-record-slug-or-url> so whereami, terminal context, snapshots, and PR bodies can lead from the task record.
  7. Move the task to building.
  8. Run bin/agent-worktree up <app> <task-slug> when a browser or local URL is needed.
  9. Make edits only inside /Users/alex/projects/<repo>/.worktrees/<task-slug>.
  10. When the local behavior is ready for Mr. McRitchie to inspect, keep the task in building and mark it for operator validation:
   bin/task update <task-slug> --local-url http://localhost:<port>/<path> --approval waiting

In chat, return the URL in this exact top-level format:

   Task: https://mcritchie.studio/tasks/<task-slug>
   Local Demo: http://localhost:<port>/<path>
   Local Inbox: http://localhost:<port>/_studio/local_emails   # only for email/auth flows

Waiting approval cards float to the top of their stage and pulse on the board.
After approval, set --approval approved; after requested changes, set
--approval changes_requested and keep building.
11. Commit coherent work on the feature branch.
12. Run bin/agent-worktree finish <app> <task-slug> to produce the PR/QA
packet.
13. Update the task with branch, PR URL, local URL, checks_run, and any
changed acceptance criteria. Add a task conversation handoff note with the
change summary, verification, and review focus. Move it to submitted when
the PR is ready for Avi.
14. Return the task URL first, then the PR URL, branch, worktree path, local
URL, tests, and PR/QA recommendation in the handoff. Do not merge to main
unless assigned the QA/Release lane.

Exceptions:

  • Pure read-only audit or exploration can stay in the primary checkout.
  • The explicit deploy owner may use the primary checkout for integration, version bumps, deploy commits, and production rollout.
  • Emergency fixes can use the fastest safe path, but the handoff must say why the worktree path was skipped.

Launcher

Use McRitchie Studio's launcher for new parallel task stacks:

cd /Users/alex/projects/mcritchie-studio
bin/agent-worktree apps
bin/agent-worktree plan turf-monster docs-stack
bin/agent-worktree new turf-monster docs-stack
bin/agent-worktree bind-task turf-monster docs-stack task-abc123def456
bin/agent-worktree up turf-monster docs-stack
bin/agent-worktree finish turf-monster docs-stack

The launcher creates /Users/alex/projects/<repo>/.worktrees/<task-slug>, branches from the current base reforigin/release when the repo has one (the persistent feature-PR target), else origin/main for repos that have not run bin/release init — copies the primary .env, writes .env.agent-stack, prepares the isolated database, and prints the local URL.

Use bin/agent-worktree status <app> <task-slug> to recover the URL later, and bin/agent-worktree down <app> <task-slug> to stop a running stack.
Use bin/agent-worktree finish <app> <task-slug> when the work is committed
and ready for PR/QA handoff.

Lifecycle

Use the launcher as the source of truth for worktree stack state:

bin/agent-worktree list
bin/agent-worktree whereami
bin/agent-worktree bind-task turf-monster task-slug task-abc123def456
bin/agent-worktree shell-hook zsh
bin/agent-worktree status turf-monster task-slug
bin/agent-worktree finish turf-monster task-slug
bin/agent-worktree doctor
bin/agent-worktree snapshot
bin/agent-worktree cleanup
bin/agent-worktree cleanup --reclaim
bin/agent-worktree cleanup --reclaim --yes
bin/agent-worktree remove turf-monster task-slug --yes
bin/agent-worktree scale status
  • list shows task, health, URL, branch, dirty state, merge state, ahead/behind, database, Redis DB, pidfile state, and local inbox URL.
  • bind-task stores the production McRitchie Studio task record slug and URL in the generated stack env, then refreshes .agent-context.json. Pass either task-<hex> or https://mcritchie.studio/tasks/task-<hex>. Use this after the production task exists and before PR handoff so the terminal context and PR body lead from the task record.
  • whereami reads the nearest .agent-context.json marker and prints the app, production task record, task URL, task/worktree slug, branch, local URL, port, Redis DB, database, terminal title, prompt badge, and shell exports. Pass <app> <task-slug> to refresh the marker for a known stack, --json for machine-readable output, or --shell for exports/title commands. The shell form recomputes exports from validated scalar context fields at runtime and ignores any persisted executable shell content in .agent-context.json.
  • shell-hook zsh prints a zsh hook that refreshes AGENT_CONTEXT_* variables and the terminal title whenever the prompt redraws or the working directory changes. It does not edit shell dotfiles; source it explicitly when you want evergreen terminal titles.
  • status shows the detailed state for one generated stack.
  • finish prints a feature graduation packet and PR body. It blocks dirty worktrees, branches with no commits ahead of the base ref, stale branches behind the base ref, and already-merged branches. Add --push to push the branch. --push --pr additionally requires a bound production task record from bind-task, then creates a draft PR based on the base branch (release, else main) through gh when available.
  • doctor reports lifecycle drift such as missing stack env files, reused ports, reused Redis DBs, stale pidfiles, dirty worktrees, disabled local email capture, and clean branches already merged to the base ref. It also reconciles git worktree list against the managed registry per repo and flags any orphan — a git worktree that is neither the primary checkout nor a managed .worktrees/* dir — with its path, branch, and merge/clean state. An orphan whose directory was deleted on disk but is still tracked by git is reported distinctly as prunable (clear it with git -C <repo> worktree prune); doctor and snapshot --write both tolerate it and still exit 0. Orphans are detect-and-report only; removal stays approval-gated (bin/agent-worktree remove).
  • snapshot prints a non-secret JSON registry of every generated worktree, including health, local URLs, branch state, Redis DB, database name, cleanup candidacy, compare URL, and doctor issues. The payload also carries a top-level capacity block (floor, step, current, used, free, physical_max) describing the elastic Redis band.
  • snapshot --write writes the same registry to /Users/alex/projects/.agents/worktree-registry.json for conductor sessions, dashboards, and future automation. Set AGENT_WORKTREE_REGISTRY=/tmp/worktree-registry.json when a sandboxed session needs a scratch write instead of the shared projects registry.
  • cleanup is a dry run. It prints clean worktree candidates whose branch is either contained in the base ref (origin/release, else origin/main) or has an empty final diff against the base ref after a squash merge. A branch merged into release but not yet shipped to main therefore counts as done. Each candidate prints the exact safety class (merged or base-equivalent), base ref, ahead/behind count, stack health, /up code, pidfile state, Redis DB, database state, and the exact bin/agent-worktree remove … --yes command. Use that dry run as the approval packet before deleting anything.
  • cleanup --write appends candidates to ../maintenance/delete-later.md. It does not remove files, worktrees, branches, databases, Redis keys, or processes.
  • cleanup --reclaim is the scale-down-on-close normal flow: a merged worktree self-releases its Redis slot the same way a stack scales down when it closes. The dry run (no --yes) lists only the worktrees that are SAFE to auto-remove — clean and either contained in the base ref or base-equivalent (the same cleanup_ready? criteria as cleanup) — and prints the same safety evidence and removal command as cleanup. It never lists a dirty or unmerged worktree, and the candidate set is sourced from .worktrees/* only, so the primary checkout is never a candidate.
  • cleanup --reclaim --yes runs the same full teardown as remove for each safe candidate (stop the stack, flush the stack's Redis DB, update the cleanup ledger, remove the Git worktree, delete the stale local branch), re-verifying each candidate under the worktree lock so one that turned dirty/unmerged in the interim is skipped. After the batch it shrinks the Redis band toward the floor (maybe_scale_in) and refreshes the registry once. Output names each reclaimed worktree, the freed Redis DB, and the resulting band size. Safe to re-run; with no candidates it prints a clear no-op message and changes nothing.
  • remove <app> <task-slug> --yes is the approved deletion path after Mr. McRitchie or the conductor authorizes cleanup. It refuses dirty or non-equivalent worktrees, stops the stack, flushes the stack's Redis DB (so a reused DB number cannot inherit stale keys), updates the cleanup ledger, removes the Git worktree, deletes the stale local branch, shrinks the Redis band toward the floor when slots free up, and refreshes the registry.
  • scale status prints the Redis band: floor, step, current band + DB range, used, free, and the physical ceiling (databases from Redis). scale out / scale in are manual nudges (respect floor and physical ceiling). scale --provision [--yes] raises the physical databases and restarts Redis once; see Scale Note.

Deletion remains approval-gated:

  1. Run bin/agent-worktree cleanup <app> to see candidates.
  2. Confirm bin/agent-worktree doctor <app> has no dirty or unique-work warnings for the target.
  3. After approval, run bin/agent-worktree remove <app> <task-slug> --yes.
  4. Run bin/qa-intake --refresh --apps <apps> so the conductor view no longer reports the removed worktree.

Squash-Merge Cleanup

GitHub squash merges do not preserve the feature branch SHA on the base. After a
PR lands, a branch can appear behind its base ref even when all of its content
was merged. Do not rely on ahead/behind alone.

The launcher now treats an empty final diff against the base ref
(origin/release, else origin/main) as a cleanup candidate. Before removing a
squash-merged worktree manually (substitute origin/main for repos without a
release branch):

  1. Pull the primary checkout so origin/release is current.
  2. From the feature worktree, confirm the final diff is empty:
   git diff --stat origin/release..HEAD
   git diff --name-status origin/release..HEAD
  1. If both commands are empty, prefer bin/agent-worktree remove <app> <task-slug> --yes after approval.
  2. If the diff is not empty, do not delete. The branch contains work not represented on the base; send it back through PR/QA or salvage deliberately.

Rules

  • Branch from the base ref (feature PRs target the persistent release branch, not main). This is now the launcher default: bin/agent-worktree new cuts from origin/release, finish --pr opens the PR with --base release, and ahead/behind + cleanup/merge checks all reckon against origin/release.
    • Fallback: a repo that has not run bin/release init has no origin/release, so the launcher transparently falls back to origin/main for the branch cut, the PR base, and the comparison base. The resolved base is reported per-worktree (base_ref in snapshot/registry, the base: line in finish). bin/release merge still rejects any PR whose base isn't release, so a stray main-based PR is caught at merge time.
  • One task branch per worktree.
  • Never commit task work on the primary main checkout unless you are the explicit deploy owner for that repo.
  • A feature branch is the backup and collaboration unit. release is the reviewed integration lane (main is the shipped lane, fast-forwarded from release at ship time), not a place to rush code so it is not lost.
  • Feature agents push their branch and open/prepare a PR into release. Avi or the designated release conductor owns merging to release (and shipping release → main).
  • If the primary checkout is dirty, ahead, or moves while you are working, treat it as shared-floor drift. Do not fold those changes into your task silently. Report it and continue from the isolated worktree.
  • Do not remove a worktree until its branch/PR status is known.
  • Log stale worktrees in ../maintenance/delete-later.md before deleting them.

Multi-Agent Safety & Merge Patterns

When several agents build in parallel and their work converges on one branch
— e.g. scaffolding a new app: Rolio's first cut was 4 gap features by 4 agents
merged together — isolation and merge discipline matter more than usual. The
patterns below came out of that run.

Isolation — use a manual git worktree add per agent.

  • The Agent tool's isolation: worktree mode was unreliable when the target repo differs from the session repo — agents leaked their edits onto the main checkout instead of an isolated worktree. For cross-repo or multi-agent fan-out, give each agent an explicit git worktree add -b <branch> .worktrees/<slug> <base> and point it at that absolute path.
  • One branch per worktree; never let two agents share a checkout (branches switch under you and files mutate mid-edit — see the "parallel sessions in shared worktree" lessons).

Design the work to merge cleanly — new-files-first.

  • Prefer new files over editing shared ones. A feature that lands as its own partial, service, model, or stylesheet never conflicts.
  • Shared view edits = a new partial + a single <%= render %> line at a named anchor. Each agent adds only its one render line at the agreed anchor; the body lives in the agent's own partial. Conflicts shrink to one predictable line.
  • Routes: additive blocks. Each agent appends its own routes block (ideally a namespace/scope of its own) rather than editing a shared resource line.
  • One migration owner per integration. Two agents writing migrations against the same tables will collide on schema.rb. Either nominate a single migration owner, or have the orchestrator pre-add the columns before fanning out so the agents only read them.

The recurring conflict: the CSS end-of-file seam.

  • Multiple agents appending styles to the same stylesheet conflict at the end-of-file. This one is expected; resolve it by keeping both blocks — the changes are additive by construction. Don't agonize over it, concatenate.

Merge sequentially, suite green between merges.

  • Integrate one branch at a time and run the full suite green between each merge — never batch-merge several agents' branches and test once at the end. A red suite after a sequential merge points at exactly one branch.

This is the multi-agent build path referenced by
../system/new-app-onboarding-sop.md.

Handoff Contract

A feature-agent handoff should include:

  • McRitchie Studio task URL first, then task slug, current stage, and acceptance criteria status.
  • App, task slug, branch, and worktree path.
  • Local review URL and local inbox URL when a server was started.
  • PR URL or the exact reason a PR was not opened.
  • QA-intake status when available, but do not use bin/qa-intake as a substitute for the task-board record.
  • Task conversation status: whether any qa_feedback remains open in practice, and the latest handoff note the feature agent added.
  • Tests/checks run and their result in task devops.checks_run.
  • Files or behavior changed at a high level.
  • The bin/agent-worktree finish result.
  • Whether the branch is ready for Avi review, needs another agent, or needs release-conductor integration.

Do not leave Mr. McRitchie with "run these commands." Start the stack, prove the
URL, and name any blocker that truly needs owner action.

Machine Registry

bin/agent-worktree snapshot --write is the local registry for scale. It does
not contain secrets and is safe to hand to another agent session as current
machine context.

Use it when:

  • a QA or release conductor starts a shift
  • multiple feature agents are active at once
  • worktree ports, Redis DBs, or pidfiles appear inconsistent
  • a dashboard or future supervisor needs a machine-readable queue

The registry is intentionally local under /Users/alex/projects/.agents/.
McRitchie Studio documents and owns the format, but the file itself reflects the
current machine and should not be treated as a Git-tracked source of truth.
If the agent runtime blocks writing to that directory, rerun the command with
filesystem approval or set AGENT_WORKTREE_REGISTRY to a writable scratch path.

Terminal Context Markers

The launcher writes a non-secret .agent-context.json file into every generated
worktree during new, and refreshes it during up, status, and
whereami <app> <task-slug>. The marker is added to the repository's local
Git exclude file so it does not show as untracked work or enter commits.

Use it from inside a generated worktree:

bin/agent-worktree whereami
bin/agent-worktree whereami --json
bin/agent-worktree whereami --shell

If the current directory is nested below the worktree root, call the launcher by
absolute path or from the shell PATH; whereami walks upward until it finds
the marker.

Bind the production task record once the task exists:

bin/agent-worktree bind-task <app> <task-slug> task-abc123def456
bin/agent-worktree bind-task <app> <task-slug> https://mcritchie.studio/tasks/task-abc123def456

After binding, the marker carries both the human-readable worktree slug and the
generated production task record. whereami --shell exports the task record as
AGENT_CONTEXT_TASK_RECORD, the browser URL as AGENT_CONTEXT_TASK_URL, and
the human slug as AGENT_CONTEXT_WORKTREE_SLUG. The prompt badge includes the
production task slug when available. The shell output is generated from scalar
marker fields each time; agents must not store or trust executable shell lines
inside .agent-context.json.

For an evergreen terminal title in zsh, source the generated hook:

eval "$(/Users/alex/projects/mcritchie-studio/bin/agent-worktree shell-hook zsh)"

The hook exports:

  • AGENT_CONTEXT_APP
  • AGENT_CONTEXT_TASK
  • AGENT_CONTEXT_WORKTREE_SLUG
  • AGENT_CONTEXT_TASK_RECORD
  • AGENT_CONTEXT_TASK_URL
  • AGENT_CONTEXT_PORT
  • AGENT_CONTEXT_URL
  • AGENT_CONTEXT_TITLE
  • AGENT_CONTEXT_BADGE

The hook updates the terminal title automatically. Shell prompt customization
can include $AGENT_CONTEXT_BADGE wherever the operator wants the badge to
appear.

Ports

Worktree servers must use a non-primary port from the app's reserved range. See ports-and-processes.md.

Worktree Stack Requirements

Worktree tooling should make parallel stacks "just work" without user terminal chores.

Each worktree stack needs its own:

  • App-range port (3101, 3102, etc. for Turf Monster).
  • Redis DB for Sidekiq and cache. The launcher allocates globally across generated stack env files from an elastic band starting at DB 9 (see Scale Note).
  • Development database via DATABASE_URL.
  • Isolated test database (<app>_test_<slug>), provisioned by bin/agent-worktree new and pinned via TEST_DATABASE_URL in .env.test.local (see Running tests).
  • Session cookie key.
  • APP_PORT so magic links point at the stack.
  • LOCAL_EMAIL_CAPTURE=1 so mail is recorded locally instead of sent.
  • Ruby PATH guard when the repo requires a non-system Ruby.

Do not let two Sidekiq processes share one Redis DB while pointing at different databases. A job enqueued by one stack can be processed by the other stack and silently mutate the wrong records.

Worktree magic links are local-first through /_studio/local_emails. The central launcher writes LOCAL_EMAIL_CAPTURE=1, blanks provider mail credentials in .env.agent-stack, and prints the inbox URL next to the app URL. Agents should request the magic link in the UI, then open:

http://localhost:<port>/_studio/local_emails

The inbox shows recent outbox rows and proof links such as magic-link sign-in URLs. Worktree stacks should not email real recipients unless the task is specifically testing real delivery. For provider tests, intentionally set LOCAL_EMAIL_CAPTURE=0 and restore the needed mail credentials in that stack env.

Callback-heavy flows such as Stripe, Google OAuth, CDP/MoonPay, webhooks, and emailed magic links stay on the primary port unless the provider and local listener are configured for the worktree port.

Running tests

bin/agent-worktree new provisions an isolated test database (<app>_test_<slug>) and writes .env.test.local with TEST_DATABASE_URL pointing at it. config/database.yml's test.url reads TEST_DATABASE_URL, and an explicit url: wins over DATABASE_URL — so a plain bin/rails test resolves to the isolated test DB out of the box:

bin/rails test           # auto-resolves to <app>_test_<slug>; no manual prep
bin/agent-worktree test mcritchie-studio <slug>   # same, single-process + hermetic env

Do not source .env.agent-stack before running tests. You do not need the dev DATABASE_URL to test (the test DB resolves on its own), and sourcing it sets AGENT_WORKTREE=1/LOCAL_EMAIL_CAPTURE=1, which routes mail into the local capture store and diverges email-delivery tests from CI. Source .env.agent-stack only for dev-DB chores like bin/rails runner seeding. bin/agent-worktree test sidesteps this with a hermetic env (correct Ruby PATH, RAILS_ENV=test, single-process to avoid the parallel worker-DB clone deadlock on a cold test DB).

Scale Note

Redis capacity has two layers:

  • Physical capacity is the Redis databases setting. It is fixed at Redis startup; changing it needs a restart. Stock Redis exposes 0-15 (16 DBs).
  • Soft band is the slot range the launcher allocates from, starting at DB 9. It is elastic and restart-free within the physical ceiling.

The band idles at 20 slots (FLOOR) and changes by 10 (STEP):

  • Scale-out (auto): when the band is full and physical room remains, allocate_redis_db grows the band by 10 (scaled out: 20 -> 30 slots) and retries. No restart. At the physical ceiling it aborts with guidance to run cleanup or scale --provision.
  • Scale-in (auto): remove, cleanup --write, and cleanup --reclaim --yes drop the band by 10 (never below the floor, never stranding a still-used DB) as slots free up (scaled in: 30 -> 20 slots). No restart. cleanup --reclaim --yes is the hands-off scale-down-on-close path: it releases every safe candidate, then calls maybe_scale_in once for the batch.

The band size is persisted in /Users/alex/projects/.agents/redis-capacity.json
and band allocation + capacity mutation are guarded by a flock on
/Users/alex/projects/.agents/agent-worktree.lock so concurrent new/up
runs cannot collide.

Inspect the band with bin/agent-worktree scale status. To realize the full
20-slot floor you need physical databases >= 29 (DB 9 band start + 20). The
band caps band hand-outs at the physical ceiling, so on stock Redis (16 DBs)
only DBs 9-15 are usable until you provision.

To raise physical capacity (one-time, target databases 64):

bin/agent-worktree scale --provision         # interactive confirm
bin/agent-worktree scale --provision --yes   # skip the prompt

This edits the brew redis.conf ($(brew --prefix)/etc/redis.conf, overridable
with AGENT_REDIS_CONF) and restarts Redis exactly once. It is idempotent
(no-op when databases is already at/above target). The restart bounces every
running worktree stack
on localhost:6379, so it belongs to the QA/infra lane
during a quiet window, never mid-session while other stacks are live.

Overrides: AGENT_REDIS_FLOOR, AGENT_REDIS_STEP,
AGENT_REDIS_PHYSICAL_TARGET, and the legacy AGENT_REDIS_MAX_DB (pins the band
top explicitly). Do not set band overrides past what Redis actually serves.