# worktree

> Git 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).

| field | value |
|-------|-------|
| version | 0.2.4 |
| type | binary |
| repo | https://github.com/iii-hq/workers |
| supported_targets | x86_64-apple-darwin, aarch64-apple-darwin, i686-pc-windows-msvc, x86_64-pc-windows-msvc, aarch64-pc-windows-msvc, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, armv7-unknown-linux-gnueabihf |
| author | iii |

## installation

```sh
iii worker add worktree@0.2.4
```

## dependencies

- `configuration` @ `^0.21.6`
- `state` @ `^0.21.2`
- `shell` @ `^0.7.0`

## readme

# worktree

Git worktree lifecycle for parallel agents. `worktree::create` mints an
isolated, locked worktree off any ref and registers it in a cross-agent
registry; agents work inside it through their turn's filesystem scope
(`metadata.fs_scope.root`); `worktree::land`
rebases the branch onto a target, runs an optional test gate through the
[shell](https://github.com/iii-hq/workers/tree/main/shell) worker, fast-forwards the target with an atomic
compare-and-swap, and cleans up. Every lifecycle change emits a trigger type
sibling workers can subscribe to, so a Slack bot can announce lands or a
harness can react when a sibling's branch merges.

## Install

```bash
iii worker add worktree
```

`iii worker add` fetches the binary, writes a config block into
`~/.iii/config.yaml`, and the engine starts the worker on the next `iii`
boot. The land test gate delegates to `shell::exec`, so install the shell
worker alongside it:

```bash
iii worker add shell
```

## Quickstart

```rust
use iii_sdk::{register_worker, InitOptions};
use iii_sdk::protocol::TriggerRequest;
use serde_json::json;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let iii = register_worker("ws://localhost:49134", InitOptions::default());

    // 1. Mint an isolated worktree and hand its path to an agent.
    let wt = iii.trigger(TriggerRequest {
        function_id: "worktree::create".into(),
        payload: json!({ "repo_path": "/home/me/project", "session_id": "sess_1" }),
        action: None,
        timeout_ms: Some(30_000),
    }).await?;
    println!("agent fs_scope root: {}", wt["path"]);

    // 2. ... the agent commits work inside the worktree ...

    // 3. Land it: rebase onto main, gate on tests, ff-merge, clean up.
    let job = iii.trigger(TriggerRequest {
        function_id: "worktree::land".into(),
        payload: json!({
            "worktree_id": wt["worktree_id"],
            "target_branch": "main",
            "test_cmd": "cargo test",
        }),
        action: None,
        timeout_ms: Some(30_000),
    }).await?;
    println!("queued land job: {}", job["job_id"]);
    Ok(())
}
```

The land completes asynchronously; subscribe to `worktree::landed` /
`worktree::land-blocked` (below) or poll `worktree::get` for the outcome.

## Configuration

Runtime settings live in the `configuration` worker under id `worktree` and
hot-reload on change (a `prune_schedule` change re-binds the cron live).

```yaml
worktree_root: "~/.iii/worktrees"   # where managed worktrees are created
branch_prefix: "iii/"               # auto-minted branch names
branch_naming: "id"                 # or "codename" for <adjective>-<noun>-<4hex> names
prune_schedule: "0 0 * * * *"       # six-field cron for the reconcile sweep
prune_expire_hours: 72              # idle hours before a clean, unclaimed worktree is prunable
land_queue: "worktree-land"         # engine queue name for land jobs
max_land_retries: 3                 # rebase retries when the target branch moves mid-land
git_timeout_ms: 60000               # per-git-subprocess bound
test_timeout_ms: 600000             # land test gate bound
provision:
  copy_ignored: false               # replicate gitignored files into new worktrees
  include: []                       # globs; empty = every ignored path
  exclude: []                       # globs subtracted from the copy
  max_copy_bytes: 2147483648        # total budget; entries beyond it are skipped
gates:
  allow_remove: true                # worktree::remove
  allow_force: false                # remove force, claim/release force, land force_restart
  allow_branch_delete: true         # remove delete_branch and the land finalize cleanup
  allow_land: true                  # worktree::land
  allow_prune: true                 # worktree::prune (including the cron sweep)
  land_targets: ["*"]               # globs; pin to ["main"] to allow only mainline lands
  repos: ["*"]                      # globs over canonical repo paths worktrees may branch from
  max_worktrees_per_repo: 0         # live-worktree budget per repo at create time; 0 = unlimited
```

### Copy-ignored provisioning

With `provision.copy_ignored: true`, every create spawns a background task
that replicates the source repo's gitignored files (.env files, caches,
local settings) into the new worktree: cheapest copy per platform (APFS
clones via `cp -c` with a plain fallback on macOS, `--reflink=auto` on
Linux), VCS metadata directories and the managed worktree root always
excluded, bounded by `max_copy_bytes`. Provisioning is best-effort by
design: the create response never waits on it, failures only log, and a
retried copy skips files that already landed.

