@agentproto/worktree
v0.5.2
Published
AIP-14 TOOL contracts + builtin AIP-30 PROVIDER for provisioning, gating, and cleaning up a git worktree — the primitive an AgentStep binds its `cwd` to for a launch-agent-in-a-worktree workflow.
Maintainers
Readme
@agentproto/worktree
AIP-14 TOOL contracts + a builtin AIP-30 PROVIDER for provisioning, gating,
and cleaning up a git worktree — the primitive a @agentproto/workflow-runtime
AgentStep binds its cwd to for a "launch an agent in a worktree" workflow.
Tools
worktree.provision—git worktree adda new worktree forrepoRootat<repoRoot>/../_worktrees/<slug>on branchwt/<slug>, cut frombase(defaultorigin/main). Optionally runsdepsCmdinside it, and copiescopyGlobs(e.g. gitignored local secrets) fromrepoRootinto it at the same relative path. Then runs the base tree'sagentproto.jsonsetup hooks (unlessrunSetup: false). Returns{ cwd, branch }.worktree.run-gate— run a caller-provided command inside a directory and report pass/fail from its exit code.worktree.cleanup— stop the worktree's supervised services, run the base tree's teardown hooks (failures logged, never blocking), thengit worktree remove(+ optionalgit branch -D).worktree.run-script— run a declaredscripts.<name>command once inside a worktree, with theAGENTPROTO_*env injected.worktree.start-service/worktree.stop-service/worktree.list-services— start/stop/list the declaredtype: "service"scripts as supervised long-running children with allocated ports and a*.localhostproxy route.
The gate/provision/cleanup trio is agnostic: no hardcoded package manager, env layout, or gate command — everything is an input.
agentproto.json — per-repo worktree lifecycle
Drop an agentproto.json at the repo root to declare how a fresh worktree is
set up, torn down, and what dev services it runs:
{
"worktree": {
"setup": ["pnpm install", "cp \"$AGENTPROTO_SOURCE_CHECKOUT_PATH/.env\" .env"],
"teardown": "rm -rf .cache"
},
"scripts": {
"test": { "command": "pnpm test" },
"web": { "command": "pnpm dev --port $AGENTPROTO_PORT", "type": "service", "port": 3000 },
"api": { "command": "pnpm api --port $AGENTPROTO_PORT", "type": "service" }
}
}worktree.setup/worktree.teardown— a single (multiline) shell string or an array of commands, run sequentially with the worktree as cwd. A failing setup command fails provisioning with its captured output; a failing teardown command is logged but never blocks cleanup.scripts.<name>—{ command, type?: "service", port? }. Plain scripts run once (worktree.run-script);type: "service"scripts are supervised long-running processes (worktree.start-service).
Security: config is read from the committed base tree
agentproto.json is always read via git show <base>:agentproto.json —
the committed tree of the base ref (default origin/main), never a worktree's
working tree. A feature branch or an agent editing files inside a worktree
therefore cannot inject setup/teardown hooks or service commands that run
on the host; only what a reviewer merged into the base branch executes.
Environment
Every hook, script, and service receives:
| Variable | Meaning |
| --- | --- |
| AGENTPROTO_SOURCE_CHECKOUT_PATH | Absolute path to the original repo checkout |
| AGENTPROTO_WORKTREE_PATH | Absolute path to the worktree directory |
| AGENTPROTO_BRANCH_NAME | The worktree's branch name |
Each service additionally receives its own AGENTPROTO_PORT and
AGENTPROTO_URL (its proxy URL), plus peer-discovery vars for every sibling
service in the same worktree: AGENTPROTO_SERVICE_<NAME>_PORT and
AGENTPROTO_SERVICE_<NAME>_URL (name upper-cased, non-alphanumerics → _).
Services, ports, and the reverse proxy
- Port allocation — a service uses its declared
portwhen free, else an OS-assigned ephemeral port. Ports are reserved up front for every declared service so peer discovery is complete. - Reverse proxy —
ProxyTable+createProxyServer/startProxyroutehttp://<script>--<branch-slug>--<repo-slug>.localhost:<proxy-port>to a service's local port, with WebSocket upgrade passthrough. On the repo's default branch the branch label is dropped:http://<script>--<repo-slug>.localhost:<proxy-port>. Slugging lowercases, maps non-alphanumerics to-, collapses repeats, and trims.*.localhostresolves to127.0.0.1on modern systems, so no DNS setup is needed.
agentproto worktree CLI
agentproto worktree ls [--repo <dir>] [--json]
agentproto worktree archive <path> [--base <ref>] [--keep-branch] [--json]ls lists the repo's git worktrees; archive stops a worktree's services,
runs its teardown hooks, and removes it (deleting the branch unless
--keep-branch).
worktreeAgentWorkflow
This package also exports the RuntimeWorkflow def that chains the three
tools above around an AgentStep: provision → agent (cwd bound to the
provisioned worktree) → gate → on pass, human approval → cleanup. On gate
failure the worktree is left in place for inspection.
worktree-agent CLI
A bin runs that workflow end-to-end against a real agentproto daemon (the
coding agent is a real, supervisable agent_start session — not a bare
subprocess):
worktree-agent run \
--repo <abs repo root> --slug <id> --task "<prompt>" --gate "<check cmd>" \
[--base origin/main] [--adapter claude-code] [--deps-cmd "pnpm install --prefer-offline"] \
[--copy-glob <glob>]... [--no-cleanup] [--yes]It connects to the daemon's MCP endpoint (http://127.0.0.1:18790/mcp, or
AGENTPROTO_MCP_URL) and fails loudly if it can't reach one. The approval
step reads a y/n answer from /dev/tty; --yes auto-approves, and a
non-interactive run (no TTY) defaults to NOT approving — the worktree is left
in place rather than silently cleaned up.
worktree-gc routine (AIP-41, opt-in)
routines/worktree-gc/ROUTINE.md is a reference AIP-41 routine that reaps
merged worktrees on a schedule, so a long-running workspace doesn't accumulate
stale _worktrees/* trees and dead wt/* branches. It fires the worktree_gc
tool (the daemon's MCP/HTTP surface over planGc / applyGc in src/gc.ts)
with apply: true, salvageDirty: false on a daily cron (0 4 * * *, UTC).
It ships disabled (enabled: false) — it registers but never fires until
you turn it on. To activate it in a workspace:
- Copy
routines/worktree-gc/to the workspace's routine library at<workspace>/.routines/worktree-gc/ROUTINE.md(the path@agentproto/routine'sroutineSpec.pathOfexpects). - Set
enabled: truein the frontmatter. To pin a specific repo, addrepoRoot: <abs path>orworkspaceSlug: <slug>totarget.inputs; otherwise the daemon resolves the repo from the active workspace. - Reload routines so the daemon registers the schedule.
The safety invariants are enforced by the engine and cannot be weakened by the
routine: reclaim is merge-gated (integration ∈ {merged, fresh} and the
tree is clean) except for a narrow dep-bump exemption: a clean unpushed
worktree whose commits are all mechanical dependency bumps (chore(deps) /
fix(deps) subjects and a diff touching only lockfiles + package.json) is
also promoted to reclaim. An open PR or live-session worktree is always
held, and a dirty integrated worktree is only ever archived — never
discarded — and only when salvageDirty is true.
