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 has no daemon, so this SDK shells out to it (asyncio.create_subprocess_exec + --json, watch --json for a live NDJSON stream).

Async-first: watch() is naturally a stream. A sync script can still call every method via asyncio.run(...).

Import name is holt; the package on PyPI is hausfold-holt.

Install

pip install hausfold-holt
# or: uv add hausfold-holt

For local development against this repo instead: pip install -e sdk/python.

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

Two shapes of usage

Programmatic. 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.
        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)

# Or scoped to the one lane this session holds — no hello/ready framing,
# and nothing about anybody else's lanes.
async for event in holt.watch_lane(lane_dir):
    if event.kind == "reaped":
        end_session()

Interactive. 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, without checking for a TTY, so piped stdio blocks forever. Use resume() instead: it detects piped stdout and prints the reopen command as text rather than exec'ing.

Holding a session open: leases

holt's sweep (reap) needs to know a checkout is in use. On a human's machine, lsof answers that; a server has no pane or 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.

holt.lease(...) is a coroutine, so it can await the first heartbeat before returning: 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. Async generators aren't guaranteed to close promptly when they go out of scope — wrap long-lived use in contextlib.aclosing() to tear down the subprocess deterministically:

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. Import just the dataclasses if you're modeling the same wire shape elsewhere:

from holt import HoltLane, WatchEvent

What's NOT here yet

hook create/hook remove have no wrapper — shell out via run() if you need them. Types are hand-ported from the Go structs, not generated; if holt's JSON shape drifts from this file, that's a bug here.

Testing

tests/fake-holt.sh stands in for the real binary so tests don't need a Go build.

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

Release files for hausfold-holt 0.4.2

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.4.2
File Size Uploaded
hausfold_holt-0.4.2.tar.gz 14.1 kB Details

Built distribution (wheel)

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

Total release size: 29.5 kB

Release files / hausfold_holt-0.4.2.tar.gz

Download URL hausfold_holt-0.4.2.tar.gz
Size 14.1 kB
Tags Source
SHA-256 checksum
How to use checksums
66b0313e312cc737a9e968a446e3fc865373c566b799f4d8a77c321dafa0c63d
BLAKE2b-256 checksum
How to use checksums
689748c69b3e9c2155e27a3329965ea6f5e00cfc680b342cc7cf57f7fda1fcb1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.

Transparency log

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

Download URL hausfold_holt-0.4.2-py3-none-any.whl
Size 15.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6bf7a11225e55cb37f915037128a19b54275d41f7f3381e6b62fe7a7cd43694e
BLAKE2b-256 checksum
How to use checksums
163c5643a3eac17ae3e97c996b940ae937b54010caaef7c16ee734033538c9d4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.1

2 release files

0.5.0

2 release files

This release

0.4.2 This release

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

0.1.0

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