Skip to main content
A client already compatible with OpenAI has exactly one thing to change: base_url. Everything else — model, messages, parameters — stays the same. Haki injects memory before the call, forwards to the configured provider, captures the exchange afterwards, and returns the provider’s response byte for byte.

Memory identity headers

Identity travels only in headers, never in the request body — the model never chooses what gets remembered.
string
Required to activate memory. Without it: plain pass-through, X-Haki-Memory: disabled, no capture — an existing OpenAI client never breaks by pointing at the gateway.
string
Conversation thread, forwarded to the captured event.
string
Run id, forwarded to the captured event.
string
Forwarded to build_context (see Policy Engine, rule 3).
string
Default: "gw-" + sha256(raw body). A retry with the same body never captures the same exchange twice.
string
Used only in open dev mode (HAKI_AUTH_REQUIRED=false, no key so no resolved project); defaults to prj_gateway_dev. With a real hk_... key, the project is the key’s project, no exception — the chat/completions body carries no project_id anyway.

What happens on every call

1

Scope resolution

The hk_... key (auth middleware already extended to /gateway/v1/*) resolves project_id. The Haki key is never forwarded upstream — only the HAKI_LLM_* credentials are.
2

Context construction

If X-Haki-Subject-Id and a last user message are both present: build_context(...) (the same Context Assembler as /v1/context) is called with that last message as the query.
3

Injection

The packet is rendered by build_prompt_context (the same function as the Python SDK — one implementation, never a divergent copy) and prepended to the system message inside a <haki_memory>…</haki_memory> block (a system message is created first if none existed).
4

Forwarding

POST {HAKI_LLM_BASE_URL}/chat/completions, 60s timeout.
5

Capture (after the response, best-effort)

If memory was active and the response is 2xx: the exchange becomes an idempotent conversation.turn event, and a consolidation job is queued — off the critical path.
6

Response returned

The provider’s body and status, unchanged, plus three Haki headers.

Response headers

string
required
active (memory injected and exchange captured) · disabled (no subject, or stream: true) · degraded (context construction failed — the request still goes through, without memory).
uuid
Present when memory is active — inspectable via GET /v1/inspect/{trace_id}.
string
Duration of build_context in milliseconds (1 decimal).

Clean degradation

The agent is never blocked by Haki:

Streaming: pure pass-through, a deliberate choice

stream: true is a raw SSE pass-through: no injection, no capture, unconditional X-Haki-Memory: disabled. This is not a limitation to fix: injecting memory without being able to capture the final answer would break the memory loop (“no final answer without a Haki pass afterwards”); buffering the whole stream would defeat the very point of streaming. Documented in the README and in the code (app/gateway/__init__.py).

Honest limit

The gateway sees calls to the model, not the tools an agent runs locally between calls — those must be captured via the SDK or the API directly (reference: research/Haki_Memory_Runtime.md).

Latency

The memory overhead is dominated by build_context (~15 ms locally; p95 of /v1/context < 250 ms — see the measured numbers). Reproducible benchmark: