The paved road (git work on a worktree, over the CLI)
The "paved road": the CLI routes an agent uses to do git work on its own worktree — commit the files it changed, sync onto the latest shared branch, stamp the branch ready to merge, hold and release a gate, hand a worktree over or take one back, reclaim a stranded one, and repair a broken dependency folder. Explains why the raw git commands are the wrong tool for each job.
What it is
When a session works in its own git worktree, the operations it wants are the ordinary ones — commit what I changed, catch up with the shared branch, mark my branch finished, throw the worktree away. Each of them has a raw git command, and each of those raw commands is the wrong tool, because Omniscio keeps a record of every worktree — who owns it, what it is for, whether it is still in use — and a raw command changes the repository without changing the record. The worktree then stops matching its own description: it reads as abandoned while it is still being worked in, or as live while it is gone.
The paved road is the set of routes that do those same operations and keep the record correct. Routing work through it is what makes a worktree's state trustworthy to everyone else.
Where to find it
This is a CLI-only surface — there is no button for it. The routes live on Omniscio's local
control server at 127.0.0.1:19519, and they are the machines' side of the Work in Flight and
Saved Work views.
GET /worktreeslists the worktrees with their branch, task and state, and is safe to read with any token. Start there to find the:idevery other route takes.- Everything that changes something needs the full-trust token (
~/.amc/cli-tokenfrom the Settings → CLI Control page). A session's own scoped token is deliberately refused on every mutation: a worktree operation can rewrite history or delete a branch, which is more than a spawned session should be able to do to the machine it is running on.
How it behaves
The routes fall into five jobs. Every body is strict: an unrecognised field is a 400 rather
than a silently-ignored typo, which matters most on /sync, where a mistyped action would
otherwise quietly default to starting a rebase. Five routes carry no body at all — remove,
reclaim, release, gate-hold and gate-hold/release — and sending one is likewise a 400.
Reading what is really there. GET /worktrees/:id/status reports the branch, how far ahead and
behind it is, whether it is dirty, the last commit, whether the ready-to-merge tag is set, and
whether a rebase is half-finished. This is the honest read — use it instead of inferring state from
the branch name.
Finishing your own work.
POST /worktrees/:id/commitstages the files you name, with the message you give it, and commits them. Naming them is the point: there is no "commit everything" and the paths are literal names rather than a wildcard, so one agent cannot sweep another's half-written work into its own commit.POST /worktrees/:id/syncbrings the branch up to date by replaying its commits on top of the shared branch. It rewrites this branch's history, so it takes an action: start (the default), continue or abort. A conflict stops the replay mid-way and reports it; you then either resolve by hand andcontinue, orabortto put the branch back exactly as it was. A half-done rebase is a state you are expected to finish, not one the route will guess its way out of.POST /worktrees/:id/readystamps the ready-to-merge tag, and requires a summary line that is recorded inside the tag. The tag names the exact commit it was taken at, so any later change to the branch invalidates it — that is the point: the tag is a claim about one specific tree, not a flag on the branch. It also accepts an optional evidence note and an optional human override; the override exists so a person can order a tag past a red gate by saying why, and a session using its own scoped token is refused that option — a check an agent can wave through is not a check.
Gates. POST /worktrees/:id/gate-hold marks a gate as running on this worktree and
POST /worktrees/:id/gate-hold/release takes the mark off. Holding is what stops a cleanup sweep
from reclaiming a worktree in the middle of a build; releasing it lets the sweep back in.
Passing a worktree between owners. POST /worktrees/:id/adopt takes ownership of the record,
and POST /worktrees/:id/release hands it back. Neither touches git — they change only who the
record says is responsible, which is what lets work be handed over without either side having to
guess.
A recorded owner is not by itself a claim that somebody is still in that checkout: it names the
session the worktree was issued to. So an owner that is still alive but has finished with this
worktree — its work has landed and the checkout has sat untouched for six hours — no longer blocks
a handover. adopt still asks for confirmTakeFromLiveOwner from a live owner; what changed
(2026-10-02) is that it is no longer a flat refusal with no route out. And when a session is
archived, its rows are handed back at that moment, so a successor is not refused not_owner
while the finished session's name is still on the row.
Retiring one.
POST /worktrees/:id/removeretires your own worktree — the sanctioned replacement for the raw delete. It archives the branch tip before anything else, so the work is recoverable even on the path that also deletes the branch. A removal that is refused comes back as a200saying it did not remove (removed: false) with the reason, never as a success.POST /worktrees/:id/reclaimretires a stranded worktree the caller does not own, and keeps the branch — it is the gentler of the two, for clearing up someone else's abandoned workspace without touching their commits.GET /worktrees/:id/reclaim-previewruns the same decision and writes nothing, so you can see what it would do first.POST /worktrees/blockparks a branch as blocked when there is no worktree involved at all.
Repairing one. POST /worktrees/:id/install-deps rebuilds a dependency folder that was left
half-written, in place. It is the correct move when a worktree reports broken dependencies —
deleting the folder by hand leaves the record disagreeing with the disk.
One route has a longer deadline than the rest. /ready is allowed to run well past the usual
request timeout, because deciding whether a branch is landable involves a series of checks that
cannot be hurried. A client that gives up on it early will see a failure where the operation
actually completed.
For agents
Every route, with the token tier and body each one takes:
| Method | Path | Body | Token | What it does |
|---|---|---|---|---|
GET |
/worktrees/:id/status |
— | any | branch, ahead/behind, dirty, last commit, ready tag, rebase state |
POST |
/worktrees/:id/commit |
{ files[], message } |
full-trust | stage those files and commit (explicit paths, never a pathspec; ≤500) |
POST |
/worktrees/:id/sync |
{ action } |
full-trust | rebase onto the shared branch, rerere off (start | continue | abort, default start) |
POST |
/worktrees/:id/ready |
{ summary, evidence?, humanOverride? } |
full-trust | stamp SHA-bound ready-to-merge (dispatchDeadlineMs = 15 min) |
POST |
/worktrees/:id/gate-hold |
— | full-trust | stamp the gate-in-progress holding tag |
POST |
/worktrees/:id/gate-hold/release |
— | full-trust | retract the holding tag |
POST |
/worktrees/:id/adopt |
— | full-trust | take ownership of the ledger row (no git) |
POST |
/worktrees/:id/release |
— | full-trust | hand the row back (no git) |
POST |
/worktrees/block |
repo + branch facts | full-trust | park a branch blocked, no worktree needed |
POST |
/worktrees/:id/reclaim |
— | full-trust | retire a stranded row the caller does not own, keeping the branch |
GET |
/worktrees/:id/reclaim-preview |
— | full-trust | read-only dry run of reclaim |
POST |
/worktrees/:id/remove |
— | full-trust | retire your own worktree (replaces git worktree remove) |
POST |
/worktrees/:id/install-deps |
— | full-trust | repair a torn node_modules in place |
Implementation: src/main/services/cli/cli-server-worktree-ops-routes.ts (lifecycle) and
cli-server-worktree-ledger-routes.ts (/worktrees, /worktrees/:id, landed-proof, pin,
install-deps, create, lease restore). Five routes — remove, reclaim, release, gate-hold
and gate-hold/release — take no body and answer refusedUnhonouredBody (400) if one is sent.
All mutations are registered { selfRateLimited: true } and bill the per-source paved-road bucket
(429 rateLimitedWorktreeOp). /remove also carries the ledger's floor: a refusal returns
200 { removed: false } with the reason. Every body is .strict(), so a misspelled action on /sync is a 400 rather than a silent
default (that schema carries z.enum([...]).default('start')). Invariants live in
agent-git-paved-road-contract.md,
worktree-ledger-contract.md and
git-guardrails-contract.md rule 10.
Related
- Session isolation — what a worktree is, why each session gets its own, and how the work inside one finds its way back.
- Git guardrails — the protected-branch and destructive-operation blocks that sit above this surface.
- Worktree cleanup — the bulk pass that retires worktrees whose work has already landed.
- Orphan worktree archive — the background job that reclaims the disk folder of a worktree whose session has been gone a long time.
- Where's My Work — the read-only screen that answers what happened to a branch's changes.
- CLI Control — the local server these routes belong to, and where the full-trust token comes from.
Last verified 2026-10-02