{
  "markdown": "<p align=\"center\">\n  <img src=\"docs/assets/akb-hero.png\" alt=\"AKB — agents reading and writing into a permissioned knowledge vault of docs, tables, and files, linked by a URI graph\" width=\"100%\">\n</p>\n\n# AKB — Agent Knowledge Base\n\n> **Organizational memory for AI agents.** Git-backed knowledge base served\n> over the **Model Context Protocol (MCP)** — agents read and write directly\n> with hybrid semantic + keyword search, structured tables, files, and a URI\n> graph. Drop-in alternative to Confluence / Notion for Claude Code, Cursor,\n> Windsurf, and any MCP-aware agent.\n\n[![License: BSL 1.1](https://img.shields.io/badge/license-BUSL--1.1-blue.svg)](./LICENSE)\n[![npm: akb-mcp](https://img.shields.io/npm/v/akb-mcp.svg?label=npm%3A%20akb-mcp)](https://www.npmjs.com/package/akb-mcp)\n[![MCP](https://img.shields.io/badge/MCP-2026--07--28%20%2B%20legacy-orange.svg)](https://modelcontextprotocol.io)\n\n## Works with\n\nAny agent client that speaks **MCP (Streamable HTTP or stdio)**:\n\n- **Claude Code** — CLI / VS Code / JetBrains\n- **Claude Desktop** — macOS / Windows\n- **Cursor**, **Windsurf**, **Cline**, **Continue** — via the\n  [`akb-mcp`](https://www.npmjs.com/package/akb-mcp) stdio proxy\n- Custom agents — direct HTTP `POST /mcp/` with a Bearer token\n\nThe default flow uses a Personal Access Token. Deployments with the\noptional **MCP OAuth Resource Server** path turned on (via Keycloak as\nthe AS — see [`docs/mcp-clients/web-connectors.md`](./docs/mcp-clients/web-connectors.md))\nalso accept Claude Code's `mcp add --transport http` + `mcp login` flow\nend-to-end, without a PAT.\n\n## MCP protocol compatibility\n\nAKB keeps one tool and authorization core behind two protocol adapters:\n\n| Surface | Modern | Legacy |\n|---|---|---|\n| Direct HTTP `/mcp/` | `2026-07-28` stateless `server/discover` and per-request `_meta` with `Mcp-Protocol-Version` / `Mcp-Method` (and `Mcp-Name` for named calls) | `2024-11-05`, `2025-03-26`, `2025-06-18`, `2025-11-25` initialize + `Mcp-Session-Id` lifecycle |\n| `akb-mcp` stdio proxy | `2026-07-28` discovery and per-request metadata | `2025-06-18` initialize |\n\nThe proxy answers either handshake locally and normalizes backend calls to the\nmodern stateless contract when available. Legacy backend sessions are used\nonly during a rolling upgrade when modern discovery is unavailable. A process\ncannot mix generations, and unsupported revisions or conflicting protocol\nevidence fail closed before a tool or local-file operation runs.\n\n## Plugins\n\nBeyond raw MCP access, AKB ships ready-made **agent plugins** for **Claude Code**\nand **Codex** that wrap common vault workflows:\n\n- **akb-wiki** — ingest a source (local file, web URL, GitHub PR / release /\n  commit, Confluence page, or Jira issue) into the vault as a structured\n  document, and answer questions from the vault with grounded, cited synthesis\n  (read-only).\n- **akb-sessions** — capture a coding session as structured notes: a session\n  report plus follow-up tasks, learnings, ideas, and decisions.\n- **akb-claude-code** — a Claude Code lifecycle bridge: hooks anchor each\n  session to your AKB memory vault, injecting preferences and recent learnings\n  at the start and writing a recap at the end.\n\n```\n/plugin marketplace add dnotitia/akb        # Claude Code\ncodex plugin marketplace add dnotitia/akb   # Codex\n```\n\nInstall details and credentials: [`plugins/`](./plugins/skillpack-plugins.md).\n\n## Try it live\n\nA public demo runs at **[akb-demo.agent.seahorse.dnotitia.ai](https://akb-demo.agent.seahorse.dnotitia.ai)**.\nBrowse and search a small fictional-organization knowledge base — product docs,\na company handbook, agent session notes, and an engineering wiki, cross-linked\nby the URI graph — right in your browser, no signup. To wire it into your own\nagent, sign up with any email (a throwaway address is fine) and point the\n[`akb-mcp`](https://www.npmjs.com/package/akb-mcp) proxy at\n`https://akb-demo.agent.seahorse.dnotitia.ai/mcp/`.\n\n> ⚠️ **Throwaway demo.** It is public, wiped and re-seeded weekly, and runs on\n> minimal resources with **no uptime, privacy, or data guarantees**. Don't put\n> anything real or sensitive in it — treat every write as public and ephemeral.\n> For real use, [self-host](#quick-start) with Docker Compose or Kubernetes.\n\n## Why AKB\n\nMost knowledge tools are built for humans clicking through a UI. Agents need a\ndifferent shape: structured documents, semantic + keyword search in one call,\nexplicit relations, and full version history. AKB gives agents a single set of\ntools (`akb_put`, `akb_search`, `akb_browse`, `akb_relations`, …) over a\nbacking store of Git bare repos and a PostgreSQL hybrid index.\n\n## Retrieval quality\n\nMemory is only useful if the right note comes back. AKB's hybrid retrieval\n(dense + BM25, source-level dedup) was benchmarked on\n[LongMemEval](https://github.com/xiaowu0162/LongMemEval)-S — 500 long-context\nquestions, ~50 chat sessions per question. **Recall@5 = 98.4%**, with no\nreranker in the loop.\n\n| System | R@5 | n | Reranker | Source |\n|---|---:|:---:|:---:|---|\n| **AKB hybrid** | **98.4%** | 500 | no | this repo |\n| MemPalace hybrid + rerank | 98.4% | 450 | yes | [MemPalace](https://github.com/mempalace/mempalace) |\n| gbrain hybrid | 97.6% | 500 | no | [gbrain-evals](https://github.com/garrytan/gbrain-evals) |\n| gbrain vector | 97.4% | 500 | no | gbrain-evals |\n\nMethodology, per-category breakdown, and a one-command reproducible harness\nlive in [`eval/longmemeval/`](eval/longmemeval/). The embedding model differs\nacross systems (AKB: `bge-m3@1024`), so read this as a stack-level comparison.\n\n## Design philosophy\n\n**Core stays small; flexibility comes from extension, not built-in\nautomation.** AKB does not ship its own consolidator, summariser, or\n\"knowledge gardener\" — instead every write records a structured event in the\nPostgreSQL outbox. When `redis_url` is configured, the publisher fans those\nevents out to a Redis Stream (`akb:events`). Operators wire any external\nconsumer (periodic synthesis bot, doc-rot reaper, weekly-digest agent, audit\ntrail, …) on top, with no patches to the core. The base contract is a\nread/write store; opinions about *what to do with* the knowledge live outside.\n\n## Architecture\n\n```\n┌──────────────────────────────────────────────────────────┐\n│                  Access Layer                            │\n│   MCP Server  │  REST API  │  Web UI                     │\n├──────────────────────────────────────────────────────────┤\n│                  Core Services                           │\n│   Document (Put/Get)  │  Search (Hybrid: dense+BM25)     │\n│   Relations (graph)   │  Session  │  Publications        │\n├──────────────────────────────────────────────────────────┤\n│                  Storage Layer                           │\n│   Git bare repos       │  PostgreSQL 16 (text + meta SoT)│\n│                        │  Vector store (driver):         │\n│                        │    pgvector        (default, PG)│\n│                        │    qdrant          (optional)   │\n│                        │    seahorse-cloud  (managed)    │\n│                        │    seahorse-db     (self-hosted)│\n│                        │    seahorse-db-grpc(experimental)│\n└──────────────────────────────────────────────────────────┘\n```\n\nPostgreSQL is the source of truth — chunk text + metadata + BM25 vocab.\nThe vector store is a driver-pluggable derived index holding dense\nembeddings and corpus-side sparse vectors. Full vector-store loss is\nrecoverable from PG by setting `chunks.vector_indexed_at = NULL` and\nletting the indexing worker re-populate.\n\n## Key Concepts\n\n- **Vault** — A Git bare repo. The unit of access control and physical isolation.\n- **Collection** — A directory inside a vault. Topical grouping of documents.\n- **Document** — Markdown + YAML frontmatter, optimised for agent read/write.\n- **Hybrid Search** — Dense (semantic) + BM25 (lexical) fused via RRF in one call.\n- **Relations** — `depends_on`, `related_to`, `implements` in frontmatter form an explicit knowledge graph.\n- **Vault isolation in `akb_sql`** — Enforced by PostgreSQL ACL. Each\n  AKB user has a corresponding PG role (`akb_user_<uid>`) and each\n  vault has three group roles (`akb_vault_<vid>_{reader,writer,admin}`).\n  `akb_sql` runs the user's SQL inside a transaction with\n  `SET LOCAL ROLE`; cross-vault references return PG `42501`\n  directly. No application-side regex inspects user SQL for forbidden\n  identifiers. See `docs/designs/pg-native-rbac/`.\n\n## MCP Tools (selection)\n\n| Tool | Description |\n|------|-------------|\n| `akb_list_vaults` / `akb_create_vault` | Vault management |\n| `akb_put` / `akb_get` / `akb_update` / `akb_delete` | Document CRUD (Git commit + indexing) |\n| `akb_put_file` / `akb_get_file` / `akb_update_file` / `akb_delete_file` | File attachments — proxy-side (requires local filesystem) |\n| `akb_put_image` / `akb_discard_image` | Validated inline Markdown images — proxy-side in `akb-mcp` 2.2+ |\n| `akb_create_table` / `akb_alter_table` / `akb_drop_table` / `akb_sql` | Tabular content — per-doc tables + SQL |\n| `akb_browse` | Tree traversal (collection → docs) |\n| `akb_search` / `akb_grep` | Hybrid search (dense + BM25) / literal grep |\n| `akb_drill_down` | Section-level retrieval |\n| `akb_relations` / `akb_link` / `akb_unlink` / `akb_graph` | Knowledge graph |\n| `akb_edit` / `akb_diff` / `akb_history` | In-place edit, diff, Git history |\n| `akb_grant` / `akb_revoke` / `akb_set_public` | Permission boundaries — per-user, per-org, public |\n| `akb_publish` / `akb_unpublish` | Public publication |\n\nAgent memory and session lifecycle are not MCP tools — they live on\nthe dedicated `/api/v1/agent-sessions` REST surface, driven by AKB\nlifecycle plugins (`akb-claude-code`, `akb-cursor`, …) that hook into\nthe agent's own SessionStart / PreCompact / SessionEnd events. As an\nagent, your own memory vault (`agent-memory-{username}`) is browsable\nthrough the standard `akb_search` / `akb_browse` / `akb_get` tools\nexactly like any other vault.\n\nThe full tool catalogue is exposed via `akb_help()` from any MCP client.\n\n### Inline document images from MCP\n\nInline images are hidden document attachments, not browsable Files. Upload a\nlocal PNG, JPEG, GIF, or WebP (maximum 10 MiB), then insert the returned\nMarkdown without reconstructing its asset URL:\n\n```text\nimage = akb_put_image(\n  parent=\"akb://eng/coll/specs\",\n  file_path=\"/workspace/architecture.png\",\n  alt_text=\"Request processing architecture\")\n\nakb_put(\n  parent=\"akb://eng/coll/specs\",\n  title=\"Request Processing\",\n  content=\"# Architecture\\n\\n\" + image.markdown)\n```\n\nFor an existing document, use `akb_get` followed by a targeted\n`akb_edit(base_commit=...)`. Do not pass only the image fragment to\n`akb_update(content=...)`, which replaces the complete body. Image bytes are\nimmutable: replacing an image means uploading a new one and editing the\nMarkdown reference. Remove an image by deleting its Markdown expression; use\n`akb_discard_image` only for an upload that never reached a successful document\ncommit. Run `akb_help(topic=\"images\")` for retention and publication behavior.\n\nThe image tools require both the matching backend release and `akb-mcp` 2.2 or\nnewer. For upgrades, deploy the backend first, then publish/install the proxy\nand restart existing MCP processes so they load the updated tool list.\n\n## Document Format\n\nEvery vault resource has a location-aware AKB URI — the canonical handle\nused by every tool and stored in relations. As of 0.3.0:\n\n```\nakb://{vault}                                          vault root (browse target)\nakb://{vault}/coll/{coll_path}                         collection (browse target)\nakb://{vault}[/coll/{coll_path}]/doc/{filename}        document\nakb://{vault}[/coll/{coll_path}]/table/{name}          table\nakb://{vault}[/coll/{coll_path}]/file/{uuid}           file\n```\n\nThe `/coll/{coll_path}` segment is omitted for resources at the vault\nroot. Walking up a URI to its parent collection is a pure string\noperation — paste the parent into `akb_browse(uri=...)` to list\nsiblings without an extra lookup.\n\n```yaml\n---\ntitle: \"Payment API v2 migration plan\"\ntype: plan              # note | report | decision | spec | plan | session | task | reference\nstatus: active          # draft | active | archived | superseded\ntags: [payments, api]\ndomain: engineering\nsummary: \"REST → gRPC transition plan.\"\ndepends_on: [\"akb://eng/coll/specs/doc/payment-api-v2.md\"]\nrelated_to: [\"akb://eng/coll/meetings/doc/2026-05-01-payments.md\"]\n---\n\n# Payment API v2 migration plan\n...\n```\n\n### Open Knowledge Format (OKF) compatible\n\nA vault is stored as a git tree of `.md` + YAML-frontmatter files whose\nidentity is the path — the same model as Google Cloud's\n[Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog)\n(OKF v0.1), which AKB independently arrived at before the spec existed.\nAKB-authored bundles satisfy all three OKF MUST rules, and AKB can export any\nvault as a conformant OKF bundle (documents, plus tables/files as concept docs)\nand validate any bundle:\n\n```bash\npython -m app.cli okf-export --from-git /data/vaults/_worktrees/<vault> \\\n    --vault <vault> --out ./okf-out/\npython -m app.cli okf-validate ./okf-out/\n```\n\nOKF and AKB are complementary — OKF standardizes *how knowledge is written\ndown*; AKB stores, versions, searches, governs, and serves it to agents. See\n[`okf/`](okf/) for the mapping and a sample bundle.\n\n## Quick Start\n\nThe default Docker Compose stack runs four long-lived services: PostgreSQL\nwith pgvector, MinIO, the backend, and the frontend. A one-shot\n`minio-bootstrap` service creates the local file bucket before the backend\nstarts. For semantic (dense) search you bring an OpenAI-compatible\nembedding endpoint (OpenAI, OpenRouter, self-hosted vLLM/TEI, etc.). It is\nnot strictly required: with no embed endpoint (or during an outage) the\npgvector and Qdrant drivers **degrade to BM25-only** lexical search rather\nthan returning nothing — dense is genuinely optional end-to-end (the\n`seahorse-db` driver is the exception; see *Vector store* below). Prefer\nrunning a separate Qdrant cluster, or pointing at Seahorse? See *Vector\nstore* below.\n\n```bash\n# 1. Configure\ncp config/app.yaml.example   config/app.yaml\ncp config/secret.yaml.example config/secret.yaml\n$EDITOR config/secret.yaml   # set embed_api_key and replace system_hmac_secret\n\n# Generate the installation's persistent RSA-3072 local-session keyset.\n# This directory is gitignored; back it up with the other installation secrets.\ncd backend\nuv run python -m app.cli generate-local-session-keyset \\\n  --output-dir ../config/local-session\ncd ..\n\n# 2. Run\ndocker compose up -d\n\n# 3. Provision the designated recovery administrator (local mode)\n#    The password is read from stdin and is never printed by AKB.\ndocker compose exec -T backend python -m app.cli provision-recovery-admin local \\\n  --username recovery-admin --email recovery-admin@example.com \\\n  --password-file - < /secure/operator/recovery-admin.password\n\n# 4. Open\nopen http://localhost:3000\n```\n\n`config/app.yaml` and `config/secret.yaml` are the **single source of\napplication configuration**. Mount the `config/` directory at `/etc/akb/` in\nany deployment. Process composition is the narrow exception:\n`AKB_PROCESS_ROLE=all|api|worker` selects the entrypoint role and\n`AKB_TOKENIZER_PROCESSES=1..4` can lower the per-process tokenizer pool for a\ndeployment container. The Kubernetes base owns those two operational values;\nbusiness, auth, storage, and provider settings remain in the YAML files.\n\nCompose also runs the API and worker separately. See the\n[local deployment guide](deploy/compose/README.md) for existing-volume upgrades,\nconfiguration changes, and remote-access URLs.\n\nOrdinary registration always creates a non-admin account, including on an\nempty database. Administrator bootstrap is available only through the\noperator CLI; there is no unauthenticated HTTP bootstrap endpoint. The CLI\nprofile must match `auth_mode`:\n\n```bash\n# Local: have AKB generate the password only when an operator-owned output\n# file is explicitly requested. A new file is created with mode 0600 and the\n# password is not written to stdout, stderr, logs, or application config.\npython -m app.cli provision-recovery-admin local \\\n  --username recovery-admin --email recovery-admin@example.com \\\n  --generate-password-file /secure/operator/recovery-admin.password\n\n# SSO: pre-bind the product administrator to the exact external identity.\n# Username and email are snapshots; issuer + subject are the identity key.\npython -m app.cli provision-recovery-admin sso \\\n  --username recovery-admin --email recovery-admin@example.com \\\n  --issuer https://issuer.example.com/realms/akb \\\n  --subject exact-provider-subject\n```\n\nThe same exact identity is idempotent. A different designation, an existing\nusername/email, or an already-bound external identity fails closed. The SSO\ncommand stores no usable local password and does not contact the identity\nprovider. Generated output files are create-only and never overwritten; for a\nretry after the file exists, pass that file back with `--password-file`.\n\nThe two local forms differ in one further way. `--generate-password-file` is\nAKB producing a credential and handing it over, so the account it creates owes\na replacement for it: the first session that credential opens can reach the\npassword change and nothing else, exactly as a password reset behaves.\n`--password-file` installs a value the caller already holds — AKB delivers it\nto nobody — so it arms nothing, and an installation that signs in as this\naccount to bootstrap its own service identity keeps working. To force a\nreplacement for a credential supplied that way, rotate it afterwards with the\ncommand below; rotation always leaves the account owing a change.\n\nIf that credential later leaks, is lost, or has to be taken back, rotate it\nrather than reprovisioning the account:\n\n```bash\n# Break-glass: replace the credential and print the new one once. Nothing\n# stores or logs the value, and the machine-readable report omits it.\npython -m app.cli issue-recovery-admin-credential \\\n  --expected-username recovery-admin \\\n  --expected-email recovery-admin@example.com\n```\n\nRotation names the account it expects and refuses any mismatch, so it cannot\nact on the wrong one. The credential it replaces stops working immediately,\nincluding one currently in use, and sessions held before the rotation are\nrevoked — both are what a compromise response requires. The same operation is\navailable at `POST /admin/recovery-admin/issue-credential`, which requires an\nindependent service-administrator token rather than a human session. Rotation\nis not available in `sso` mode: the identity provider holds the credential,\nand nothing in a running AKB can replace it.\n\nOpen `/admin` for the separate product-administration surface. In `local`\nmode it accepts the provisioned local administrator and returns the same\n`local-session-rs256-v2` profile used by local human authentication, but it\nrefuses non-admin accounts. In `sso` mode local credentials are absent:\n`/admin` uses a dedicated confidential `akb-admin` Keycloak client with PKCE\nand nonce, then accepts only the exact pre-bound `(issuer, subject)` whose AKB\naccount is still active and `is_admin=true`.\n\nIn SSO mode the same `/admin` surface can configure a built-in upstream IdP,\nsave it disabled, inspect its exact broker redirect URI, and enable or disable\nits ordinary-login option without redeploying AKB. The option becomes a usable\nbutton only when the server-side browser-session capability is ready. Client\nsecrets are write-only, and an enabled provider must be disabled before\nreconfiguration.\nSee the [SSO provider guide](./docs/sso/README.md), the standards-based\n[generic OIDC integration](./docs/sso/providers/oidc.md), and the stricter\n[Keycloak OIDC reference](./docs/sso/providers/keycloak-oidc.md). Existing\nKubernetes installations should also follow the\n[local-to-SSO cutover runbook](./docs/sso/kubernetes-cutover.md) instead of\ntreating `auth_mode` as a rolling one-line configuration change.\n\nThe dedicated admin callback stores no Keycloak access, refresh, or ID token.\nIt creates a short-lived opaque HttpOnly admin cookie plus a CSRF token;\nPostgreSQL stores only their hashes plus the exact identity snapshot, and\nrechecks the account, unchanged external binding, and admin flag on every\nrequest. Its one-time OIDC state is also bound to a short-lived HttpOnly cookie\nso a callback copied into another browser fails before token exchange.\nConfigure `keycloak_admin_client_secret`, register\n`<public_base_url>/api/v1/admin/auth/keycloak/callback` and\n`<public_base_url>/admin` in the dedicated client, and keep the admin client ID\nout of every API/MCP resource-client path. Browser-facing AKB and Keycloak URLs\nmust use HTTPS outside the explicit loopback development exception.\n\nOrdinary SSO login uses the separate `akb-web` client. The browser receives\nonly an opaque HttpOnly AKB session plus a readable CSRF value; SSO does not\nmint an AKB user JWT. AKB encrypts the Keycloak refresh/ID token set with the\nindependent `sso_browser_session_encryption_key` and never persists an access\ntoken. The client must map Keycloak's `identity_provider` user-session note\ninto both ID and access tokens with `oidc-usersessionmodel-note-mapper`; AKB\nbinds that signed broker alias to the selected enabled provider on callback\nand every refresh. Production HTTPS cookies use the browser-enforced `__Host-` prefix,\n`Secure`, no `Domain`, and `Path=/`; loopback HTTP uses isolated development\nnames. Generate the key as 32 random bytes encoded with unpadded base64url and\nkeep it stable across restarts. See the [Keycloak boundary](./docs/designs/keycloak-oidc/00-overview.md)\nfor refresh, logout, and back-channel revocation details.\n\nLocal login issues only the versioned `local-session-rs256-v2` profile:\nRS256 with an installation-owned RSA-3072 key, an RFC 7638 `kid`, exact\ndeployment issuer/audience, `jti`, and a public-only JWKS at\n`GET /api/v1/auth/jwks`. AKB never chooses a verifier from an untrusted token\n`alg` header. Upgrading from an HS256 release is an intentional forced-login\nboundary: generate and persist the v2 keyset before rollout, set\n`jwt_algorithm: RS256`, and restart all backends together. Existing HS256 user\nsessions then receive 401 and must sign in again; PATs and service keys are not\nrevoked. The old `jwt_secret` may be retained for one release only as migration\ninput for short-lived internal HMAC capabilities, or renamed unchanged to\n`system_hmac_secret`; it is never accepted as human-session signing material.\n\nFor routine v2 key rotation, generate a new directory while retaining the\ncurrent public JWKS, publish the new immutable Secret/config revision, and\nroll every backend to that exact pair:\n\n```bash\ncd backend\nuv run python -m app.cli generate-local-session-keyset \\\n  --output-dir /secure/akb/local-session-next \\\n  --retain-jwks /secure/akb/local-session-current/jwks.json\n```\n\nKeep a retained public key for at least `jwt_expire_hours` plus rollout skew,\nthen remove it in a later coordinated keyset revision. Restoring the previous\nprivate/JWKS pair is the rollback; never overwrite key files in place.\n\n### Vector store (driver-pluggable)\n\nHybrid search (dense + BM25 sparse, RRF-fused) runs through a driver\ninterface. Five drivers ship; pick at config time:\n\n- **`pgvector`** (default) — uses the same Postgres container that holds\n  application data. The pgvector/pgvector image pre-installs the\n  extension; the driver creates a separate `vector_index` schema, so the\n  main `chunks` table stays plain PostgreSQL. RRF fusion runs\n  application-side. No external service to operate.\n- **`qdrant`** — runs a separate Qdrant container; native RRF via the\n  Query API. Useful when you already operate Qdrant or want to scale\n  the vector store independently of Postgres.\n- **`seahorse-cloud`** — points at a managed [Seahorse Cloud][shc] table\n  over its BFF management API + per-table data-plane host (Bearer auth).\n  No infrastructure to run on your side; you provision a table in the\n  Seahorse console (or let the driver auto-create one) and AKB stores\n  its chunks there. Native RRF, server-side BM25. See\n  [`docs/vector-store-seahorse.md`](./docs/vector-store-seahorse.md)\n  for the end-to-end setup walkthrough (sign-up → token → schema →\n  config).\n- **`seahorse-db`** — points at a **self-hosted SeahorseDB** cluster via\n  its Coral coordinator HTTP API. You run Coral + Writer + Reader(s) +\n  Redis + Kafka + a sparse-embedding server yourself (the SeahorseDB\n  monorepo's `deploy/docker-compose.yml` brings up a minimal single-box\n  stack). Native dense+sparse hybrid. Unlike the other drivers it does\n  **not** support BM25-only fallback when the embed API is down (its\n  sparse path is server-side and structurally coupled to a live embed\n  step) — keep an embedding endpoint reachable for this driver.\n- **`seahorse-db-grpc`** *(experimental)* — same Coral coordinator as\n  `seahorse-db`, same `seahorsedb_*` settings, but talks gRPC instead\n  of REST/JSONL. Coral merges axum + tonic onto a single listener so\n  the port doesn't change; only the wire format does. Trades the JSON\n  parsing path (and a class of foot-guns like INT64 sign mismatch and\n  Arrow JSON decoder edge cases) for typed protobuf messages and an\n  Arrow IPC streaming result. Prefer the REST driver for production\n  until the gRPC variant clears its own QPS / recall benchmark. Same\n  CRUD parity with REST (passes the same 25-scenario hybrid e2e), but\n  it has not yet had the production-scale exposure the REST driver\n  has.\n\n[shc]: https://console.seahorse.dnotitia.ai\n\nSwitching drivers is a config edit (no schema migration on the main DB):\n\n```bash\n# Default flow targets pgvector.\ndocker compose up\n\n# Qdrant:\ndocker compose -f docker-compose.yaml -f docker-compose.qdrant.yaml up\n$EDITOR config/app.yaml     # vector_store_driver: qdrant\n                            # vector_url: http://qdrant:6333\n\n# Seahorse Cloud (managed; full guide in docs/vector-store-seahorse.md):\ndocker compose up           # no extra container needed\n$EDITOR config/app.yaml     # vector_store_driver: seahorse-cloud\n                            # seahorse_cloud_tenant_uuid: <your tenant>\n                            # seahorse_cloud_table_name: <your table>\n$EDITOR config/secret.yaml  # seahorse_cloud_token: shsk_<...>\n\n# SeahorseDB (self-hosted cluster reached via the Coral coordinator):\ndocker compose up           # run the SeahorseDB stack separately\n$EDITOR config/app.yaml     # vector_store_driver: seahorse-db\n                            # seahorsedb_coordinator_url: http://localhost:3003\n                            # seahorsedb_table_name: akb_chunks\n```\n\nEmbedding model + dimensions are also fully pluggable via\n`embed_base_url` / `embed_model` / `embed_dimensions` — the codebase has\nno hard-coded model. For pgvector with HNSW, keep `embed_dimensions ≤ 2000`\n(or 4000 with `halfvec`); larger models fall back to exact scan.\nQdrant / Seahorse (cloud or db) have no such limit (Qdrant up to 65536,\nSeahorse up to its table-defined dim).\n\n### LLM features (optional)\n\nLLM is only used by the `metadata_worker` to auto-tag documents imported via\nexternal git mirroring. Core CRUD/search works without it. To enable, set\n`llm_base_url` / `llm_model` in `app.yaml` and `llm_api_key` in `secret.yaml`.\n\nStandalone deployments default to `model_api_governance_mode: external_metering`\nand may point embedding, chat, and rerank at any compatible provider. A managed\ncontrol plane can instead set `platform_hard` plus an exact\n`platform_gateway_base_url`. In that mode AKB fails startup if an active model\nroute points anywhere else or lacks a credential, and every model call carries\na caller-generated `Idempotency-Key` for durable gateway reservation/settlement.\nGateway policy or budget denials are never fanned out into per-item retries.\n\n### Event fanout (optional)\n\nThe PG `events` outbox is always written. Set `redis_url` in `app.yaml` to\nhave the `events_publisher` worker drain the outbox to a Redis Stream\n(`akb:events`) so external services can subscribe via `XREAD` / consumer\ngroups. Leave blank to disable; events still accumulate in PG and you can\nbuild an SSE endpoint on top of the LISTEN/NOTIFY trigger without Redis.\n\n### Audit log (optional)\n\nOff by default. Set `audit.enabled: true` in `app.yaml` to emit a structured,\nappend-only, **hash-chained** JSON-lines audit log at the MCP dispatch\nchokepoint — every read, write, and auth denial, uniformly. AKB is a\n**producer only**: it does not store, query, or retain audit data; your SIEM\n(Splunk/QRadar/Elastic) scrapes the stream and owns retention under its own\ncompliance regime. Each line carries a monotonic `seq` plus\n`sha256(prev ‖ line)`, so the chain can be verified for dropped or altered\nlines and re-seeds from disk across restarts. Optionally hand the daily\nrolled file off to a **WORM** object-storage bucket (`audit.bucket` —\nprovision with Object Lock and a write-only key for a true immutable trail);\nthe local buffer is pruned only after a confirmed upload. Capture is\nbest-effort and never raises into the serving path. See\n`config/app.yaml.example` for the full `audit:` block.\n\n### Production deployment\n\nFor Kubernetes, start with the\n[`deployment guide`](./deploy/k8s/README.md). AKB provides standalone local and\nstandalone-SSO resource sets through both Helm and Kustomize.\n\n- Use the dependency-free [`AKB Helm chart`](./deploy/helm/akb/README.md) for a\n  standard `helm upgrade --install` workflow.\n- Use [`deploy/k8s`](./deploy/k8s/README.md) for the standalone Kustomization or\n  `deploy/k8s/standalone-sso` for an installation-owned Keycloak stack.\n- Both paths consume pre-existing, operator-owned Kubernetes Secrets. AKB does\n  not install or operate a credential service or synchronization controller.\n\nReal hostnames, registries, storage classes, TLS issuers, and provider settings\nbelong in Helm values or an operator-owned Kustomize overlay; do not commit\nproduction credentials to this repository.\n\n## Project Structure\n\n```\nakb/\n├── backend/                  # Python 3.14 / FastAPI / asyncpg / GitPython\n│   ├── app/\n│   │   ├── api/routes/       # REST endpoints\n│   │   ├── services/         # Business logic + workers\n│   │   └── db/               # PostgreSQL schema + migrations\n│   ├── mcp_server/           # Streamable HTTP MCP server\n│   └── tests/                # E2E shell tests\n├── frontend/                 # React 19 + TypeScript + Vite + Tailwind\n├── packages/\n│   ├── akb-client/           # REST SDK boundary (npm: @akb/client)\n│   └── akb-mcp-client/       # stdio ↔ HTTP MCP proxy (npm: akb-mcp)\n├── agents/                   # Reference Python agent runtime (think/act loop over MCP)\n├── plugins/                  # Claude Code / Codex agent plugins (ingest, query, session capture, lifecycle)\n├── templates/                # Doc templates (ADR, PRD, runbook, …) and vault profiles\n├── okf/                      # Open Knowledge Format interop: positioning + sample bundle\n├── design-system/            # Frontend design system docs\n├── config/\n│   ├── app.yaml.example      # Non-secret runtime settings\n│   └── secret.yaml.example   # API keys, passwords (gitignored when not .example)\n├── deploy/\n│   ├── all-in-one/           # Single-container demo image\n│   ├── helm/\n│   │   └── akb/              # AKB chart with local and standalone-SSO profiles\n│   └── k8s/\n│       ├── *.yaml            # Standalone AKB + PostgreSQL resources\n│       └── standalone-sso/   # Standalone plus owned Keycloak and its database\n└── docker-compose.yaml       # Local stack (PG + MinIO + API + worker + frontend)\n```\n\n## Tech Stack\n\n- **Backend**: Python 3.14, FastAPI, Uvicorn, asyncpg, GitPython, MCP SDK\n- **Database**: PostgreSQL 16 (main DB needs no extension; the same\n  pgvector/pgvector image hosts the optional vector_index schema)\n- **Vector store**: driver-pluggable (pgvector default; Qdrant,\n  Seahorse Cloud, or self-hosted SeahorseDB optional — hybrid dense +\n  BM25 sparse, RRF fusion; BM25-only fallback when embed is down)\n- **Event stream** (optional): PG `events` outbox + Redis Streams fanout\n- **Audit log** (optional): hash-chained append-only JSONL at the MCP\n  dispatch point + optional WORM S3 handoff; producer-only (SIEM owns retention)\n- **Frontend**: React 19, TypeScript, Vite, Tailwind CSS v4, Radix UI\n- **Auth**: local RS256 sessions or Keycloak SSO, plus Personal Access Tokens\n  (PATs) for API and MCP access\n- **MCP**: Streamable HTTP (backend) + stdio proxy (`akb-mcp` on npm)\n\n## Versioning\n\nAKB follows [SemVer](https://semver.org/). The backend product version lives in\n`backend/pyproject.toml` (`[project].version`). A coordinated release uses\n`scripts/bump-version.sh <x.y.z>` to update it together with\n`frontend/package.json`. With image building enabled, each\n`deploy/k8s/deploy.sh` run tags the Docker images with both the\nexplicit backend version (`:${VERSION}`) and `:latest`, so historical builds\nremain pullable for rollback.\n\n`packages/akb-mcp-client` (the `akb-mcp` npm proxy) follows its own npm\nsemver lifecycle and is **not** tied to the product version.\n\n## License\n\nThe AKB backend, frontend, and deployment manifests are licensed under\nthe [Business Source License 1.1](./LICENSE) — source-available, with\nan Additional Use Grant that permits production use (commercial or\nnon-commercial) up to a seat-count threshold, automatically converting\nto **Apache License 2.0** four years after each version's first public\nrelease.\n\nThe npm `akb-mcp` proxy (`packages/akb-mcp-client/`) is separately\nlicensed under the **MIT License** so it can be freely embedded in any\nagent client without restriction.\n\n**Free production use of the backend** — you may deploy AKB in\nproduction, commercial or not, provided your aggregate deployment\nserves **fewer than 100 Named Seats** (distinct human user accounts in\nthe `users` table, per deployment; service accounts and\n90-day-inactive accounts excluded — see [LICENSE](./LICENSE) for the\nprecise definition).\n\n**Commercial license required** for any of:\n\n- Production use of the backend at or above 100 Named Seats.\n- Offering AKB (modified or not) as a hosted service, on-premises\n  product, embedded component, or rebranded distribution to third\n  parties — regardless of seat count.\n\n**Trademarks** — \"AKB\", \"Dnotitia\", and \"Seahorse\" are trademarks of\nDnotitia, Inc. The software license does not grant trademark rights.\nForks and derivative works must be distributed under a different name.\nSee [TRADEMARKS.md](./TRADEMARKS.md).\n\nFor commercial licensing, the rationale behind the BSL transition, or\ntrademark permission requests, see\n[LICENSE-CHANGE.md](./LICENSE-CHANGE.md) or contact\n**support@dnotitia.com**.\n\n## Security\n\nFound a vulnerability? See [SECURITY.md](./SECURITY.md) — please report\nprivately, not via public issues.\n\n## Contributing\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md).\n",
  "bytes": 35079,
  "sha": "7061ac0b601aeac0441366711e23fceb18c172e784b65ce33fab80fa804adb40",
  "repo_slug": "dnotitia/akb",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_dnotitia_akb_a1b38cf1/readme"
}