worktree
v0.2.4Git worktree lifecycle for parallel agents — mint, claim, and track isolated worktrees per repo, emit lifecycle trigger types, and land branches back through a per-repo FIFO queue (rebase, test gate, ff-only merge).
- macOS: arm64 · x64
- Linux: arm64 · armv7 · x64
- Windows: arm64 · x64 · x86
exact versions are immutable; binary and bundle artifacts are digest-pinned.
skill doc
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 returnedpathas 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::createwithpr:fetchesrefs/pull/from/head originand branches at it). - A finished branch should merge back into
main(or any target) with tests enforced first (worktree::landwithtest_cmd). - A land was blocked on conflicts and the agent resolved them in place:
finish the rebase, then rerun
worktree::land; passforce_restartto 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::createare 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
shellworker with the worktree path ascwd; 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 forsession_idwhen given, names the branch by id or deterministic codename per config, and returns an advisorydev_portderived 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 (forceto take over).worktree::release— release ownership (forceto override).worktree::status— clean flag, ahead/behind, staged/unstaged/untracked counts, diffstat, rebase-in-progress, andintegratedwith anintegration_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 ajob_idimmediately.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-blockedwithreasonandconflict_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.