H3 Harness SDK for Python
Python SDK for building H3-compliant agent harnesses.
Install
pip install h3-harness-sdk
Install fallback (source / git)
If a release isn't published to PyPI yet (or you want the latest unreleased changes), install directly from the repository:
# From git
pip install git+https://github.com/get-h3/sdk-python.git
# Editable source install (development)
git clone https://github.com/get-h3/sdk-python.git
cd sdk-python
pip install -e .
Quickstart
from datetime import datetime, timezone
from h3_harness import (
BaseHarness,
Decision,
DecisionType,
End,
TextResponse,
create_router,
)
from fastapi import FastAPI
class MyHarness(BaseHarness):
def __init__(self):
# Track sessions so cancel/session lookups 404 on unknown ids
# (battery: test_5_9b cancel_unknown_session, test_5_10 session_not_found).
self._sessions: dict[str, dict] = {}
async def on_process(self, req):
# Echo conversation history from context (battery: history preserved).
history = list(req.context.history)
# Streaming: "do not finish" in message -> unfinished text.
streaming = "do not finish" in req.message.content
finished = not streaming
self._sessions[req.session_id] = {
"started_at": datetime.now(timezone.utc).isoformat(),
"turn_count": (
self._sessions.get(req.session_id, {}).get("turn_count", 0) + 1
),
}
return Decision(
decision=DecisionType.TEXT,
text=TextResponse(
content=f"Echo: {req.message.content}",
finished=finished,
),
history=history,
)
async def on_result(self, req):
return Decision(decision=DecisionType.END, end=End(reason="task_complete"))
def get_session_info(self, session_id: str) -> dict | None:
return self._sessions.get(session_id)
app = FastAPI()
app.include_router(create_router(MyHarness()))
# Run with: uvicorn my_harness:app --port 9191
Testbed
import asyncio
from h3_harness.testbed import MockHermes
from h3_harness.examples.echo import EchoHarness
async def main() -> None:
mock = MockHermes(EchoHarness())
decision = await mock.send_message("Hello!")
assert decision.text.content == "Echo: Hello!"
if __name__ == "__main__":
asyncio.run(main())
Examples
- echo.py — Echo harness that mirrors user messages
- minimal.py — Minimal harness with health endpoint, uvicorn runner
- langchain_agent.py — LangChain integration: LLM call with text response
Passing the battery (h3-test compliance)
The gate for any H3 harness is the test battery (test_battery.py from
get-h3/shim — 44 tests across 6 categories).
Run it against any running harness endpoint:
# The shim is not yet published to PyPI — install from source (get-h3/shim)
pip install git+https://github.com/get-h3/shim
h3-test --endpoint http://localhost:9191 # exit 0 = compliant
The Quickstart harness above implements all four conventions and is fully battery-compliant (44/44). If you modify it, keep the conventions intact — a naive harness that drops them scores 41/44. The four conventions the battery checks (beyond "return a Decision") are:
- Echo
context.historyin every Decision returned fromon_process. The battery sends a session with prior history and asserts it flows back through the response (test_2_8_process_preserves_history). Pass it through explicitly:history = list(req.context.history) return Decision(..., history=history)
This applies to decisions returned fromon_process— theProcessRequestcarries thecontextfield.on_resultreceives aResultRequest, which has nocontextfield (onlydecision_id/result/session_id); a decision returned fromon_resultsimply omitshistory. Echoingreq.context.historythere raisesAttributeError. - Never issue
llm_callwhencontext.modelsis empty. The battery sendscontext.models: []and FAILS any harness that returns anllm_calldecision (test_5_8_no_models_available— "hallucinated model"). Only returnLLM_CALLwhen the request actually lists models. - Return
text.finished=falsefor "do not finish" prompts. The battery sends "Just start a thought, do not finish it yet." and asserts the response hastext.finished == False(test_2_4_process_text_finished_false). Detect streaming/unfinished intent and setfinishedaccordingly. - 404 unknown sessions. The battery cancels a nonexistent session
(
test_5_9b cancel_unknown_session) and GETs one (test_5_10 session_not_found) and asserts a 404. Track sessions in the harness (get_session_inforeturningNonefor unknown ids) — the router turns that into the 404.
The canonical battery-ready template is echo.py — it implements all four conventions and scores 44/44. Use it as the starting point for your own harness.
Development
make install # create venv + install deps
make build # build wheel (and sdist) into dist/
make test # run tests
make lint # ruff check
make fmt # ruff format
Reference
- Spec: get-h3/h3 — specs/04-SDK-Libraries.md
- Protocol: get-h3/protocol
- API reference: docs/api/index.md
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file h3_harness_sdk-0.1.3.tar.gz.
File metadata
- Download URL: h3_harness_sdk-0.1.3.tar.gz
- Upload date:
- Size: 152.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f33a4fd042f6ad69a2275dadb94407842e07587e04bbd651f65eb6b805799cc
|
|
| MD5 |
9fdd932e9bdee566a58b5d2ca37a5ff5
|
|
| BLAKE2b-256 |
cbc8149c5b496b8377a1665441cfde3208946f2034182cc592e10a63ef6479e9
|
File details
Details for the file h3_harness_sdk-0.1.3-py3-none-any.whl.
File metadata
- Download URL: h3_harness_sdk-0.1.3-py3-none-any.whl
- Upload date:
- Size: 17.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5907987e1028822bb513fe398f605c9495d5db28fa8d1d9f34a116250c6fade9
|
|
| MD5 |
9ebd2363d9477c39ddab39d0e0b62991
|
|
| BLAKE2b-256 |
d8986bb1059bf5fd39c3d94f56abff679e69766d47bf4a1375d503606090b7ae
|