Help us shape the future of Go! Take the 2026 Go Developer Survey

holt

module
v0.2.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 16, 2026 License: Apache-2.0

README ΒΆ

🦦 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.

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.

Every vendor now ships worktree creation β€” 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 that's still alive after the pane died, the checkout nobody is sitting in anymore, 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 your way through a graveyard trying to remember which of these you can safely delete β€” that's the problem holt makes go away.

Quickstart

# spin up a lane on this repo: new checkout, new branch, agent opens in it
holt new fix-flaky-test

# see every lane you've got going, live or parked, across every repo
holt

# pane's closing and the tree's dirty? don't reach for `git stash` β€” it's
# shared across every worktree of this repo. `park` commits it instead, as
# a wip: commit only this branch has
holt park "mid-refactor on the retry logic"

# back later β€” rebuild the checkout, reopen the agent, right where you left it
holt fix-flaky-test

# sweep every lane whose branch already landed and nobody's standing in
holt reap

# working on a DIFFERENT repo than this pane β€” never a raw `git worktree add`
cd "$(holt child ../other-repo)"

What's in a lane?

A lane is one agent's branch, checkout, and pane β€” not a "worktree" (a parked lane has no checkout on disk, only a branch), and not a "session" (that's your multiplexer's). holt --json returns lanes in this shape; the SDKs below wrap it.

Field Meaning
name lane name
repo / main repo identity, and the main checkout's path
branch full branch name
path checkout path on disk β€” empty once parked
parent the pane that spawned it via holt child, or ""
agent claude | codex | opencode | jcode
state live | parked | stray
occupied is anything actually standing in it right now
dirty uncommitted changes
landed reached the default branch? yes | no | contained
post_merge_ahead commits made after the PR merged
last_commit most recent commit

Full lifecycle (states, invariants, landing rules, policy hooks) β†’ docs/lifecycle.md.

Commands

holt                    list every live/parked lane, across all repos
holt <name>             resume one: rebuild its checkout, reopen its agent
                        --pick to choose the session instead of the newest
holt new [name]         a lane on THIS repo, then open the default agent in it
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
holt park [label]       set the working tree aside as a wip: commit on this branch
holt unpark             put the last parked commit's changes back, uncommitted
holt reap               sweep every LANDED lane that nobody is standing in
holt heartbeat [path]   hold the occupancy lease on a lane, so reap spares it
holt watch --json       lifecycle events on stdout, one NDJSON object per line
holt reship [name]      push a branch that outran its merged PR, open the follow-up
holt hook create        [hook] open a lane β€” JSON on stdin, path on stdout
holt hook remove        [hook] retire one without losing work β€” JSON on stdin

Config, exit codes, and building from source β†’ docs/reference.md.

How the CLI and all five SDKs are cut from one version number β†’ docs/releasing.md.


Why not your agent's built-in worktrees? No vendor ships cross-client (claude and codex and opencode and jcode, one registry), none ship cross-repo, and losing your work isn't their problem β€” it's holt's.

SDKs

TypeScript β€” @hausfold/holt

sdk/ts β€” list(), watch()/watchLane() as an async iterator over the NDJSON stream, child/spawn to create a lane without attaching an agent, park/unpark/reap/reship, and occupancy leases. Works from a Bun/Node TUI or a web backend; its types are safe to import into a browser bundle for the frontend.

npm install @hausfold/holt
# or: bun add @hausfold/holt
import { HoltClient } from "@hausfold/holt";

const holt = new HoltClient();
const envelope = await holt.list();
for await (const line of holt.watch()) {
  if (line.kind === "created") console.log("new lane:", line.lane?.name);
}

See sdk/ts/README.md for the full API.

Python β€” hausfold-holt

sdk/python β€” the same client, async-first (asyncio.create_subprocess_exec): list(), watch()/watch_lane() as an async iterator, child/spawn, park/unpark/reap/reship, and occupancy leases. Drops into a FastAPI/asyncio backend or a plain script equally.

pip install hausfold-holt
# or: uv add hausfold-holt
import asyncio
from holt import HoltClient

