- Spec
- HAPPI
- Version
- 1.5
- Diagrams
- 04
- Status
- Current
How HAPPI Works
Four diagrams covering the request flow, the event lifecycle, the signed-record family, and the transport layer — everything needed to implement a conforming runtime.
Client → Runtime → Provider → Stream
A HAPPI call starts with a single JSON envelope written to
the runtime's stdin (or POSTed to /dispatch). The runtime
validates the envelope, routes it to the correct provider adapter, and
opens a streaming connection to that provider. Each provider chunk is
normalised into a HAPPI event and flushed immediately to the client as an
NDJSON line. The client never sees provider-specific framing.
Drag the diagram to pan →
cmd field selects the provider adapter. No magic inference.started → delta × N → completed
Every HAPPI response follows a deterministic state machine. A
started event is always emitted first — it anchors the
id and sets ts=0. Zero or more delta
events carry incremental text. The stream closes with exactly one terminal
event: completed (success) or error (failure).
tool_call and tool_result pairs may interleave
with deltas when the provider uses tool use, and a sub_request
marks a child envelope dispatched mid-stream — the parent stream resumes
once the child's own stream closes.
Seven streaming events, three signed records. The seven
above are the whole of the streaming vocabulary, and Axiom II holds that
they are sufficient. The other three — idr,
context and continuity — sit alongside this
machine rather than inside it: they record what a run decided, believed and
assumed. idr and context follow the terminator;
continuity deliberately precedes the work it governs. Diagram
03 covers all three.
Drag the diagram to pan →
envelope. A dispatch can fan out and still emit one flat stream.Decided, believed, assumed
idr records what was decided; context records what
was believed. A continuity event, added in HAPPI/1.5, records
what was assumed — the load-bearing premise an agent is
about to act on, named together with the observation that would refute it.
It completes the family: decision, belief, and the reasoning path between
them.
The event is not a terminator, and that is the whole design.
A continuity-commit is emitted BEFORE the dispatch whose
reasoning depends on it, so a reader checking the stream can see that the
premise and its falsifier predate the outcome. Reasoning recorded after the
result can always be fitted to it; reasoning recorded before it cannot. A
later continuity-verdict settles the commitment it names, with
one of CONGRUENT, INCONGRUENT or
UNVERIFIABLE.
Drag the diagram to pan →
A runtime emits the record with the continuity.append cmd. The
address is a content hash over the commitment body — canonical JSON with
sorted keys, excluding the volatile id, ts and
audit fields — and it is byte-identical to the
context address algorithm, so one implementation serves both.
The runtime rejects an unrecognised kind, and rejects a verdict
with no settles, rather than writing a record that cannot be
checked.
{"v":"happi/1.5","id":…,"type":"continuity","ts":…,
"continuity_ref":{
"sha256": "sha256:<hex>",
"kind": "continuity-commit" | "continuity-verdict",
"predecessor_continuity": <id of the prior node, null at genesis>,
"settles": <sha256 of the commitment — required on a verdict>,
"model_versions": [ … ]
}}
Emitted, not described — bash happi.md run
$ echo '{"v":"happi/1.5","id":"c1","cmd":"continuity.append",
"args":["{\"claim\":\"staging schema matches prod\",\"falsifier\":\"a column in prod is absent in staging\",\"deps\":[\"migration-0142\"],\"severity\":\"high\"}"],
"flags":{"kind":"continuity-commit"}}' | bash happi.md run
{"v":"happi/1.5","id":"c1","type":"started","ts":0}
{"v":"happi/1.5","id":"c1","type":"continuity","ts":0,"continuity_ref":{"sha256":"sha256:10acdeb6d5dd6e30b73fa0a35497ce464614f1568948573c363ab9f4f983f2cd","kind":"continuity-commit","predecessor_continuity":null,"settles":null,"model_versions":[]}}
{"v":"happi/1.5","id":"c1","type":"completed","ts":0,"usage":{"content_addr":"sha256:10acdeb6d5dd6e30b73fa0a35497ce464614f1568948573c363ab9f4f983f2cd","kind":"continuity-commit"}}
Read the order, because the order is the claim. The
continuity line sits between
started and completed — it does not close the
stream, completed does. That is what lets a real run put its
premise and its falsifier on the wire first and then carry on: anything
emitted afterwards is already committed against. Settling it takes a second
envelope carrying flags.settles set to that
sha256 — the literal string printed above, which is what makes
the two records join.
The settlement — settles is the address above, verbatim
$ echo '{"v":"happi/1.5","id":"c2","cmd":"continuity.append",
"args":["{\"verdict\":\"INCONGRUENT\",\"axis\":\"schema\",\"evidence\":\"prod.orders.tax_code absent in staging\",\"verifier\":\"migration-diff\"}"],
"flags":{"kind":"continuity-verdict","predecessor_continuity":"c1",
"settles":"sha256:10acdeb6d5dd6e30b73fa0a35497ce464614f1568948573c363ab9f4f983f2cd",
"model_versions":["claude-opus-4-7"]}}' | bash happi.md run
{"v":"happi/1.5","id":"c2","type":"started","ts":0}
{"v":"happi/1.5","id":"c2","type":"continuity","ts":0,"continuity_ref":{"sha256":"sha256:7eb4a426c4f0498be0769800cf35df1992aa03d4a1a253869212f5ae7f426388","kind":"continuity-verdict","predecessor_continuity":"c1","settles":"sha256:10acdeb6d5dd6e30b73fa0a35497ce464614f1568948573c363ab9f4f983f2cd","model_versions":["claude-opus-4-7"]}}
{"v":"happi/1.5","id":"c2","type":"completed","ts":0,"usage":{"content_addr":"sha256:7eb4a426c4f0498be0769800cf35df1992aa03d4a1a253869212f5ae7f426388","kind":"continuity-verdict"}}
Run both yourself and the addresses come back identical, because the address
is a hash of the body and nothing else — not the clock, not the run. That is
the whole point of a content address: the commitment you can reproduce is
the commitment that was made. Note also what the first record shows about
model_versions: the spec table marks it required, but the
commit above passed none and the runtime emitted [] and exited
zero. It is required of the author, not enforced by the runtime.
And what it refuses — both checks, quoted from the runtime
$ echo '{"v":"happi/1.5","id":"x","cmd":"continuity.append","args":["{\"verdict\":\"CONGRUENT\"}"],"flags":{"kind":"continuity-verdict"}}' | bash happi.md run
{"v":"happi/1.5","id":"x","type":"started","ts":0}
{"v":"happi/1.5","id":"x","type":"error","ts":0,"code":"parse_error","message":"continuity.append: a continuity-verdict must set flags.settles to the content address of the commitment it settles"}
# exit 1
$ echo '{"v":"happi/1.5","id":"x","cmd":"continuity.append","args":["{\"claim\":\"a\"}"],"flags":{"kind":"continuity-guess"}}' | bash happi.md run
{"v":"happi/1.5","id":"x","type":"started","ts":0}
{"v":"happi/1.5","id":"x","type":"error","ts":0,"code":"parse_error","message":"continuity.append: kind must be one of continuity-commit, continuity-verdict, got 'continuity-guess'"}
# exit 1
A verdict that names no commitment cannot be joined back to a premise, and a kind the runtime does not know cannot be replayed by anything reading the chain. Both would produce a record that looks like evidence and is not, so the runtime declines to write either.
The protocol carries the record, never the
policy. Dependency-contamination closure, settlement
worklists, predictor stacks and any braking or halting behaviour built on
these records live in the harness — exactly the division idr
and context already keep. HAPPI/1.5 is fully backward
compatible: the runtime accepts happi/1.0 through
happi/1.5 and emits happi/1.5.
continuity_ref. Not a terminator — the ordering is the guarantee.claim, falsifier, deps and severity. A verdict names verdict, axis, evidence and verifier.Same contract, any wire
The HAPPI contract is transport-agnostic. The same envelope object and the
same event stream appear identically whether the bytes travel over a Unix
pipe, an HTTP connection, a WebSocket, or a message queue. Runtimes MUST
expose at least stdio. Additional transports are additive — a
client written for stdio works against HTTP without modification because the
framing is the contract, not the transport.
Drag the diagram to pan →
application/x-ndjson. Fetch-compatible.