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

Built distribution (wheel)

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

Total release size: 29.5 kB

Release files / hausfold_holt-0.4.0.tar.gz

Download URL hausfold_holt-0.4.0.tar.gz
Size 14.1 kB
Tags Source
SHA-256 checksum
How to use checksums
ac08c6827fbf3f3b7eea5715f364e3280f862d3d4a29b0980100c9fa5cb67173
BLAKE2b-256 checksum
How to use checksums
5ed6a6df225a02948678abad360a4ba27b3382905f9f2edc3911bfd8791601ea
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 23, 2026.

Transparency log

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

Download URL hausfold_holt-0.4.0-py3-none-any.whl
Size 15.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5907a6423f3ffb2cc196b487756bab7c8dba6c8a6bdf4e594554fdd9fad4dcf7
BLAKE2b-256 checksum
How to use checksums
11bcccd645c884949640a535bd64a4951b1cf9a4f4177edbe889669ecbe72cd8
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 23, 2026.

Transparency log

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

This release

0.4.0 This release

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