async def main() -> None:
    holt = HoltClient()
    envelope = await holt.list()
    async for line in holt.watch():
        if line.kind == "created" and line.lane is not None:
            print("new lane:", line.lane.name)

asyncio.run(main())

See sdk/python/README.md for the full API.

Swift β€” Holt

sdk/swift β€” the same client over Foundation.Process: list(), watch()/watchLane(path:) as AsyncThrowingStream, child/spawn, park/unpark/reap/reship, and occupancy leases. macOS + Linux β€” not iOS/tvOS/watchOS, since Process can't spawn a subprocess there.

Ships from a generated mirror, hausfold/holt-swift β€” send changes to sdk/swift here, never to the mirror directly.

.package(url: "https://github.com/hausfold/holt-swift", from: "0.1.0")
import Holt

let holt = HoltClient()
let envelope = try await holt.list()
for try await line in holt.watch() {
    if case .event(let event) = line, event.kind == .created {
        print("new lane:", event.lane?.name ?? "?")
    }
}

See sdk/swift/README.md for the full API.

Go β€” github.com/hausfold/holt/sdk/go

sdk/go β€” the same client over os/exec: List, Watch/WatchLane as Go 1.23 range-over-func iterators, Child/Spawn, Park/Unpark/Reap/Reship, and occupancy leases via Client.Lease.

The one SDK with zero setup: its own nested go.mod (github.com/hausfold/holt/sdk/go) means go get resolves it straight from this repo β€” no publish step, no separate mirror. No tagged release yet, so go get github.com/hausfold/holt/sdk/go@<commit-sha> for now.

⚠️ The module path moved. Versions up to v0.2.8 are published on Go's immutable proxy under the pre-2026-08-16 owner and stay resolvable there forever; everything from the next release on is at github.com/hausfold/holt/sdk/go. An existing importer has to edit its import line to pick up anything newer.

import (
	"context"
	holt "github.com/hausfold/holt/sdk/go"
)

c := &holt.Client{}
envelope, err := c.List(context.Background())
for line, err := range c.Watch(context.Background()) {
	if err == nil && line.Kind == holt.WatchCreated {
		fmt.Println("new lane:", line.Lane.Name)
	}
}

See sdk/go/README.md for the full API.

Rust β€” hausfold-holt

sdk/rust β€” the same client, async (tokio): list(), watch()/watch_lane() as a Stream of typed lines, child/spawn, park/unpark/reap/reship, and occupancy leases via HoltClient::lease. Drops into an axum/tonic backend or a plain async binary equally.

cargo add hausfold-holt
use futures_util::StreamExt;
use holt::HoltClient;

let client = HoltClient::default();
let envelope = client.list().await?;
let mut lines = Box::pin(client.watch());
while let Some(line) = lines.next().await {
    if line?.kind == holt::watch_kind::CREATED {
        println!("new lane created");
    }
}

See sdk/rust/README.md for the full API.


βŒ‚ hausfold

Directories ΒΆ

Path Synopsis
cmd
holt command
Command holt manages LANES β€” one coding agent's branch, checkout and pane β€” for any git repo.
Command holt manages LANES β€” one coding agent's branch, checkout and pane β€” for any git repo.
internal
config
Package config is holt's machine-wide configuration and, through it, holt's policy seams.
Package config is holt's machine-wide configuration and, through it, holt's policy seams.
exitcode
Package exitcode defines holt's public exit-code contract (SPEC.md Β§2.4).
Package exitcode defines holt's public exit-code contract (SPEC.md Β§2.4).
gitx
Package gitx is holt's git layer: subprocess calls to the real `git`, never a reimplementation of it.
Package gitx is holt's git layer: subprocess calls to the real `git`, never a reimplementation of it.
occupancy
Package occupancy answers the one question the reap is forbidden to guess at: is somebody standing in this checkout?
Package occupancy answers the one question the reap is forbidden to guess at: is somebody standing in this checkout?
registry
Package registry is holt's source of truth.
Package registry is holt's source of truth.
ui
Package ui is holt's only writer of human-facing text.
Package ui is holt's only writer of human-facing text.
sdk
go module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL