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, whose call_id names the call. Match a refusal to your answer by it, never by the detail text; it may land in a later turn than the question, and events holds every one read so far.
  • A question you are offered may already be closed: the session withdrew it, or its call's result had not been read yet. Answering it is then not a mistake — the answer comes back refused, and whether the call ran is the ok of its tool_result_received.
  • 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.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 ai-hats-client 0.4.0
File Size Uploaded Provenance
ai_hats_client-0.4.0.tar.gz 24.4 kB With provenance Details

Built distribution (wheel)

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

Total release size: 45.9 kB

Release files / ai_hats_client-0.4.0.tar.gz

Download URL ai_hats_client-0.4.0.tar.gz
Size 24.4 kB
Tags Source
SHA-256 checksum
How to use checksums
750fda5484349bc666bd39de2b0ff2093ffc06c5beb27444f3dec6348c9179c3
BLAKE2b-256 checksum
How to use checksums
6c334a7d4e7dfca7a9503ff582ef8e789a5804c937f8c67123b57ee91240fdd6
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 Oct 9, 2026.

Transparency log

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

Download URL ai_hats_client-0.4.0-py3-none-any.whl
Size 21.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f69614a71033c4c4a6dc4d4d0436fefbcca19b31bd4375a250a26481dc81f9fb
BLAKE2b-256 checksum
How to use checksums
e6d5cb810454ba9a00cfee3ebe36ad0b3339d5ebfffaa2f4e726d2856bd1bf34
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 Oct 9, 2026.

Transparency log

Security for ai-hats-client 0.4.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.4.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 Oct 9, 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.4.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