Skip to main content

ai-hats-client

Drive an ai-hats headless session from a program: send turns, wait for each one to end, and read everything the session did.

ai-hats headless runs a role's interactive session with no terminal. Its stdin takes commands, one JSON object per line (commands/v1). Its stdout opens with a header line (headless/v1) and then copies the session's event log byte for byte (events/v1). Its exit code says how the session ended. This package speaks that contract and nothing else. It uses only the standard library and imports nothing from ai-hats.

from ai_hats_client import HeadlessSession

with HeadlessSession.start(["ai-hats", "headless", "-r", "maintainer"]) as s:
    first = s.turn("remember the word KIWI")
    second = s.turn("which word?")
    end = s.close()
assert "KIWI" in second.text and end.code == 0
  • HeadlessSession.start(argv, cwd=, env=) launches the holder and reads the header. If the holder refuses before the start, it raises SessionEnded, which carries the exit code.
  • prompt(text, id=None) sends a turn and returns its id. turn_for(id) waits for the turn_ended event that lists that id. turn(text) does both.
  • close() closes stdin, which tells the holder to finish its turns and exit. It then reads to the end and returns an Exit with the code, every event and the holder's stderr. terminate() signals the holder instead.
  • Leaving a with block closes the session. If the session does not end within start(..., close_timeout=60), the holder is terminated and the HeadlessTimeout is raised, so no session keeps spending turns.

Every wait has a bound. Every error carries what was read so far and the tail of the holder's stderr.

  • A command the holder would refuse raises ValueError before it is sent: empty text, an id that is not a lowercase canonical UUID, a decision other than allow or deny.
  • A command the holder does not run raises HeadlessError, and so does any command after the session has ended. The holder lists what it runs in header.commands.
  • prompt(), answer() and interrupt() may be called from another thread, for instance a watchdog that interrupts a turn while the main thread waits on it. The waits themselves belong to one thread.

Questions and interrupts

A session asks its stdin owner what it would ask a person at the terminal: a gate's ask, a consent point, claude's own permission prompt, leaving plan mode, the model's own question. Each question arrives as a person_asked event with a call_id, and the call waits for your answer.

def decide(question):
    s.answer(question["call_id"], "allow")          # or "deny", message="why"

turn = s.turn("push the branch", on_question=decide)
  • A turn wait hands each question to on_question. Without a handler it raises QuestionPending instead of waiting out its bound. Answer pending.question["call_id"] and wait again: nothing read so far is lost.
  • answer(call_id, "allow", answers={"<question>": "<answer>"}) answers a question the model asked with AskUserQuestion. tool_call(call_id) returns the call the question is about, with the questions and their options in its input.
  • A question stays offered until you answer it, its handler returns, or its call gets a result: a handler that raised sees it again on the next wait.
  • The first answer on a call id wins. The holder refuses the rest and logs a command_rejected signal for each.
  • interrupt() stops the running turn and keeps the session. The turn still ends with its turn_ended, and a question it had open is closed.

The holder never times out a question. If you will not wait, answer deny.

The client is synchronous. A caller that needs asyncio runs it in a thread.

Testing without a model

ai_hats_client.testing has a stand-in claude binary that speaks the same wire. Tests that use it need no model and no login:

import os

from ai_hats_client.testing import install

stub = install(tmp_path)  # writes tmp_path/stub-bin/claude
env = {**os.environ, **stub.env(os.environ["PATH"])}

Each prompt's text picks what the stub does. The directives are listed in the module docstring of ai_hats_client.testing.stub_claude.

Metadata

Release files for ai-hats-client 0.2.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 ai-hats-client 0.2.0
File Size Uploaded Provenance
ai_hats_client-0.2.0.tar.gz 22.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for ai-hats-client 0.2.0
File Interpreter ABI Platform
ai_hats_client-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 42.7 kB

Release files / ai_hats_client-0.2.0.tar.gz

Download URL ai_hats_client-0.2.0.tar.gz
Size 22.1 kB
Tags Source
SHA-256 checksum
How to use checksums
52afd400c73c77ddf8320c2cea3a93c62ed835d020171113dd9589b779383dd3
BLAKE2b-256 checksum
How to use checksums
cd4db0b8da6ca5b0ee06a0a6fa0e844e0a804538c451aa2925fa446e2439dbfc
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 Sep 27, 2026.

Transparency log

Release files / ai_hats_client-0.2.0-py3-none-any.whl

Download URL ai_hats_client-0.2.0-py3-none-any.whl
Size 20.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c3d62aab556794b79afa31ca580c59e003050b044cfbe51bb14a2b04930163b5
BLAKE2b-256 checksum
How to use checksums
adc4e1e3aa599a876438fcb814a4c9e38ebf2dddb7744fc19c2e95b322a6a47f
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 Sep 27, 2026.

Transparency log

Security for ai-hats-client 0.2.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ai_hats_client-0.2.0.tar.gz

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 Sep 27, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

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