### Pull request checkouts, dev ports, integration

`worktree::create` also takes `pr: <number>`: it fetches
`refs/pull/<n>/head` from `origin` (GitHub layout) and branches
`<prefix>pr-<n>` at it, so reviewing a PR gets its own isolated checkout.
Every worktree carries an advisory `dev_port` (10000 + hash of the id mod
10000): deterministic, collision-improbable across parallel worktrees, and
never reserved anywhere; point dev servers at it to avoid port fights.
`worktree::status` reports `integrated` with an `integration_reason`
(`same_commit`, `ancestor`, `no_added_changes`, `trees_match`,
`merge_adds_nothing`, `patch_id_match`) so squash- or rebase-landed branches
read as merged, and `worktree::prune` removes integrated-but-ahead worktrees
instead of holding them forever.

### Removal and trash

`worktree::remove` never blocks on a large directory delete: the worktree
is renamed into a `.trash` staging area under `worktree_root` (instant),
the git admin entry is pruned, and a detached task deletes the staged
directory in the background; if the rename fails the removal falls back to
a synchronous `git worktree remove`. Entries stranded by a crash are
cleared by the next prune's trash sweep, which runs even when
`gates.allow_prune` is off (it is remove's crash recovery, not part of
prune's destructive surface). Unforced removals also probe for processes
holding files open under the worktree and refuse with `W222` rather than
deleting a directory out from under a running dev server.

### Gates

Every mutating handler checks its gate before anything else, so an operator
can turn off whole classes of destruction with one config flip and the
change hot-reloads. Denials are structured: `W500` (operation disabled),
`W501` (force paths disabled), `W502` (land target not allowed), and every
message names the exact key to flip, e.g. `worktree::remove is disabled by
configuration; set gates.allow_remove: true to enable`. Force is the one
gate that ships closed: takeovers, forced removals, and land restarts are
opt-in.

Creation is gated too: `gates.repos` pins which repositories worktrees may
branch from (globs over the canonicalized path, `W503` otherwise), and
`gates.max_worktrees_per_repo` caps live worktrees per repository at create
time (`W504` names the current count; orphaned records do not count, and
narrowing either gate later never breaks existing worktrees).

Gates constrain this worker's own surface only. Restricting what an agent
can run in a shell (`rm`, `git branch -D`, ...) is the shell worker's
allowlist/denylist job; pair both for defense in depth.

### Filesystem scope interplay

Agents are scoped through `metadata.fs_scope.root` on the turn: the console
picker (or `harness::send` options) sets it to the worktree path, and the
harness stamps `fs_scope: { root, grants }` onto every scoped `shell::*` /
`coder::*` call the agent makes. The land test gate does the same directly:
it runs `shell::exec` with the worktree as `cwd` and as the `fs_scope` root,
so the shell worker enforces the scope for gated tests exactly as it does
for agent calls.

`worktree_root` must still resolve inside the shell worker's
`fs.host_roots`: a root outside the jail fails with `S215` (a path inside
the jail but outside the scope root fails with `S220`), and the test gate
surfaces the `S215` misconfiguration as a `W411` land block naming it.

### Engine queue config

Land jobs serialize per repository through an engine FIFO queue. Add this
block to the engine config (the `repo_key` group field is carried on every
job payload):

```yaml
queue_configs:
  worktree-land:
    type: fifo
    message_group_field: repo_key
    concurrency: 1
    max_retries: 2
```

Without it the queue still delivers, but ordering falls back to this
worker's in-process per-repo lock, which only holds within a single
instance.

## Functions

- `worktree::create` — mint a locked worktree off a ref; auto-claims when
  `session_id` is given.
- `worktree::list` — registry view with filters and optional git status;
  reports unmanaged worktrees of a repo without adopting them.
- `worktree::get` — one worktree with status.
- `worktree::validate` — picker-style path validation; reconciles records
  whose directories are gone.
- `worktree::claim` / `worktree::release` — session ownership with `force`
  takeover and `W210`/`W211` mismatch errors.
- `worktree::status` — clean, ahead/behind, staged/unstaged/untracked,
  diffstat, rebase-in-progress.
- `worktree::remove` — guarded removal (`W220` dirty, `W221` unmerged,
  `W222` busy), instant trash staging with background delete, optional
  branch deletion.
- `worktree::prune` — reconcile sweep, cron-bound; `{}` payload works.
- `worktree::land` — queue a land job; returns `{ job_id, queued }`.
- `worktree::land-step` — internal queue consumer; not agent-callable.

Errors carry stable `W###` codes (`src/error.rs`). One instance per engine:
iii-state has no compare-and-set, so run a single worktree worker (shard by
repo if you need more).

## Custom trigger types

| Trigger type | Fires when | Payload highlights |
|---|---|---|
| `worktree::created` | A worktree was minted | `worktree_id`, `path`, `branch`, `base_sha` |
| `worktree::claimed` | A session took ownership | `session_id`, `previous_session_id` |
| `worktree::released` | A claim was released | `session_id` |
| `worktree::removed` | A worktree was removed or pruned | `branch_deleted` |
| `worktree::landed` | A land fast-forwarded the target | `target_branch`, `merged_sha` |
| `worktree::land-blocked` | A land ended blocked | `reason`, `code`, `conflict_files`, `exit_code` |

Bindings accept optional equality filters: `repo_path`, `worktree_id`,
`session_id`.

```rust
use iii_sdk::protocol::RegisterTriggerInput;

iii.register_trigger(RegisterTriggerInput {
    trigger_type: "worktree::landed".to_string(),
    function_id: "notify::on-land".to_string(),
    config: serde_json::json!({ "repo_path": "/home/me/project" }),
    metadata: None,
})?;
```

## Conflict resolution flow

When a land's rebase hits conflicts, the rebase is left IN PROGRESS inside
the worktree and a `worktree::land-blocked` event fires with
`reason: "rebase_conflict"` and the conflicted paths. The owning agent
resolves in place (its filesystem scope already roots at the worktree),
finishes with `git rebase --continue`, and reruns `worktree::land`. A rerun
while the rebase is still unresolved returns `W402`; pass
`force_restart: true` to abort the rebase, reset, and start the land over.

## HTTP endpoints

With the `http` worker (or any provider of the `http` trigger type)
installed, the read surface is also served over HTTP: `worktree/list`,
`worktree/get`, `worktree/status`, and `worktree/validate` (POST, JSON
bodies matching the function schemas). Registration is best-effort; without
an http provider the worker logs a warning and keeps serving the bus.

## Local development and testing

```bash
cargo run --release -- --url ws://127.0.0.1:49134
cargo test
```

The end-to-end test boots a real engine and self-skips when `iii` is not on
PATH. Regenerate wire-schema goldens with `UPDATE_GOLDENS=1 cargo test`.

## api reference

```json
{
  "functions": [
    {
      "description": "Claim a worktree for a session. Fails with W210 when another session holds it, unless force is set.",
      "metadata": {},
      "name": "worktree::claim",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "force": {
            "default": false,
            "description": "Take the claim even when another session holds it.",
            "type": "boolean"
          },
          "session_id": {
            "description": "The claiming session.",
            "type": "string"
          },
          "worktree_id": {
            "description": "The worktree to claim.",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "worktree_id"
        ],
        "title": "Request",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "claimed": {
            "description": "True when the claim is now held by `session_id`.",
            "type": "boolean"
          },
          "previous_session_id": {
            "description": "The session that held the claim before, when taken over with force.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "description": "The claiming session.",
            "type": "string"
          },
          "worktree_id": {
            "description": "The claimed worktree.",
            "type": "string"
          }
        },
        "required": [
          "claimed",
          "session_id",
          "worktree_id"
        ],
        "title": "Response",
        "type": "object"
      }
    },
    {
      "description": "Create an isolated, locked git worktree off a base ref and register it in the cross-agent registry. Returns the path to hand to an agent as its working directory.",
      "metadata": {},
      "name": "worktree::create",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "base_ref": {
            "default": null,
            "description": "Ref to base the worktree on. Defaults to `HEAD`.",
            "type": [
              "string",
              "null"
            ]
          },
          "branch": {
            "default": null,
            "description": "Branch to create. Defaults to `<branch_prefix><worktree_id>` (or a codename when `branch_naming: codename`).",
            "type": [
              "string",
              "null"
            ]
          },
          "pr": {
            "default": null,
            "description": "Create the worktree at a GitHub pull request head: fetches `refs/pull/<n>/head` from `origin` and branches `<prefix>pr-<n>` at it. Mutually exclusive with `base_ref` and `branch`.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "repo_path": {
            "description": "Absolute path of the repository to branch from.",
            "type": "string"
          },
          "session_id": {
            "default": null,
            "description": "Session to auto-claim the worktree for.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "repo_path"
        ],
        "title": "Request",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "base_ref": {
            "description": "The ref the worktree was based on.",
            "type": "string"
          },
          "base_sha": {
            "description": "The commit `base_ref` resolved to.",
            "type": "string"
          },
          "branch": {
            "description": "The branch checked out in the worktree.",
            "type": "string"
          },
          "created_at": {
            "description": "Creation time, ms since epoch.",
            "format": "int64",
            "type": "integer"
          },
          "dev_port": {
            "description": "Advisory dev-server port derived from the worktree id.",
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "description": "Absolute path of the new worktree; hand this to an agent as its working directory.",
            "type": "string"
          },
          "repo_key": {
            "description": "Canonical per-repo key (FIFO group for lands).",
            "type": "string"
          },
          "worktree_id": {
            "description": "Stable id, `wt_<12hex>`.",
            "type": "string"
          }
        },
        "required": [
          "base_ref",
          "base_sha",
          "branch",
          "created_at",
          "dev_port",
          "path",
          "repo_key",
          "worktree_id"
        ],
        "title": "Response",
        "type": "object"
      }
    },
    {
      "description": "Fetch one managed worktree with its current git status.",
      "metadata": {},
      "name": "worktree::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "worktree_id": {
            "description": "The worktree to fetch.",
            "type": "string"
          }
        },
        "required": [
          "worktree_id"
        ],
        "title": "Request",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Lifecycle": {
            "description": "Lifecycle of a managed worktree.",
            "oneOf": [
              {
                "description": "Exists and is unclaimed.",
                "enum": [
                  "active"
                ],
                "type": "string"
              },
              {
                "description": "Claimed by a session.",
                "enum": [
                  "claimed"
                ],
                "type": "string"
              },
              {
                "description": "A land job is queued or running.",
                "enum": [
                  "landing"
                ],
                "type": "string"
              },
              {
                "description": "The last land attempt was blocked (conflict, failed tests, ...).",
                "enum": [
                  "land-blocked"
                ],
                "type": "string"
              },
              {
                "description": "The record exists but the directory is gone.",
                "enum": [
                  "orphaned"
                ],
                "type": "string"
              }
            ]
          },
          "WorktreeStatus": {
            "description": "Git status summary for one worktree.",
            "properties": {
              "ahead": {
                "description": "Commits ahead of the comparison base (upstream when set, else `base_sha`).",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "behind": {
                "description": "Commits behind the comparison base.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "clean": {
                "description": "True when there are no staged, unstaged, untracked, or conflicted entries.",
                "type": "boolean"
              },
              "conflicted": {
                "description": "Unmerged (conflicted) entry count.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "diffstat": {
                "description": "`git diff --shortstat` of committed work since `base_sha`.",
                "type": "string"
              },
              "head_sha": {
                "description": "Current HEAD commit.",
                "type": "string"
              },
              "in_rebase": {
                "description": "True while a rebase is in progress in this worktree.",
                "type": "boolean"
              },
              "integrated": {
                "default": false,
                "description": "True when the branch's work is already contained in its integration target (fast-forward, merge, rebase, or squash merge). Integrated worktrees are prunable even when ahead of their base.",
                "type": "boolean"
              },
              "integration_reason": {
                "description": "Which check detected the integration: `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, or `patch_id_match`.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "staged": {
                "description": "Staged entry count.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "unpushed": {
                "description": "Commits not merged upstream (upstream ahead count, else commits past `base_sha`).",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "unstaged": {
                "description": "Unstaged entry count.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "untracked": {
                "description": "Untracked entry count.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "ahead",
              "behind",
              "clean",
              "conflicted",
              "diffstat",
              "head_sha",
              "in_rebase",
              "staged",
              "unpushed",
              "unstaged",
              "untracked"
            ],
            "type": "object"
          }
        },
        "description": "One worktree as reported by `worktree::list` / `worktree::get`.",
        "properties": {
          "base_ref": {
            "description": "The ref the worktree was created from.",
            "type": "string"
          },
          "base_sha": {
            "description": "The commit `base_ref` resolved to at creation time.",
            "type": "string"
          },
          "branch": {
            "description": "The branch checked out in the worktree.",
            "type": "string"
          },
          "created_at": {
            "description": "Creation time, ms since epoch.",
            "format": "int64",
            "type": "integer"
          },
          "dev_port": {
            "description": "Advisory dev-server port derived from the worktree id: deterministic, collision-improbable, never reserved anywhere.",
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "lifecycle": {
            "allOf": [
              {
                "$ref": "#/definitions/Lifecycle"
              }
            ],
            "description": "Current lifecycle (reported `orphaned` when the directory is gone)."
          },
          "path": {
            "description": "Absolute path of the worktree directory.",
            "type": "string"
          },
          "repo_key": {
            "description": "Canonicalized per-repo key.",
            "type": "string"
          },
          "repo_path": {
            "description": "The repository path the worktree was created from.",
            "type": "string"
          },
          "session_id": {
            "description": "Owning session, when claimed.",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "anyOf": [
              {
                "$ref": "#/definitions/WorktreeStatus"
              },
              {
                "type": "null"
              }
            ],
            "description": "Git status, when requested and computable."
          },
          "updated_at": {
            "description": "Last mutation time, ms since epoch.",
            "format": "int64",
            "type": "integer"
          },
          "worktree_id": {
            "description": "Stable id, `wt_<12hex>`.",
            "type": "string"
          }
        },
        "required": [
          "base_ref",
          "base_sha",
          "branch",
          "created_at",
          "dev_port",
          "lifecycle",
          "path",
          "repo_key",
          "repo_path",
          "updated_at",
          "worktree_id"
        ],
        "title": "WorktreeInfo",
        "type": "object"
      }
    },
    {
      "description": "Queue a land: rebase onto the target, run the optional test gate via shell::exec, fast-forward the target with an atomic CAS, then clean up. Serialized per repo via the land queue.",
      "metadata": {},
      "name": "worktree::land",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "force_restart": {
            "default": false,
            "description": "Abort a conflicted rebase left by a previous land and start over.",
            "type": "boolean"
          },
          "keep": {
            "default": false,
            "description": "Keep the worktree and branch after a successful land.",
            "type": "boolean"
          },
          "target_branch": {
            "description": "The branch to fast-forward with the worktree's commits.",
            "type": "string"
          },
          "test_cmd": {
            "default": null,
            "description": "Optional test command run in the worktree via `shell::exec` before merging (the test gate).",
            "type": [
              "string",
              "null"
            ]
          },
          "worktree_id": {
            "description": "The worktree to land.",
            "type": "string"
          }
        },
        "required": [
          "target_branch",
          "worktree_id"
        ],
        "title": "Request",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "job_id": {
            "description": "The queued land job.",
            "type": "string"
          },
          "queued": {
            "description": "True when the job was enqueued.",
            "type": "boolean"
          }
        },
        "required": [
          "job_id",
          "queued"
        ],
        "title": "Response",
        "type": "object"
      }
    },
    {
      "description": "Internal: execute one queued land job. Bound to the land queue; not agent-callable.",
      "metadata": {},
      "name": "worktree::land-step",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "job_id": {
            "description": "The job to advance.",
            "type": "string"
          },
          "repo_key": {
            "default": "",
            "description": "FIFO group key; consumed by the engine's queue config, echoed here.",
            "type": "string"
          }
        },
        "required": [
          "job_id"
        ],
        "title": "Request",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "done": {
            "description": "True when the job reached a terminal state (landed or blocked).",
            "type": "boolean"
          },
          "job_id": {
            "description": "The advanced job.",
            "type": "string"
          },
          "phase_completed": {
            "description": "The last phase this invocation completed.",
            "type": "string"
          }
        },
        "required": [
          "done",
          "job_id",
          "phase_completed"
        ],
        "title": "Response",
        "type": "object"
      }
    },
    {
      "description": "List managed worktrees, optionally filtered by repo or session and enriched with git status. Also reports unmanaged worktrees of the repo when repo_path is given.",
      "metadata": {},
      "name": "worktree::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "include_status": {
            "default": false,
            "description": "Compute git status for each worktree (slower).",
            "type": "boolean"
          },
          "repo_path": {
            "default": null,
            "description": "Only worktrees of this repository.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "default": null,
            "description": "Only worktrees claimed by this session.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "Request",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Lifecycle": {
            "description": "Lifecycle of a managed worktree.",
            "oneOf": [
              {
                "description": "Exists and is unclaimed.",
                "enum": [
                  "active"
                ],
                "type": "string"
              },
              {
                "description": "Claimed by a session.",
                "enum": [
                  "claimed"
                ],
                "type": "string"
              },
              {
                "description": "A land job is queued or running.",
                "enum": [
                  "landing"
                ],
                "type": "string"
              },
              {
                "description": "The last land attempt was blocked (conflict, failed tests, ...).",
                "enum": [
                  "land-blocked"
                ],
                "type": "string"
              },
              {
                "description": "The record exists but the directory is gone.",
                "enum": [
                  "orphaned"
                ],
                "type": "string"
              }
            ]
          },
          "UnmanagedWorktree": {
            "description": "A worktree of the repo that this worker does not manage.",
            "properties": {
              "branch": {
                "description": "Checked-out branch ref, when not detached.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "path": {
                "description": "Absolute path of the worktree.",
                "type": "string"
              }
            },
            "required": [
              "path"
            ],
            "type": "object"
          },
          "WorktreeInfo": {
            "description": "One worktree as reported by `worktree::list` / `worktree::get`.",
            "properties": {
              "base_ref": {
                "description": "The ref the worktree was created from.",
                "type": "string"
              },
              "base_sha": {
                "description": "The commit `base_ref` resolved to at creation time.",
                "type": "string"
              },
              "branch": {
                "description": "The branch checked out in the worktree.",
                "type": "string"
              },
              "created_at": {
                "description": "Creation time, ms since epoch.",
                "format": "int64",
                "type": "integer"
              },
              "dev_port": {
                "description": "Advisory dev-server port derived from the worktree id: deterministic, collision-improbable, never reserved anywhere.",
                "format": "uint16",
                "minimum": 0,
                "type": "integer"
              },
              "lifecycle": {
                "allOf": [
                  {
                    "$ref": "#/definitions/Lifecycle"
                  }
                ],
                "description": "Current lifecycle (reported `orphaned` when the directory is gone)."
              },
              "path": {
                "description": "Absolute path of the worktree directory.",
                "type": "string"
              },
              "repo_key": {
                "description": "Canonicalized per-repo key.",
                "type": "string"
              },
              "repo_path": {
                "description": "The repository path the worktree was created from.",
                "type": "string"
              },
              "session_id": {
                "description": "Owning session, when claimed.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "status": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WorktreeStatus"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Git status, when requested and computable."
              },
              "updated_at": {
                "description": "Last mutation time, ms since epoch.",
                "format": "int64",
                "type": "integer"
              },
              "worktree_id": {
                "description": "Stable id, `wt_<12hex>`.",
                "type": "string"
              }
            },
            "required": [
              "base_ref",
              "base_sha",
              "branch",
              "created_at",
              "dev_port",
              "lifecycle",
              "path",
              "repo_key",
              "repo_path",
              "updated_at",
              "worktree_id"
            ],
            "type": "object"
          },
          "WorktreeStatus": {
            "description": "Git status summary for one worktree.",
            "properties": {
              "ahead": {
                "description": "Commits ahead of the comparison base (upstream when set, else `base_sha`).",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "behind": {
                "description": "Commits behind the comparison base.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "clean": {
                "description": "True when there are no staged, unstaged, untracked, or conflicted entries.",
                "type": "boolean"
              },
              "conflicted": {
                "description": "Unmerged (conflicted) entry count.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "diffstat": {
                "description": "`git diff --shortstat` of committed work since `base_sha`.",
                "type": "string"
              },
              "head_sha": {
                "description": "Current HEAD commit.",
                "type": "string"
              },
              "in_rebase": {
                "description": "True while a rebase is in progress in this worktree.",
                "type": "boolean"
              },
              "integrated": {
                "default": false,
                "description": "True when the branch's work is already contained in its integration target (fast-forward, merge, rebase, or squash merge). Integrated worktrees are prunable even when ahead of their base.",
                "type": "boolean"
              },
              "integration_reason": {
                "description": "Which check detected the integration: `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, or `patch_id_match`.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "staged": {
                "description": "Staged entry count.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "unpushed": {
                "description": "Commits not merged upstream (upstream ahead count, else commits past `base_sha`).",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "unstaged": {
                "description": "Unstaged entry count.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "untracked": {
                "description": "Untracked entry count.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "ahead",
              "behind",
              "clean",
              "conflicted",
              "diffstat",
              "head_sha",
              "in_rebase",
              "staged",
              "unpushed",
              "unstaged",
              "untracked"
            ],
            "type": "object"
          }
        },
        "properties": {
          "unmanaged": {
            "description": "Non-primary worktrees of the repo without a record (only populated when repo_path is given). Never auto-adopted.",
            "items": {
              "$ref": "#/definitions/UnmanagedWorktree"
            },
            "type": "array"
          },
          "worktrees": {
            "description": "Managed worktrees, oldest first.",
            "items": {
              "$ref": "#/definitions/WorktreeInfo"
            },
            "type": "array"
          }
        },
        "required": [
          "unmanaged",
          "worktrees"
        ],
        "title": "Response",
        "type": "object"
      }
    },
    {
      "description": "Internal: hot-reload the worktree worker from the authoritative configuration when it changes — swaps the per-call snapshot and re-binds the prune cron on a schedule change.",
      "metadata": {},
      "name": "worktree::on-config-change",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Internal `worktree::on-config-change` trigger payload. The handler re-fetches the authoritative configuration and ignores this payload, so a direct call can never inject config.",
        "properties": {
          "id": {
            "default": null,
            "description": "Configuration id that changed (advisory; the handler re-fetches).",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "OnConfigChangeEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Ack returned by the internal `worktree::on-config-change` handler.",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "OnConfigChangeResponse",
        "type": "object"
      }
    },
    {
      "description": "Sweep managed worktrees: reconcile records whose directories are gone and remove clean, unclaimed, expired worktrees. Cron-safe: an empty payload uses defaults; dry_run reports without acting.",
      "metadata": {},
      "name": "worktree::prune",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "dry_run": {
            "default": false,
            "description": "Report what would be pruned without acting.",
            "type": "boolean"
          },
          "repo_path": {
            "default": null,
            "description": "Only sweep worktrees of this repository.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "Request",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "PruneSkip": {
            "description": "One record the sweep left alone, and why.",
            "properties": {
              "id": {
                "description": "The skipped worktree.",
                "type": "string"
              },
              "reason": {
                "description": "Why it was skipped.",
                "type": "string"
              }
            },
            "required": [
              "id",
              "reason"
            ],
            "type": "object"
          }
        },
        "properties": {
          "pruned": {
            "description": "Worktree ids pruned (or prunable, with dry_run).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "skipped": {
            "description": "Records left alone, with reasons.",
            "items": {
              "$ref": "#/definitions/PruneSkip"
            },
            "type": "array"
          }
        },
        "required": [
          "pruned",
          "skipped"
        ],
        "title": "Response",
        "type": "object"
      }
    },
    {
      "description": "Release a session's claim. Fails with W211 when the caller does not hold the claim, unless force is set.",
      "metadata": {},
      "name": "worktree::release",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "force": {
            "default": false,
            "description": "Release even when another session holds the claim.",
            "type": "boolean"
          },
          "session_id": {
            "description": "The releasing session (must hold the claim unless force is set).",
            "type": "string"
          },
          "worktree_id": {
            "description": "The worktree to release.",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "worktree_id"
        ],
        "title": "Request",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "released": {
            "description": "True when the worktree is now unclaimed.",
            "type": "boolean"
          },
          "worktree_id": {
            "description": "The released worktree.",
            "type": "string"
          }
        },
        "required": [
          "released",
          "worktree_id"
        ],
        "title": "Response",
        "type": "object"
      }
    },
    {
      "description": "Remove a managed worktree. Refuses dirty (W220) or unmerged (W221) work unless force is set; optionally deletes the branch.",
      "metadata": {},
      "name": "worktree::remove",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "delete_branch": {
            "default": false,
            "description": "Also delete the worktree's branch.",
            "type": "boolean"
          },
          "force": {
            "default": false,
            "description": "Remove even when dirty or carrying unmerged commits.",
            "type": "boolean"
          },
          "worktree_id": {
            "description": "The worktree to remove.",
            "type": "string"
          }
        },
        "required": [
          "worktree_id"
        ],
        "title": "Request",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "branch_deleted": {
            "description": "True when the branch was deleted too.",
            "type": "boolean"
          },
          "removed": {
            "description": "True when the worktree (and its record) are gone.",
            "type": "boolean"
          }
        },
        "required": [
          "branch_deleted",
          "removed"
        ],
        "title": "Response",
        "type": "object"
      }
    },
    {
      "description": "Git status for one worktree: cleanliness, ahead/behind, diffstat, rebase-in-progress, lifecycle.",
      "metadata": {},
      "name": "worktree::status",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "worktree_id": {
            "description": "The worktree to inspect.",
            "type": "string"
          }
        },
        "required": [
          "worktree_id"
        ],
        "title": "Request",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Lifecycle": {
            "description": "Lifecycle of a managed worktree.",
            "oneOf": [
              {
                "description": "Exists and is unclaimed.",
                "enum": [
                  "active"
                ],
                "type": "string"
              },
              {
                "description": "Claimed by a session.",
                "enum": [
                  "claimed"
                ],
                "type": "string"
              },
              {
                "description": "A land job is queued or running.",
                "enum": [
                  "landing"
                ],
                "type": "string"
              },
              {
                "description": "The last land attempt was blocked (conflict, failed tests, ...).",
                "enum": [
                  "land-blocked"
                ],
                "type": "string"
              },
              {
                "description": "The record exists but the directory is gone.",
                "enum": [
                  "orphaned"
                ],
                "type": "string"
              }
            ]
          }
        },
        "description": "Git status summary for one worktree.",
        "properties": {
          "ahead": {
            "description": "Commits ahead of the comparison base (upstream when set, else `base_sha`).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "behind": {
            "description": "Commits behind the comparison base.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "branch": {
            "description": "The branch checked out in the worktree.",
            "type": "string"
          },
          "clean": {
            "description": "True when there are no staged, unstaged, untracked, or conflicted entries.",
            "type": "boolean"
          },
          "conflicted": {
            "description": "Unmerged (conflicted) entry count.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "dev_port": {
            "description": "Advisory dev-server port derived from the worktree id.",
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "diffstat": {
            "description": "`git diff --shortstat` of committed work since `base_sha`.",
            "type": "string"
          },
          "head_sha": {
            "description": "Current HEAD commit.",
            "type": "string"
          },
          "in_rebase": {
            "description": "True while a rebase is in progress in this worktree.",
            "type": "boolean"
          },
          "integrated": {
            "default": false,
            "description": "True when the branch's work is already contained in its integration target (fast-forward, merge, rebase, or squash merge). Integrated worktrees are prunable even when ahead of their base.",
            "type": "boolean"
          },
          "integration_reason": {
            "description": "Which check detected the integration: `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, or `patch_id_match`.",
            "type": [
              "string",
              "null"
            ]
          },
          "lifecycle": {
            "allOf": [
              {
                "$ref": "#/definitions/Lifecycle"
              }
            ],
            "description": "Current lifecycle."
          },
          "staged": {
            "description": "Staged entry count.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "unpushed": {
            "description": "Commits not merged upstream (upstream ahead count, else commits past `base_sha`).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "unstaged": {
            "description": "Unstaged entry count.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "untracked": {
            "description": "Untracked entry count.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "worktree_id": {
            "description": "The inspected worktree.",
            "type": "string"
          }
        },
        "required": [
          "ahead",
          "behind",
          "branch",
          "clean",
          "conflicted",
          "dev_port",
          "diffstat",
          "head_sha",
          "in_rebase",
          "lifecycle",
          "staged",
          "unpushed",
          "unstaged",
          "untracked",
          "worktree_id"
        ],
        "title": "Response",
        "type": "object"
      }
    },
    {
      "description": "Validate a path as a managed worktree (console picker surface). Unmanaged worktrees are reported but never adopted.",
      "metadata": {},
      "name": "worktree::validate",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "description": "Absolute path to validate.",
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "Request",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "managed": {
            "description": "True when a record manages this path.",
            "type": "boolean"
          },
          "reason": {
            "description": "Why the path is not valid.",
            "type": [
              "string",
              "null"
            ]
          },
          "valid": {
            "description": "True when the path is a live, managed worktree.",
            "type": "boolean"
          },
          "worktree_id": {
            "description": "The managing record's id, when one exists.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "managed",
          "valid"
        ],
        "title": "Response",
        "type": "object"
      }
    }
  ],
  "triggers": [
    {
      "description": "A session took ownership of a worktree.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `worktree::*` trigger binding. All filters are optional equality matches; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "repo_path": {
            "description": "Only deliver events for this repository path.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "description": "Only deliver events for worktrees claimed by this session.",
            "type": [
              "string",
              "null"
            ]
          },
          "worktree_id": {
            "description": "Only deliver events for this worktree.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "worktree::claimed",
      "return_schema": {}
    },
    {
      "description": "A new managed worktree exists.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `worktree::*` trigger binding. All filters are optional equality matches; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "repo_path": {
            "description": "Only deliver events for this repository path.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "description": "Only deliver events for worktrees claimed by this session.",
            "type": [
              "string",
              "null"
            ]
          },
          "worktree_id": {
            "description": "Only deliver events for this worktree.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "worktree::created",
      "return_schema": {}
    },
    {
      "description": "A land ended blocked (conflict, failed tests, dirty tree, moved target).",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `worktree::*` trigger binding. All filters are optional equality matches; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "repo_path": {
            "description": "Only deliver events for this repository path.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "description": "Only deliver events for worktrees claimed by this session.",
            "type": [
              "string",
              "null"
            ]
          },
          "worktree_id": {
            "description": "Only deliver events for this worktree.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "worktree::land-blocked",
      "return_schema": {}
    },
    {
      "description": "A land completed; the target branch was fast-forwarded.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `worktree::*` trigger binding. All filters are optional equality matches; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "repo_path": {
            "description": "Only deliver events for this repository path.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "description": "Only deliver events for worktrees claimed by this session.",
            "type": [
              "string",
              "null"
            ]
          },
          "worktree_id": {
            "description": "Only deliver events for this worktree.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "worktree::landed",
      "return_schema": {}
    },
    {
      "description": "A worktree claim was released.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `worktree::*` trigger binding. All filters are optional equality matches; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "repo_path": {
            "description": "Only deliver events for this repository path.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "description": "Only deliver events for worktrees claimed by this session.",
            "type": [
              "string",
              "null"
            ]
          },
          "worktree_id": {
            "description": "Only deliver events for this worktree.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "worktree::released",
      "return_schema": {}
    },
    {
      "description": "A managed worktree was removed.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "additionalProperties": false,
        "description": "Config accepted by every `worktree::*` trigger binding. All filters are optional equality matches; unknown fields fail at registration so a misspelled filter key fails loudly instead of silently receiving nothing.",
        "properties": {
          "repo_path": {
            "description": "Only deliver events for this repository path.",
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "description": "Only deliver events for worktrees claimed by this session.",
            "type": [
              "string",
              "null"
            ]
          },
          "worktree_id": {
            "description": "Only deliver events for this worktree.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "BindingConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "worktree::removed",
      "return_schema": {}
    }
  ]
}
```
