HAPPI/1.2 · Architecture Reference

How HAPPI Works

Three diagrams covering the request flow, event lifecycle, and transport layer — everything needed to implement a conforming runtime.

01 · Request flow

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.

flowchart LR Client(["Client\n(any language)"]) Envelope["HAPPI Envelope\n{v, id, cmd, args}"] Runtime(["HAPPI\nRuntime"]) Router["Provider\nRouter"] Anthropic["Anthropic\nAdapter"] OpenAI["OpenAI\nAdapter"] Gemini["Gemini\nAdapter"] Stream["NDJSON\nEvent Stream"] Client -->|"stdin / HTTP POST"| Envelope Envelope --> Runtime Runtime --> Router Router -->|cmd starts with\nanthropicXXX| Anthropic Router -->|cmd starts with\nopenaiXXX| OpenAI Router -->|cmd starts with\ngeminiXXX| Gemini Anthropic -->|normalise| Stream OpenAI -->|normalise| Stream Gemini -->|normalise| Stream Stream -->|"stdout / HTTP stream"| Client
Entry point
stdin
Canonical transport. One line of JSON in, NDJSON stream out.
Routing key
cmd
The cmd field selects the provider adapter. No magic inference.
Normalisation
per adapter
Each adapter converts provider-specific SSE frames into HAPPI events.
Latency cost
< 1 ms
Runtime overhead is a JSON parse + a dispatch table lookup.

02 · Event lifecycle

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. meta carries out-of-band signals (rate-limit headers, model version) and never affects the primary output.

stateDiagram-v2 [*] --> started : runtime accepts envelope started --> delta : first token arrives delta --> delta : more tokens delta --> tool_call : model invokes a tool tool_call --> tool_result : client executes tool tool_result --> delta : model resumes delta --> completed : provider signals stop delta --> error : provider signals failure started --> error : upstream connection fails completed --> [*] error --> [*] note right of started ts = 0 id anchored end note note right of completed usage.in_tokens usage.out_tokens ts = total ms end note
Always first
started
Guaranteed. A client can treat its absence as a transport error.
Always last
completed / error
Exactly one terminal event closes every stream. No ambiguous half-open states.
Interleaved
tool_call
Tool round-trips suspend the delta sequence and resume it transparently.
Out-of-band
meta
Carries headers and diagnostics. Never part of the primary text output.

03 · Transport layer

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.

flowchart TD Contract["HAPPI Contract\nOne envelope in → NDJSON stream out"] subgraph Transports ["Transport Layer (all produce identical events)"] stdio["stdio\nCanonical · pipe JSON in\nread NDJSON out"] http["HTTP / SSE\nPOST /dispatch\napplication/x-ndjson"] ws["WebSocket\nOne message in\nstream of messages out"] queue["Message Queue\nPublish envelope\nConsume event stream"] end Contract --> stdio Contract --> http Contract --> ws Contract --> queue stdio --> Provider(["Provider\nAdapter"]) http --> Provider ws --> Provider queue --> Provider
Canonical
stdio
The reference implementation. Works in every shell and language with zero dependencies.
Web-native
HTTP / SSE
POST the envelope, receive application/x-ndjson. Fetch-compatible.
Browser
WebSocket
Natural for browser clients — one message in, stream of messages out.
Async
Queue
Decouple sender from receiver. The event stream is still NDJSON on the consumer side.