Skip to main content

hausfold-holt (Python SDK)

A thin Python client over the holt binary — the worktree-lifecycle substrate for parallel coding agents. holt stays a binary; this SDK shells out to it (asyncio.create_subprocess_exec + --json, watch --json for a live NDJSON stream) rather than talking to a daemon, because there isn't one (SPEC.md §14.1).

Async-first, like the wire protocol wants: watch() is naturally a stream, and the obvious host for this SDK — a web backend serving many concurrent agent sessions — is async-native in Python too (FastAPI, Starlette, aiohttp). A sync script can still call every method with asyncio.run(...).

Import name is holt; the package on PyPI is hausfold-holt (import holt after pip install hausfold-holt, same split as most <org>-<name> distributions).

Install

Not published yet — for now, reference it from within this repo (pip install -e sdk/python) or copy sdk/python out. Once published: pip install hausfold-holt / uv add hausfold-holt.

holt itself must be on PATH, or pass HoltClientOptions(bin="/path/to/holt").

Two shapes of usage

Programmatic (a web backend, an orchestrator). Every HoltClient method except the two ending in _interactive captures the child's stdout and returns — safe to call from a server with many concurrent sessions.

import asyncio
from holt import HoltClient

async def main() -> None:
    holt = HoltClient()

    envelope = await holt.list()
    for lane in envelope.lanes:
        # occupied/dirty are `bool | None` — None means "not determined",
        # never coerce it to False (SPEC.md §2.2's whole nullable-discipline
        # point).
        print(lane.name, lane.state, lane.occupied)

    # Create a lane WITHOUT attaching an agent to it — the primitive an
    # orchestrator wants. child/spawn only ever print the new path.
    lane_dir = await holt.child("/path/to/some-repo", "task-42")
    # ...now launch YOUR OWN agent process against lane_dir.

asyncio.run(main())
# Live updates instead of polling — created/parked/resumed/reaped/changed.
async for line in holt.watch():
    if line.kind == "created" and line.lane is not None:
        notify_ui(line.lane)

Interactive (a real terminal TUI). new_interactive / resume_interactive inherit the calling process's stdio, so when holt execs the configured agent client (claude, codex, opencode), it takes over the real terminal — same as running holt new by hand — and control returns to you when that session ends.

# A terminal app, run in an actual TTY:
await holt.new_interactive("task-42")
# ... the agent owned the screen; you're back here when it exits.

Do not call new_interactive from a server. holt new execs the agent client unconditionally — it doesn't check for a TTY the way resume does — so calling it with piped stdio blocks forever with your pipes attached to whatever the agent expects on stdin. resume() (the non-interactive form) is safe from a server: holt detects the piped stdout and prints the reopen command as text instead of exec'ing.

Holding a session open: leases, not callbacks

holt's sweep (reap) needs to know a checkout is in use. On a human's machine, lsof answers that. A server holding one session per lane has no pane and no shell cwd'd anywhere — so it says so itself, with a lease:

lease = await holt.lease(lane_dir)  # refreshes on an interval, < the 90s TTL
# ... serve the session ...
await lease.release()

Pass pid= instead when the lease should track a real local process — the OS then drops it the instant that pid dies, no refresh loop needed.

A lease can only save a lane from reap, never condemn one — "nobody leased it" isn't proof nobody's there. See SPEC.md §14.2.

holt.lease(...) is a coroutine here, unlike the TS SDK's constructor-based holt.lease(...): Python can await the first heartbeat before returning, so a failure to take the lease raises immediately instead of surfacing on the next refresh or release call.

watch() cleanup

watch() returns an async generator; stop consuming (break, or .aclose()) to kill the underlying process. CPython's refcounting closes a sync generator promptly when it goes out of scope, but async generators aren't guaranteed the same — wrap long-lived use in contextlib.aclosing() if you want the subprocess torn down deterministically rather than on the next GC pass:

from contextlib import aclosing

async with aclosing(holt.watch()) as stream:
    async for line in stream:
        ...

Types for a frontend

holt.types has no runtime dependencies beyond the standard library — subprocess/asyncio only appear in exec.py/watch.py/client.py. Import just the dataclasses if you're modeling the same wire shape elsewhere (e.g. your web backend fans watch() out over its own websocket, and something downstream needs HoltLane/WatchEvent to validate what it receives):

from holt import HoltLane, WatchEvent

What's NOT here yet

  • hook create/hook remove (the Claude Code hook protocol, SPEC.md §2.3) have no wrapper — they're for editor integrations, not the orchestrator use case this SDK targets first. Shell out via run() if you need them.
  • The --json envelope's future fields (pr, overlap, ahead/behind — SPEC.md §2.2's example, gated behind the overlap/forge-polling milestones) aren't in HoltLane because they aren't on the wire in schema 1 yet. Don't add them here before internal/commands/json.go does.
  • Types are hand-ported from the Go structs, not generated, same as the TS SDK. If holt's JSON shape and this file drift, that's a real bug class this SDK exists to avoid — SPEC.md §14.1 says "generate SDK types from it" as the intended end state.

Testing

tests/fake-holt.sh stands in for the real binary so tests don't need a Go build — it's a fixture, not a spec of holt's behavior, kept in sync by hand with sdk/ts/test/fake-holt.sh. Once holt builds in CI, add a second suite that runs the same assertions against the real binary in a scratch repo.

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
mypy src

Release files for hausfold-holt 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hausfold-holt 0.1.0
File Size Uploaded
hausfold_holt-0.1.0.tar.gz 14.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hausfold-holt 0.1.0
File Interpreter ABI Platform
hausfold_holt-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 29.9 kB

Release files / hausfold_holt-0.1.0.tar.gz

Download URL hausfold_holt-0.1.0.tar.gz
Size 14.2 kB
Tags Source
SHA-256 checksum
How to use checksums
52c17f6d2c8c7ad82e99183438e965277ce965823f3ad7061521512d6e748f52
BLAKE2b-256 checksum
How to use checksums
e448a6a8d88045f9be357388542b6fec0def4e04010696f7496c590052733cce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / hausfold_holt-0.1.0-py3-none-any.whl

Download URL hausfold_holt-0.1.0-py3-none-any.whl
Size 15.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bd807e3f3c9cc45422aeb0f6e07222de06e6c5d459e63647f878f543105bc173
BLAKE2b-256 checksum
How to use checksums
281b63b80ba777c46cf83259f0289da53d34006bf8de417831f27b66ff4d9a1d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release history Release notifications | RSS feed

0.5.1

2 release files

0.5.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.94

2 release files

0.2.93

2 release files

0.2.92

2 release files

0.2.91

2 release files

0.2.9

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page