𦦠holt
the worktree-lifecycle substrate for parallel coding agents
never loses work Β· never reaps what's in use Β· the registry, not the filesystem, is truth
Every vendor ships worktree creation now β claude --worktree, the Claude
Agent SDK's isolation: worktree, Cursor, Copilot CLI β and every one of them
stops there. Nobody owns the rest of the life: the branch still alive after
the pane died, the checkout nobody is sitting in, the tree with 40
uncommitted minutes in it, the branch whose PR merged yesterday and has
kept committing since.
If you've ever git worktree list'd through a graveyard trying to remember
which of those you can safely delete β that's the problem holt makes go away.
install
nix run github:hausfold/holt # try it
nix profile install github:hausfold/holt # keep it
go install github.com/hausfold/holt/cmd/holt@latest # or, with Go 1.23+
Needs git. gh and lsof are optional and make it sharper β gh proves a
branch landed, lsof proves nobody's standing in a checkout; without them holt
degrades toward keep, never toward delete. On
haus it's already on PATH, wired to ββ΅.
a lane
One agent's branch, checkout and pane, from create to reaped. Not a
"worktree" β a parked lane has no checkout on disk, only a branch. Not a
"session" β that's your multiplexer's word.
cd "$(holt new fix-flaky-test)" # a lane on this repo: new checkout, new branch, path on stdout
holt new fix --open # β¦or hand the pane straight to your agent (--open codex, --cmd 'nvim')
holt # every lane you've got going, live or parked, across every repo
holt fix-flaky-test # back later β rebuild the checkout, reopen the agent, where you left off
holt reap # sweep every lane whose branch landed and nobody's standing in
cd "$(holt child ../api)" # a lane on a DIFFERENT repo β never a raw `git worktree add`
And never git stash again. The stash stack lives in the shared .git
dir, so every worktree of a repo and the main checkout push and pop the same
one β parallel agents routinely pop each other's work into a tree that never
asked for it. holt park commits your dirty tree as a single wip: commit on
the branch only this pane has checked out; holt unpark puts it back.
holt park "mid-refactor on the retry logic"
holt unpark
the promise
Not "makes worktrees" β every vendor does that. holt's product is the state
machine and three invariants, in this priority order:
- Never lose work. Every destructive path parks first. The failure
direction is always a branch lingers, never a tree vanished.
- Never reap what's in use. Occupied, dirty, or not provably landed β
keep. Uncertainty resolves to keep β including when GitHub is unreachable.
- The registry is truth. Not the filesystem, not
git worktree list β
those are derived, and they lie.
So a command exiting 2 β refused for safety is holt working, not holt
failing. Don't reach for git worktree remove; ask it why.
the verbs
|
|
holt |
list every live/parked lane, across all repos Β· --json for machines |
holt <name> |
resume one β rebuild its checkout, reopen its agent |
holt focus <name> |
go to the window a lane is already running in β [hooks] focus, resume without one |
holt new [name] |
a lane on this repo β prints its path Β· --open [agent] / --cmd 'β¦' to run something in it |
β¦ --prompt '<task>' |
open it on a first turn instead of a blank pane Β· --prompt-file <file|-> for a brief Β· --image <file> |
holt child <repo> |
a lane on another repo, as a child of this pane |
holt spawn <repo> <name> |
a named lane, for a spawner with no pane of its own β --prompt/--prompt-file opens it through [hooks] open |
namer = "claude" |
opt-in: a lane opened on a task and given no name takes its name FROM that task β hud-draft-color, not cozy-otter. One config line; holt runs the client already on PATH, never a model of its own |
holt park [label] / unpark |
set the tree aside as a wip: commit, and put it back |
holt reap |
sweep every landed, unoccupied lane |
holt reaped |
what went, why, and the SHA to get it back |
holt drop <name> |
retire a lane that will never land β recorded, undoable |
holt reship [name] |
push a branch that outran its merged PR, open the follow-up |
holt heartbeat [path] |
hold the occupancy lease, so reap spares this lane |
holt watch --json |
lifecycle events on stdout, one NDJSON object per line |
holt runtime up|enter|down <name> |
hand a lane to an isolation backend, and take it back Β· --backend <id> every time, never automatic Β· tart is built in β a headless macOS per lane, so an agent can drive a desktop without taking yours |
holt runtime eject tart |
print the built-in backend as an adapter file, to edit and override it with |
holt hook create / remove |
client-hook entry points (this is what claude --worktree calls) |
holt hook notify |
client events β a trill banner for the lane: Notification hangs an ask on the ledge when a lane blocks on its user, Stop replaces it with a done when the turn finishes, and UserPromptSubmit / PostToolUse take an answered ask back down β the session moved again, so the question did too Β· every banner is keyed by lane, so one lane never hangs two Β· clicking it runs holt focus on that lane Β· exit 0 always, silent no-op without trill |
holt --help is exhaustive. Config, exit codes, and the --json lane payload
are in docs/reference.md.
SDKs
Five clients over the same CLI, sharing one version number β list, watch as
a native async stream, child/spawn, park/unpark/reap/reship, and
occupancy leases.
β οΈ Go: the module path moved on 2026-08-16. v0.2.8 and earlier stay
resolvable on Go's immutable proxy under the previous owner forever; everything
since is at github.com/hausfold/holt/sdk/go, so an existing importer edits its
import line. Β· Swift ships from a generated mirror β send changes to
sdk/swift here, never to the mirror.
more
- Lifecycle β states, landing rules, occupancy, and how to disagree with holt
- Reference β config, exit codes, the
--json payload, building from source
ai/SKILL.md β the agent-facing surface: drop it in and your agent drives holt correctly first try
ai/handoff/SKILL.md β the companion skill: write a brief a cold session can act on, and holt spawn --prompt-file it into its own lane
- SPEC.md β the design of record, and the contracts that are frozen
- Releasing β how the CLI and all five SDKs are cut from one number
pre-1.0 Β· Nothing here takes something it can't give back β a dirty tree is parked as a commit, and only merged, unoccupied lanes are ever reaped. That's the intent, not a warranty: keep a backup, and tell us what breaks.
Apache-2.0 Β· one of the hausfold repos β haus rebuilds the Mac, this is what its agent panes stand on
β hausfold