worktree
The worktree worker gives every agent its own isolated checkout of a shared
repository. Instead of two sessions fighting over one working tree,
worktree::create mints a locked git worktree on a fresh branch, records
who owns it, and returns a path to use as the agent's working directory.
When the work is done, worktree::land automates the merge-back: rebase
onto the target branch, run an optional test command, fast-forward the
target atomically, and clean the worktree up. Lands are serialized per
repository through an engine FIFO queue, so parallel agents never race a
merge.
The land test gate delegates to shell::exec, so the shell worker must be
installed and worktree_root must sit inside its fs.host_roots jail.
Landing never pushes to remotes; it moves local branches only.
Destructive surfaces are gated by configuration: every mutating function
checks its gate first, the force paths (claim takeover, forced removal,
land force_restart) ship closed, and a denial names the exact config key
to flip. The path worktree::create returns doubles as the turn's
filesystem scope root (metadata.fs_scope.root), so file access inside the
worktree is fenced by the shell worker while the gates cover the lifecycle
door the scope cannot see (remove, prune, branch deletion, landing).
When to Use
- A task should run in isolation from the primary checkout or from other
agents working on the same repo (
worktree::create, then use the
returned path as the agent's working directory).
- You need to see which worktrees exist, who owns them, and whether they
carry uncommitted or unlanded work (
worktree::list,
worktree::status).
- A GitHub pull request should be reviewed or exercised in isolation
(
worktree::create with pr: <number> fetches refs/pull/<n>/head
from origin and branches at it).
- A finished branch should merge back into
main (or any target) with
tests enforced first (worktree::land with test_cmd).
- A land was blocked on conflicts and the agent resolved them in place:
finish the rebase, then rerun
worktree::land; pass force_restart to
abort and start over instead.
- Hand a worktree between sessions (
worktree::claim,
worktree::release) or clean up abandoned ones (worktree::remove,
worktree::prune).
Boundaries
- Not a PR or code-review tool: landing fast-forwards a local branch; it
never pushes, opens pull requests, or talks to a forge.
- Worktrees created out of band are reported as unmanaged and never
adopted; only worktrees minted by
worktree::create are managed.
- One worker instance per engine: the registry has no compare-and-set, so
multi-instance deployments must shard by repository.
- Merges are fast-forward only (after the rebase); there is no merge-commit
or squash strategy.
- For running commands or editing files inside a worktree, use the
shell
worker with the worktree path as cwd; this worker only manages the
worktrees themselves.
Functions
worktree::create — mint a locked, isolated worktree off a base ref or
a pull request head (pr); auto-claims for session_id when given,
names the branch by id or deterministic codename per config, and returns
an advisory dev_port derived from the id (never reserved anywhere).
worktree::list — registry view, filterable by repo or session, with
optional git status per worktree.
worktree::get — one worktree with status.
worktree::validate — check a path is a live managed worktree;
reconciles records whose directories were removed by hand.
worktree::claim — take session ownership (force to take over).
worktree::release — release ownership (force to override).
worktree::status — clean flag, ahead/behind, staged/unstaged/untracked
counts, diffstat, rebase-in-progress, and integrated with an
integration_reason, so squash- or rebase-landed branches read as
merged even while ahead of their base.
worktree::remove — remove a worktree; refuses dirty or unlanded work
unless forced, and refuses while running processes hold files open under
it (W222); the directory leaves its path instantly (staged into a
trash area, deleted in the background); can delete the branch.
worktree::prune — sweep: drop records whose directories are gone and
remove clean, unclaimed, expired worktrees, including integrated ones
whose work already landed (cron-bound, {} payload).
worktree::land — queue the rebase / test / fast-forward / cleanup
pipeline; returns a job_id immediately.
worktree::land-step — internal queue consumer that executes land
phases; never call it directly (denied to agents).
With provision.copy_ignored enabled in config, every create also
replicates the source repo's gitignored files (.env files, caches) into
the new worktree in the background; the create response never waits on it.
Errors carry stable W### codes; the ones worth branching on are W210
(already claimed), W220 (dirty), W221 (unmerged work), W222 (files
held open by running processes), W401 (land already queued), W402
(unresolved rebase from a previous land), and the land-block reasons
carried on events (W410 conflict, W411 tests, W412 target kept
moving, W413 target checked out dirty). W5xx means configuration
denied the operation, not that it failed: W500 gate off, W501 force
disabled, W502 land target not in gates.land_targets, W503
repository not in gates.repos, W504 per-repo worktree budget hit. The
message names the exact key; do not retry, surface that key to the
operator (or drop the force flag) instead.
Reactive triggers
Register a worktree::* trigger when a different worker should react to
lifecycle changes without polling worktree::list — announce lands in
chat, start a follow-up agent when a sibling's branch merges, or alert on
blocked lands.
Reach for it when:
- A land's outcome should drive the next step (
worktree::landed,
worktree::land-blocked with reason and conflict_files).
- Ownership changes matter to an orchestrator (
worktree::claimed,
worktree::released).
- Cleanup should cascade (
worktree::removed).
The caller of worktree::land gets only { job_id, queued } back; the
outcome arrives on these triggers, so a landing workflow should always bind
one instead of polling.
How to bind
- Register a handler:
registerFunction('notify::on-land', handler).
- Register the trigger:
iii.registerTrigger({
type: 'worktree::landed',
function_id: 'notify::on-land',
config: {
// optional equality filters:
// repo_path, worktree_id, session_id
},
})
All six types accept the same three optional filters; unknown config keys
are rejected at registration. For event payload shapes, call
get function info on the trigger type.
1---2name: worktree3description: Mint isolated git worktrees for parallel agent work, track ownership in a cross-agent registry, and land finished branches back onto a target with a queued rebase, test gate, and atomic fast-forward merge.4---56# worktree78The worktree worker gives every agent its own isolated checkout of a shared9repository. Instead of two sessions fighting over one working tree,10`worktree::create` mints a locked `git worktree` on a fresh branch, records11who owns it, and returns a path to use as the agent's working directory.12When the work is done, `worktree::land` automates the merge-back: rebase13onto the target branch, run an optional test command, fast-forward the14target atomically, and clean the worktree up. Lands are serialized per15repository through an engine FIFO queue, so parallel agents never race a16merge.1718The land test gate delegates to `shell::exec`, so the shell worker must be19installed and `worktree_root` must sit inside its `fs.host_roots` jail.20Landing never pushes to remotes; it moves local branches only.2122Destructive surfaces are gated by configuration: every mutating function23checks its gate first, the force paths (claim takeover, forced removal,24land `force_restart`) ship closed, and a denial names the exact config key25to flip. The path `worktree::create` returns doubles as the turn's26filesystem scope root (`metadata.fs_scope.root`), so file access inside the27worktree is fenced by the shell worker while the gates cover the lifecycle28door the scope cannot see (remove, prune, branch deletion, landing).2930## When to Use3132- A task should run in isolation from the primary checkout or from other33 agents working on the same repo (`worktree::create`, then use the34 returned `path` as the agent's working directory).35- You need to see which worktrees exist, who owns them, and whether they36 carry uncommitted or unlanded work (`worktree::list`,37 `worktree::status`).38- A GitHub pull request should be reviewed or exercised in isolation39 (`worktree::create` with `pr: <number>` fetches `refs/pull/<n>/head`40 from `origin` and branches at it).41- A finished branch should merge back into `main` (or any target) with42 tests enforced first (`worktree::land` with `test_cmd`).43- A land was blocked on conflicts and the agent resolved them in place:44 finish the rebase, then rerun `worktree::land`; pass `force_restart` to45 abort and start over instead.46- Hand a worktree between sessions (`worktree::claim`,47 `worktree::release`) or clean up abandoned ones (`worktree::remove`,48 `worktree::prune`).4950## Boundaries5152- Not a PR or code-review tool: landing fast-forwards a local branch; it53 never pushes, opens pull requests, or talks to a forge.54- Worktrees created out of band are reported as unmanaged and never55 adopted; only worktrees minted by `worktree::create` are managed.56- One worker instance per engine: the registry has no compare-and-set, so57 multi-instance deployments must shard by repository.58- Merges are fast-forward only (after the rebase); there is no merge-commit59 or squash strategy.60- For running commands or editing files inside a worktree, use the `shell`61 worker with the worktree path as `cwd`; this worker only manages the62 worktrees themselves.6364## Functions6566- `worktree::create` — mint a locked, isolated worktree off a base ref or67 a pull request head (`pr`); auto-claims for `session_id` when given,68 names the branch by id or deterministic codename per config, and returns69 an advisory `dev_port` derived from the id (never reserved anywhere).70- `worktree::list` — registry view, filterable by repo or session, with71 optional git status per worktree.72- `worktree::get` — one worktree with status.73- `worktree::validate` — check a path is a live managed worktree;74 reconciles records whose directories were removed by hand.75- `worktree::claim` — take session ownership (`force` to take over).76- `worktree::release` — release ownership (`force` to override).77- `worktree::status` — clean flag, ahead/behind, staged/unstaged/untracked78 counts, diffstat, rebase-in-progress, and `integrated` with an79 `integration_reason`, so squash- or rebase-landed branches read as80 merged even while ahead of their base.81- `worktree::remove` — remove a worktree; refuses dirty or unlanded work82 unless forced, and refuses while running processes hold files open under83 it (`W222`); the directory leaves its path instantly (staged into a84 trash area, deleted in the background); can delete the branch.85- `worktree::prune` — sweep: drop records whose directories are gone and86 remove clean, unclaimed, expired worktrees, including integrated ones87 whose work already landed (cron-bound, `{}` payload).88- `worktree::land` — queue the rebase / test / fast-forward / cleanup89 pipeline; returns a `job_id` immediately.90- `worktree::land-step` — internal queue consumer that executes land91 phases; never call it directly (denied to agents).9293With `provision.copy_ignored` enabled in config, every create also94replicates the source repo's gitignored files (.env files, caches) into95the new worktree in the background; the create response never waits on it.9697Errors carry stable `W###` codes; the ones worth branching on are `W210`98(already claimed), `W220` (dirty), `W221` (unmerged work), `W222` (files99held open by running processes), `W401` (land already queued), `W402`100(unresolved rebase from a previous land), and the land-block reasons101carried on events (`W410` conflict, `W411` tests, `W412` target kept102moving, `W413` target checked out dirty). `W5xx` means configuration103denied the operation, not that it failed: `W500` gate off, `W501` force104disabled, `W502` land target not in `gates.land_targets`, `W503`105repository not in `gates.repos`, `W504` per-repo worktree budget hit. The106message names the exact key; do not retry, surface that key to the107operator (or drop the force flag) instead.108109## Reactive triggers110111Register a `worktree::*` trigger when a different worker should react to112lifecycle changes without polling `worktree::list` — announce lands in113chat, start a follow-up agent when a sibling's branch merges, or alert on114blocked lands.115116Reach for it when:117118- A land's outcome should drive the next step (`worktree::landed`,119 `worktree::land-blocked` with `reason` and `conflict_files`).120- Ownership changes matter to an orchestrator (`worktree::claimed`,121 `worktree::released`).122- Cleanup should cascade (`worktree::removed`).123124The caller of `worktree::land` gets only `{ job_id, queued }` back; the125outcome arrives on these triggers, so a landing workflow should always bind126one instead of polling.127128### How to bind1291301. Register a handler: `registerFunction('notify::on-land', handler)`.1312. Register the trigger:132133```typescript134iii.registerTrigger({135 type: 'worktree::landed',136 function_id: 'notify::on-land',137 config: {138 // optional equality filters:139 // repo_path, worktree_id, session_id140 },141})142```143144All six types accept the same three optional filters; unknown config keys145are rejected at registration. For event payload shapes, call146`get function info` on the trigger type.