{
  "markdown": "<div align=\"center\">\n\n# Haki\n\n### Reliable memory for AI agents\n*Context with proof: every fact carries a date, a source, and a status.*\n\n![Tests](https://img.shields.io/badge/tests-472%20Python%20%2B%2014%20Node%20passing-brightgreen)\n![Python](https://img.shields.io/badge/python-3.12-blue)\n![PostgreSQL](https://img.shields.io/badge/postgresql-16%20%2B%20pgvector-336791)\n![p95 context](https://img.shields.io/badge/p95%20context-249ms-orange)\n![License](https://img.shields.io/badge/license-Apache%202.0-lightgrey)\n\n**Haki gives any AI agent a memory that lasts for months —**\n**that tells current from stale — and that can prove every recollection.**\n\n[Quickstart](#quickstart) ·\n[Coded agent](#1-coded-agent--sdk-and-cli) ·\n[Cursor](#2-cursor--mcp-server) ·\n[n8n](#3-n8n--template-and-nodes) ·\n[Gateway](#4-openai-compatible-gateway) ·\n[API](#api-at-a-glance) ·\n[gethaki.space](https://gethaki.space)\n\n</div>\n\n---\n\n## What Haki does\n\nToday, an AI agent remembers nothing beyond a single conversation: every new\nsession starts from scratch, re-explains context, and can apply a preference\nthat went stale months ago with no way to tell.\n\nHaki is an open-source (Apache-2.0), persistent memory layer, independent of\nwhatever model or framework you use: it extracts structured facts from an\nagent's exchanges, keeps them current over time, and hands every new request\na relevant, dated, sourced context packet. It stays entirely under your\ncontrol — one `docker compose up` installs it, and your existing agent,\nmodel, and infrastructure don't change.\n\n---\n\n## The problem\n\nTeams building AI agents in production run into the same limits, every time:\n\n| Symptom | Consequence |\n|---|---|\n| The user has to repeat information already given | Degraded experience, churn |\n| The agent applies a preference that was overridden long ago | Wrong answer, broken trust |\n| The entire history gets replayed into the prompt on every call | High cost and latency, useful context diluted |\n| No way to explain why a piece of information was used | No traceability, no debugging |\n| One customer's data can leak into another's context | Security incident |\n\nExisting approaches (generic vector stores, conversation summaries) work in a\ndemo but degrade after a few weeks of real usage: stale information served as\ncurrent, undetected contradictions, zero explainability.\n\n---\n\n## The approach\n\n**A fact ledger, not a conversation history.** Haki doesn't archive raw\nmessages to replay later: it extracts structured facts from them —\npreferences, constraints, decisions — each one linked back to the source\nevent that grounds it.\n\n**Bitemporality and supersession.** Every fact carries an explicit validity\ndate and status. When information changes, the old fact is marked\n*superseded* — never silently deleted, never served again as current. On an\nunresolved contradiction, both versions are held back and flagged rather than\nserved at random.\n\n**Systematic traceability.** Every context packet injected comes with its\nsources, its validity dates, and a trace explaining which memories were kept,\nexcluded, or blocked, and why. \"Why did the agent use this piece of\ninformation?\" has a verifiable answer in under a minute.\n\n---\n\n## Quickstart\n\n> Prerequisites: Docker and [uv](https://docs.astral.sh/uv/). The defaults in\n> `.env.example` are enough to get started — no key required. For custom\n> configuration (a real LLM key, etc.), copy that file to `.env`.\n\n```bash\n# Infrastructure (PostgreSQL 16 + pgvector, Redis 7)\ndocker compose up -d\n\n# Dependencies (uv installs Python 3.12 if needed)\nuv sync\n\n# Database\nuv run alembic upgrade head\n\n# API\nuv run uvicorn app.main:app --port 8100\n```\n\n> If anything goes wrong, `bash scripts/doctor.sh` diagnoses Docker, the\n> containers, Postgres, `.env`, migrations, and the API in one command — read\n> only, no side effects, safe to re-run as often as needed.\n\nIn a second terminal, verify everything works:\n\n```bash\nuv run haki connect --api-url http://localhost:8100\nuv run haki verify\n```\n\n`haki verify` runs a complete scenario in a few seconds: a preference, then a\nchange of mind in the **same** conversation, then a **new** conversation that\nqueries memory. It must serve the current value, keep the old one at status\n`superseded` instead of erasing it, and tie the whole thing to a trace.\n\n![haki verify: capture a preference, change it in the same thread, then recall the current value from a new conversation with the old one marked superseded](docs-site/en/images/haki-verify-demo.gif)\n\n```\nhaki verify — subject usr_verify_91d952a5e06f\n\n  ✔ capture     \"Je préfère recevoir mes factures en français.\"    thr_35bb7ecf\n  ✔ consolidate 1 fact(s) extracted                                0.2s\n  ✔ capture     \"En fait, envoie-les moi en anglais plutôt, pa...\" thr_35bb7ecf (same thread)\n  ✔ consolidate 1 supersession                                     0.1s\n  ✔ context     NEW thread thr_3a21ef34                            0.0s\n\n    recalled  invoice_language = {\"language\": \"en\"}   valid since 2026-08-11\n    hidden    invoice_language = {\"language\": \"fr\"}   superseded\n    trace     7c99a8de-4905-43b4-94df-21fb66492b3b\n\nOK — your agent remembered across conversations, and it can prove it.  0.5s\n```\n\nThe command exits 1 if the stale value is still served, **or** if the old\nvalue isn't found marked as superseded: serving the right value by accident,\nwith no link between the two facts, isn't a memory that actually updates.\n\n> Multilingual by default: local embeddings are multilingual (French,\n> English, Spanish, and about fifty other languages) — the demo scenario\n> above is captured in French on purpose, and a query in a different\n> language still finds it. Verified end-to-end\n> (`scripts/check_multilingual.py`).\n\n---\n\n## Four ways to use Haki\n\n### 1. Coded agent — SDK and CLI\n\n*Python or TypeScript developers. A few lines around your existing LLM call.*\n\n```python\nfrom haki import HakiClient\nfrom haki.runtime import build_prompt_context, capture_turn\n\nclient = HakiClient(\"http://localhost:8100\")\n\n# Before the LLM call: memory becomes an instruction block\npacket = client.context(subject_id=\"usr_42\", query=user_msg, project_id=\"prj\")\nprompt = build_prompt_context(packet) + \"\\n\" + system_prompt\n\nanswer = my_llm(prompt, user_msg)   # your LLM and app code don't change\n\n# After the LLM call: the conversation turn goes back into memory\ncapture_turn(client, \"usr_42\", \"prj\", user_msg, answer)\n```\n\n<details>\n<summary><b>SDK details</b> (methods, async, errors)</summary>\n\n- `capture(events, idempotency_key)` — idempotent ingestion: a network retry\n  never creates a duplicate;\n- `context(subject_id, query, project_id, budget_tokens=2000)` — the\n  ContextPacket, with `trace_id`;\n- `inspect(trace_id)` — why these memories were chosen;\n- `timeline(subject_id, project_id)`, `consolidate_subject(...)`,\n  `facts(...)`, `consolidate()`, `forget(...)`, `health()`;\n- Async variant: `AsyncHakiClient`;\n- Typed errors: `HakiApiError` (`error_type`, `field`, `status_code`),\n  `HakiConnectionError`.\n\nCLI: `haki login` (device-code sign-in, see below), `haki connect`\n(configure and test with a key in hand), `haki verify` (timed memory test),\n`haki status` (API health), `haki mcp` (Cursor packaging).\n\n**`haki login`** — for a Cloud account, the `hk_` key is only ever shown\nonce, at provisioning: the terminal has no way to retrieve it again. The\ndevice-code flow (RFC 8628) closes that gap without a new secret. The CLI\nshows an `XXXX-XXXX` code and opens\n`<HAKI_CONSOLE_BASE_URL>/cli-auth` with the code already filled in\n(`verification_uri_complete`); the code stays on screen too, so it can be\ntyped by hand from a phone. You approve it in the console, already signed\nin — **the terminal then receives a fresh, dedicated key**, not the\nconsole's own — revoking that terminal from *Keys* disconnects nothing else.\nThe key is served exactly once, by the poll that consumes it.\n\nServer-side, `HAKI_CONSOLE_SERVICE_KEY` must be configured (it's what\nauthenticates the console against `/v1/cli/device/approve`). Wrong codes are\nrate-limited **per person**, not per IP: every approval arrives from the same\naddress (the console's own backend), so a per-IP counter would be a shared\nbucket any single user could exhaust for everyone else.\n</details>\n\n#### TypeScript SDK (parity with the Python SDK)\n\n*Same methods, same typed errors, same `<haki_memory>` block — zero runtime\ndependency (native fetch, Node 18+).*\n\n```bash\ncd sdk/typescript && npm install && npm run build && npm test\n```\n\n```typescript\nimport { HakiClient, buildPromptContext, captureTurn } from \"gethaki\";\n\nconst client = new HakiClient({ baseUrl: \"http://localhost:8100\", apiKey: \"hk_...\" });\n\nconst { packet } = await client.context({ subjectId: \"usr_42\", query: userMsg, projectId: \"prj\" });\nconst prompt = buildPromptContext(packet) + \"\\n\" + systemPrompt;\nconst answer = await myLlm(prompt, userMsg);\nawait captureTurn(client, { subjectId: \"usr_42\", projectId: \"prj\", userMsg, assistantMsg: answer });\n```\n\nCLI `haki-ts` (`node dist/cli.js …`): `connect`, `verify`, `status` — same\n`~/.haki/config.json` file as the Python CLI, the two are interchangeable.\nRunnable example:\n[`sdk/typescript/examples/basic-agent.mjs`](sdk/typescript/examples/basic-agent.mjs).\n\n### 2. Cursor — MCP server\n\n*Cursor users. One-click install, no key to copy by hand.*\n\n```bash\nuv run haki mcp   # prints the deeplink, the mcp.json, and the Project Rule\n```\n\n1. The \"Add Haki to Cursor\" deeplink installs the MCP server;\n2. The Project Rule (`.cursor/rules/haki.mdc`) tells the agent when to\n   remember and when to recall;\n3. Cursor then keeps decisions, conventions, and resolved bugs across\n   sessions.\n\nFour tools show up in Cursor:\n\n| Tool | Role |\n|---|---|\n| `haki_context` | Recall the project's relevant context before coding |\n| `haki_capture` | Store a decision, a convention, a resolved bug |\n| `haki_inspect` | See why a memory was used |\n| `haki_forget` | Forget a piece of information |\n\n> Known, documented limit: MCP can't intercept every Cursor conversation —\n> the server only sees the tool calls Cursor decides to trigger. The Project\n> Rule tells the agent *when* to call them; real coverage is measured, never\n> presented as total.\n\n### 3. n8n — template and nodes\n\n*No-code builders. One template to import, three things to configure.*\n\nChain: `Webhook → Haki Context → AI Agent → Haki Capture → Respond`\n\nTwo options in [`integrations/n8n/`](integrations/n8n/README.md):\n\n- Native template `haki-persistent-support-agent.json` — importable into any\n  n8n instance, no extra install (standard HTTP nodes);\n- Node package `n8n-nodes-haki` — visual `Haki Context` and `Haki Capture`\n  nodes, with built-in validation.\n\nThree settings are all it takes: the Haki credential, the LLM credential, and\nthe counterpart's identity (`subject`). A call with no identity is refused —\na memory with no stable identity isn't reliable.\n\n> Verified against a real n8n instance (Docker): a preference stated in the\n> first message is recalled in the second, with its source.\n\n### 4. OpenAI-compatible gateway\n\n*Apps already speaking the OpenAI API. Only `base_url` changes — memory\nbecomes automatic.*\n\n```python\nimport openai\n\nclient = openai.OpenAI(\n    base_url=\"http://localhost:8100/gateway/v1\",\n    api_key=\"hk_...\",                                 # Haki key\n    default_headers={\"X-Haki-Subject-Id\": \"usr_42\"},  # who to remember\n)\nclient.chat.completions.create(model=\"...\", messages=[...])\n```\n\nOn every `POST /gateway/v1/chat/completions` call: the subject's memory is\ninjected at the top of the system message (a `<haki_memory>…</haki_memory>`\nblock), the call is forwarded to the configured provider (`HAKI_LLM_*` — the\nHaki key itself is never sent upstream), the exchange is then captured\n(`conversation.turn`, idempotent), and consolidation resumes in the\nbackground. The response returned is the provider's own, unchanged, plus\nthree headers: `X-Haki-Memory`, `X-Haki-Trace-Id`, `X-Haki-Context-Ms`.\n\n- Identity travels via headers, never the request body (the model never\n  chooses what gets remembered): `X-Haki-Subject-Id` (required for memory),\n  `X-Haki-Thread-Id`, `X-Haki-Run-Id`, `X-Haki-Purpose`,\n  `X-Haki-Idempotency-Key` (default: a hash of the body — a retry never\n  creates a duplicate).\n- Controlled degradation: with no identity, the request passes through\n  unmodified (`X-Haki-Memory: disabled`); if context can't be built, the\n  request still goes out, flagged `degraded`. The agent is never blocked by\n  Haki.\n- Streaming: `stream: true` passes straight through\n  (`X-Haki-Memory: disabled`, no injection, no capture) — a deliberate\n  choice: injecting without being able to capture the final response would\n  break the memory loop, and buffering the whole stream would defeat the\n  point of streaming in the first place.\n- Documented limit (see `research/Haki_Memory_Runtime.md` in the private\n  repo): the gateway observes calls to the model, not tools the agent runs\n  locally between two calls — those are captured via the SDK or the API\n  directly.\n\nAn httpx variant lives in the SDK too: `haki.gateway.gateway_client(base_url,\napi_key, subject_id, ...)` (and `async_gateway_client`). Memory overhead is\ndominated by `build_context` (about 15 ms locally, `/v1/context` p95 under\n250 ms) — reproducible benchmark:\n`uv run python scripts/benchmark_gateway.py --api-key hk_...`.\n\n---\n\n## Hosted Cloud\n\n*Prefer not to run your own infrastructure?* [gethaki.space](https://gethaki.space)\nhosts the same API, plus a web console for browsing memory, inspecting\ntraces, and resolving conflicts by hand. Self-hosting stays fully supported\nand free — the API in this repository is the same one Cloud runs.\n\n---\n\n## How it works\n\n```mermaid\nflowchart LR\n    A[Incoming message] --> B[CAPTURE<br/>raw evidence,<br/>append-only]\n    B --> C[CONSOLIDATION<br/>extraction, dedup,<br/>supersession, conflicts]\n    C --> D[(MEMORY<br/>active facts,<br/>dated, sourced)]\n    D --> E[CONTEXT<br/>relevant packet,<br/>under budget,<br/>249ms p95]\n    E --> F[Agent and LLM]\n    F --> B\n    E -.-> G[INSPECT<br/>decision trace]\n    D -.-> H[FORGET<br/>propagated erasure,<br/>with a receipt]\n```\n\n1. **CAPTURE** — Your application sends an event (a message, an action, a\n   tool result). Haki records it as immutable evidence and replies in a few\n   milliseconds. A network retry never creates a duplicate (idempotence).\n2. **CONSOLIDATION** — In the background, Haki reads events and decides what\n   should become a durable fact. It deduplicates, detects changes (the old\n   fact becomes *superseded*) and contradictions (status *conflict*, held\n   back until resolved). A fact is identified by\n   **(subject, predicate, qualifiers)**: \"weekday wake-up time\" and \"weekend\n   wake-up time\" are two distinct, coexisting facts, not a contradiction —\n   and a different qualifier is never conflated with another one, no matter\n   how close the wording.\n3. **CONTEXT** — Before every response, the agent asks for relevant memory.\n   Haki only returns active, valid, in-scope facts, ranked by relevance,\n   within a strict token budget — p95 measured at 249 ms across 10,000 facts\n   (see `scripts/benchmark_context.py`).\n4. **INSPECT** — At any time, the trace explains why a piece of information\n   was kept, excluded, or blocked.\n5. **FORGET** — A correction or an erasure propagates to everything derived\n   from it, with a timestamped receipt.\n\n---\n\n## Concepts\n\n| Concept | Definition |\n|---|---|\n| **Subject** (`subject`) | The person or entity being remembered. A stable identity is required — no memory without one. |\n| **Event** | The raw evidence: \"this message was exchanged on this date.\" Immutable. |\n| **Fact** | A piece of information considered true at a given point in time. Dated, versioned, sourced. |\n| **Supersession** | One fact replaces another. The old one stays in history but is never served again as current. |\n| **Conflict** | Two facts contradict each other with no automatic arbitration possible: both are held back and flagged. |\n| **ContextPacket** | The memory packet injected before a response: the relevant facts, within budget, with their sources. |\n| **Trace** | The log explaining every memory decision: kept, excluded, blocked, and why. |\n| **Scope** | The sealed boundary of a memory (organization → project → subject). Nothing crosses it. |\n\n---\n\n## Positioning\n\n| | Common approaches | Haki |\n|---|---|---|\n| Change of mind | Old and new fact coexist, a source of contradictions | The old fact is superseded; only the current one is served |\n| Contradiction | Served to the model at random | Held back, flagged, explicitly resolvable |\n| Explainability | Black box | Trace and sources for every fact |\n| Forgetting | Deleting a row | Cascading propagation, with a receipt |\n| Retrieval latency | A network embedding call on every request | Local embeddings: no network call in the critical path |\n| Language coverage | Often optimized for English only | Multilingual natively (about 50 languages) |\n| Deployment | Several services to assemble (vector store, queue, etc.) | A single `docker compose up` |\n\n---\n\n## Measured performance\n\nReproducible benchmark: `uv run python scripts/benchmark_context.py` (100\nrequests per size, local embeddings, Windows development machine).\n\n| Facts in memory | p50 | p95 | PRD target |\n|---|---:|---:|---|\n| 100 | 60.5 ms | 80.6 ms | < 250 ms |\n| 1,000 | 63.7 ms | 68.0 ms | < 250 ms |\n| 10,000 | 27.8 ms | 42.5 ms | < 250 ms |\n\nEmbeddings are computed locally (ONNX on CPU, multilingual 384-dimension\nmodel) — no network call in the critical path. Retrieval combines a vector\nindex (hnsw) with a full-text index (GIN), then scores only the best\ncandidates. LLM cost (extraction) is fully asynchronous and never slows down\na response.\n\n---\n\n## Public benchmarks\n\nHaki publishes a reproducible benchmark harness, not a cherry-picked\nnumber: a frozen, versioned configuration (dataset and checksum, models,\nprompts, budgets, prices), a full-context baseline re-run under the exact\nsame protocol (same model, same prompt, same judge), and metrics the\nfield rarely publishes — contradiction leakage, abstention rate, tokens\nper packet, latency, cost.\n\n- Harness: [`eval/`](eval/) (LoCoMo and LongMemEval_S loaders, pipeline,\n  judge, reports).\n- Results are never committed to this repository on purpose — run the\n  harness yourself against the pinned dataset and frozen config, and the\n  numbers you get (written to `eval/results/`, gitignored) are yours to\n  trust or challenge, not a number we chose to show you.\n- Reproduction: exact commands in [`eval/README.md`](eval/README.md).\n\n---\n\n## API at a glance\n\n| Endpoint | Role |\n|---|---|\n| `POST /v1/capture` | Send events (idempotent, immediate acknowledgement) |\n| `POST /v1/context` | Get the ContextPacket (facts, warnings, `trace_id`) |\n| `GET /v1/inspect/{trace_id}` | The full trace of a memory decision |\n| `GET /v1/timeline` | A subject's events (raw evidence) |\n| `GET /v1/facts` | A subject's facts, every status (sources, dates, versions) |\n| `GET /v1/traces` | A project's recent traces (last 50) |\n| `GET /v1/conflicts` | Contradictions awaiting resolution |\n| `POST /v1/conflicts/{id}/resolve` | Resolve a conflict |\n| `POST /v1/feedback` | Rate a memory (`useful`/`irrelevant`/`incorrect`) |\n| `POST /v1/keys` · `GET` · `DELETE` | Manage API keys |\n| `POST /v1/consolidate` | Trigger consolidation (dev/ops) |\n| `POST /v1/forget` | Forget a fact or a subject, with a receipt |\n| `POST /gateway/v1/chat/completions` | OpenAI-compatible proxy: automatic memory injection and capture |\n| `GET /v1/stats/health` | Memory health metrics (freshness, open conflicts, coverage) |\n| `GET /health` | API health |\n| `/mcp` | MCP server (Cursor and other MCP clients) |\n\n> The curl examples below assume an existing key: create one with\n> `curl -X POST http://localhost:8100/v1/keys -d '{\"org_id\":\"org_acme\",\"project_id\":\"prj_support\",\"label\":\"dev\"}'`\n> (the first key is free, after that every key manages its own project), then\n> add `-H \"Authorization: Bearer hk_...\"` to every call.\n\nErrors are typed and actionable:\n`{\"error\": {\"type\": \"missing_scope\", \"message\": \"...\", \"field\": \"...\"}}`\n— never a generic message.\n\n<details>\n<summary><b>Full example: capture then context</b></summary>\n\n```bash\n# Capture a preference\ncurl -X POST http://localhost:8100/v1/capture \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"idempotency_key\": \"demo-1\",\n    \"events\": [{\n      \"org_id\": \"org_acme\", \"project_id\": \"prj_support\",\n      \"subject_type\": \"user\", \"subject_id\": \"usr_42\",\n      \"kind\": \"conversation.message\",\n      \"occurred_at\": \"2026-07-15T10:00:00Z\",\n      \"payload\": {\"role\": \"user\", \"content\": \"I prefer my invoices in French.\"},\n      \"classification\": [\"customer-data\"]\n    }]\n  }'\n\n# Consolidate (extracts the durable fact)\ncurl -X POST http://localhost:8100/v1/consolidate\n\n# Ask for memory before a response\ncurl -X POST http://localhost:8100/v1/context \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"project_id\": \"prj_support\", \"subject_id\": \"usr_42\",\n    \"query\": \"what language should the invoice be in?\",\n    \"budget_tokens\": 2000\n  }'\n```\n\nResponse: the fact `invoice_language: {\"language\": \"fr\"}`, its validity\ndate, the source event id, and a `trace_id`.\n</details>\n\n---\n\n## Security, scopes, and forgetting\n\n- **Per-project API keys**: every `/v1/*` call requires\n  `Authorization: Bearer hk_...` by default. A key is bound to a single\n  project: asking for another one returns `403 forbidden_scope`, without ever\n  revealing that other projects exist. Managed via\n  `POST/GET/DELETE /v1/keys` (details in\n  [`docs/SECURITY.md`](docs/SECURITY.md)).\n- **PostgreSQL Row-Level Security**: isolation is guaranteed by the database\n  itself (RLS on events, facts, traces, conflicts) — even if an application\n  filter is forgotten, a query can't cross projects (proven by a\n  non-disclosure test).\n- **Deterministic policy engine**: every read and write goes through explicit\n  rules (scope present, key/project match, audit) — never through the\n  language model.\n- **The model never chooses scopes**: `project_id` and `subject_id` come from\n  the calling backend or its configuration, never from the LLM.\n- **Feedback and correction**: `POST /v1/feedback`\n  (`useful`/`irrelevant`/`incorrect` — a fact flagged incorrect becomes\n  `disputed` and is never served again); `POST /v1/conflicts/{id}/resolve`\n  settles a contradiction with full history.\n- **Secrets**: the LLM key lives in `.env` (git-ignored, template provided in\n  [`.env.example`](.env.example)), never in code, the terminal, or the\n  frontend.\n- **Real forgetting**: `POST /v1/forget` propagates erasure to facts,\n  embeddings, events, and traces, with a timestamped receipt in\n  `forget_receipts`.\n- An open dev mode exists (`HAKI_AUTH_REQUIRED=false`) for local use only,\n  with an explicit warning at startup.\n\n---\n\n## Architecture\n\n<details>\n<summary><b>Stack and modules (for the technically curious)</b></summary>\n\n**Stack**: FastAPI · SQLAlchemy 2.0 async · PostgreSQL 16 + pgvector (hnsw) ·\nAlembic · Redis 7 · fastembed (ONNX CPU) · official MCP SDK.\n\n**Modules**:\n\n- **Memory Ledger** (`app/ledger/`) — bitemporal, append-only events\n  (`occurred_at` = business time, `recorded_at` = system time), versioned\n  facts, explicit status transitions:\n  `candidate → active → superseded/disputed/disabled → deleted` (terminal).\n- **Memory Consolidator** (`app/consolidator/`) — LLM extraction validated\n  by Pydantic (no batch can ever crash), content-based deduplication\n  (idempotent replay), supersession, conflict sets. A provider failure marks\n  the job `failed` without touching events, which stay replayable.\n- **Context Assembler** (`app/context/`) — strict filters (active, scope,\n  validity) then a hybrid score:\n  `0.6 × cosine similarity + 0.25 × full-text + 0.15 × recency`, plus\n  cross-encoder reranking, multi-hop entity expansion, and a temporal\n  grounding pass (see `research/Haki_Livre_Construction_2026-08-15.md` in the\n  private repo for how these interact). Two-phase retrieval (index selection,\n  then scoring) for a cost that stays stable regardless of memory size.\n- **Interchangeable providers** (`app/providers/`) — extractor\n  (`HAKI_LLM_PROVIDER=fake|openai`) and embedder\n  (`HAKI_EMBED_PROVIDER=local|fake`, local by default) configured\n  independently. No vendor SDK hardcoded in.\n- **MCP server** (`app/mcp_server/`) — mounted inside the API, Streamable\n  HTTP transport.\n- **Gateway** (`app/gateway/`) — OpenAI-compatible proxy: injects the\n  `<haki_memory>` block (rendered by the SDK's `build_prompt_context`, a\n  single shared implementation), forwards upstream via `HAKI_LLM_*` (never\n  the Haki key itself), captures idempotently after the response, documented\n  pass-through for streaming.\n\n**Database** (Alembic migrations): `events`, `facts` (`vector(384)`\nembedding, `search_vector` tsvector + GIN), `jobs`, `conflict_sets`,\n`context_traces`, `forget_receipts`, `organizations`, `subject_aliases`,\n`predicate_aliases`.\n</details>\n\n---\n\n## Quality and tests\n\n376 Python tests and 14 Node tests against a real PostgreSQL database (no\ndatabase mocking): `uv run pytest` and `cd sdk/typescript && npm test`.\n\nTests verify behavioral guarantees, not implementation details:\n\n- a superseded fact is never returned as active;\n- one subject never sees another subject's memory;\n- a network retry never creates a duplicate;\n- an open conflict holds back both facts involved;\n- after forgetting, nothing is ever served again;\n- illegal status transitions are rejected;\n- the gateway injects memory, degrades without ever blocking, forwards\n  upstream errors, and captures exactly once per idempotency key.\n\nEnd-to-end checks already run against real conditions: LLM extraction\n(OpenRouter), MCP server (official client), n8n workflow (Docker), latency\nbenchmark.\n\nThe current guarantees, each one citing the mechanism and the test that\nproves it, are documented in\n[`docs-site/en/production-guarantees.mdx`](docs-site/en/production-guarantees.mdx).\n\n---\n\n## Roadmap\n\n| Milestone | Status |\n|---|---|\n| Memory Ledger and idempotent capture | done |\n| Consolidator (supersession, conflicts) and ContextPacket | done |\n| Local embeddings and p95 benchmark under 250 ms | done |\n| Python SDK and `haki` CLI | done |\n| MCP server and Cursor integration | done |\n| n8n integration (template and nodes) | done |\n| Security: API keys, RLS, policy engine, feedback | done |\n| OpenAI-compatible gateway (automatic memory via `base_url`) | done |\n| TypeScript SDK | done |\n| Public LoCoMo and LongMemEval benchmark harness (reproducible, run it yourself) | done |\n| Haki's own accuracy numbers on that harness (calibrated, reproducible) | done |\n| Public Reliability Report page (trajectory, methodology, what's still broken) | planned |\n| CLI device-code authentication (`haki login`) | done |\n| Multi-channel identity resolution (`/v1/subjects/resolve`, `/merge`) | done |\n| Cross-encoder reranking, temporal grounding, entity/PRF expansion | done |\n| Memory health metrics (`/v1/stats/health`) | done |\n| Self-hosted memory-health dashboard (standalone from Cloud console) | planned |\n\n---\n\n## Repository structure\n\n```\nhaki/\n├── app/                 # FastAPI API (ledger, consolidator, context, gateway, MCP)\n├── sdk/python/          # SDK + haki CLI\n├── sdk/typescript/      # TypeScript SDK + haki-ts CLI (Python parity)\n├── integrations/n8n/    # Native template + e2e workflow (node package: github.com/GetHaki/n8n-nodes-haki)\n├── alembic/             # PostgreSQL migrations\n├── tests/               # Behavioral tests (incl. eval harness tests)\n├── eval/                # Public benchmark harness (LoCoMo + LongMemEval)\n├── scripts/             # Benchmarks and diagnostics\n├── docs-site/           # Product documentation (Mintlify)\n└── docker-compose.yml   # Postgres + pgvector + Redis\n```\n\n> This is the self-hosted OSS core: the API, both SDKs, the MCP server, the\n> n8n integration, and the public eval harness. The hosted web console\n> (browsing memory, resolving conflicts by click, billing) is part of the\n> [Cloud offering](https://gethaki.space) and lives in a separate, private\n> repository — self-hosting Haki never requires it.\n\n## Documentation\n\nProduct documentation (guides, full API reference, the production-guarantees\ncontract) lives in [`docs-site/en/`](docs-site/en/) — a Mintlify site, run\nlocally with `mint dev` from that folder (Node 18/20/22 LTS required). A\nFrench translation is maintained in parallel under\n[`docs-site/fr/`](docs-site/fr/).\n\n---\n\n<div align=\"center\">\n\n**Haki — your agent remembers what matters, and can prove it.**\n\n</div>\n",
  "bytes": 28776,
  "sha": "28dac6fc3d6309cf931534a573c4842fb6a56e78c3514a7bd8e09b44d231d752",
  "repo_slug": "gethaki/haki",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_gethaki_haki_23144369/readme"
}