poly-harness-dispatcher (PHD)
Spawn headless AI coding agents (Claude Code claude -p, Codex codex exec, OpenCode opencode run)
from one harness-neutral spec, and get back one normalized result.
You describe the agent once as a stack (identity, skills, MCP servers, hooks in a config/ directory) and the
run as a spec (harness, model, working directory, credentials, options). PHD builds an isolated config home per
run, launches the CLI, parses its stream into events and a RunResult, and records everything under
~/.phd/runs/<run_id>/.
Status: layer 1 (the core spawner) is built. It runs agents on the host with no sandbox; sandboxing, an API and other components are planned (architecture).
Install
Python 3.12+; the harness CLIs you use must be on PATH.
pip install poly-harness-dispatcher # import name: phd
From a clone (development, with uv):
uv sync --extra dev # or: uv venv && uv pip install -e ".[dev]"
uv run pytest -q # offline test suite
Quickstart
from phd import Credentials, Dispatcher, Inputs, Spec, Stack
spec = Spec(
harness="claude", # or "codex", "opencode"
model="haiku",
stack=Stack.load("tests/fixtures/stack"), # the bundled test stack
cwd="path/to/repo",
credentials=Credentials.from_host("claude"),
secrets={"ECHO_PREFIX": "phd-echo"}, # fills ${env:ECHO_PREFIX} in the stack
)
d = Dispatcher(spec)
result = d.spawn(Inputs(msg="List the names of your skills."))
print(result.status, result.cost_usd, result.result_text)
followup = d.spawn(Inputs(msg="Which one has a scripts/ folder?"), session_id=result.session_id)
To compose a stack from several sources (a base stack plus extra skills, MCP servers or hooks, local or https://)
and keep it in a local store, use phd.stacks:
import phd.stacks as stacks
stack = stacks.resolve(stacks.compose("reviewer", "1.0.0", base="tests/fixtures/stack",
skills=["path/to/extra-skill"]))
spec = Spec(harness="claude", model="haiku", stack=stack, cwd="path/to/repo",
credentials=Credentials.from_host("claude"))
In async code, d.run(...) ties the run to a block: leaving it cancels a run that has not finished, and a harness
whose caller dies is killed too (Windows Job Object, POSIX reaper).
async with d.run(Inputs(msg="List the names of your skills.")) as handle:
async for ev in handle.events():
print(ev.type) # run.started, session.started, message, ..., run.finished
result = await handle.wait()
session_id= continues that harness session (the home is re-materialized from the current spec).
spawn() blocks; in Jupyter or async code use await d.start(inputs), then
async for ev in handle.events() and await handle.wait() (see examples/quickstart.ipynb). A failed run does
not raise: check result.status and result.error. Problems detectable before launch raise a PhdError.
On macOS the Claude login lives in the Keychain: use Credentials.claude_oauth_token(token) with a token from
claude setup-token instead of from_host("claude").
Documentation
- Documentation index: what is built, what is planned, and every page
- Getting started: install, authentication per harness, first agent, tests, dry runs
- Stack resolver: compose and store stacks from several sources
- Architecture: package layout, data flow, roadmap components
- API reference
- Design record
Build the docs site locally: uv sync --all-extras && uv run mkdocs serve.
Metadata
Release files for poly-harness-dispatcher 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| poly_harness_dispatcher-0.1.1.tar.gz | 354.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| poly_harness_dispatcher-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 481.9 kB
Release files / poly_harness_dispatcher-0.1.1.tar.gz
| Download URL | poly_harness_dispatcher-0.1.1.tar.gz |
|---|---|
| Size | 354.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
76b5f781bbda11023352b2315175c2337fb7324847dc67413fddb72ddab7b21f
|
|
BLAKE2b-256 checksum How to use checksums |
69486a6b7a5a3928dad6938a22ee6b07706c688b46a5843c172f2f6af2ac6415
|
| 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 13, 2026.
Transparency logRelease files / poly_harness_dispatcher-0.1.1-py3-none-any.whl
| Download URL | poly_harness_dispatcher-0.1.1-py3-none-any.whl |
|---|---|
| Size | 127.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
114663e47631464dbd3c45ef21196b01ff71877bc982718bf74b7d29b43529e7
|
|
BLAKE2b-256 checksum How to use checksums |
38fa4cd999bd1487ae900d45f0741b281a61f542d8670ba4652431d7db7dec58
|
| 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 13, 2026.
Transparency log