{"slug": "show-hn-anansi-open-source-memory-api-for-llm-apps", "title": "Show HN: Anansi – open-source memory API for LLM apps", "summary": "Anansi, an open-source, self-hostable memory API for LLM applications, was released on GitHub under the MIT license, with a commercial enterprise layer. It provides two API calls, /v1/ingest and /v1/context, to store and retrieve structured organizational memory with temporal reasoning, and runs on Postgres and Redis. The project aims to give AI agents a durable understanding of how an organization works and how that changes over time, with a quickstart that takes about five minutes.", "body_md": "**A self-hostable memory engine for AI agents that can distinguish what was true from what the agent knew at the time.**\n\nAnansi gives an AI system a durable understanding of how an organization actually works — and how that changed over time.\n\nYou feed it the exhaust your company already produces: conversations, docs, tickets,\nmeeting transcripts. Anansi turns that into structured memory your agent can read\nbefore it answers, and keeps every version of it. So you can ask not just *\"what is\nour escalation process?\"* but *\"what did we think it was in March, and when did it\nchange?\"* — and get an answer with a citation.\n\nTL;DR:MIT-licensed self-hostable core, with a commercial enterprise/hosted layer. See[License].\n\nTwo API calls do the work:\n\n```\nPOST /v1/ingest    # remember this\nGET  /v1/context   # what do you know that's relevant right now?\n```\n\n`ingest`\n\nreturns `202`\n\nimmediately and does the expensive work on a queue, so it never\nsits in your response path. `context`\n\nreturns a compact, already-synthesized profile you\ncan paste straight into a system prompt — not a pile of chunks to rank yourself.\n\nSelf-hosted, MIT licensed, runs on Postgres and Redis. About five minutes to a working instance, no signup.\n\n**What that citation actually buys you** — the entity graph carries two independent\ntime axes, so you can ask what was true *and* what the system knew, separately:\n\nThe entity graph and `temporal`\n\nquery results (the two-axis reasoning above) are a **Pro+** feature. Self-hosted installs default to the `enterprise`\n\nplan (see [Plan limits](#plan-limits-do-not-apply-to-self-hosted-installs)) and get it automatically; on the hosted service it requires a paid tier.\n\nNo account and no API key from us. Everything below runs on your machine.\n\nTimings are measured, not aspirational: the API image builds from source in about\n**90 seconds** on a warm Docker, and the embedding model is a **274 MB** download. The\nfive-minute figure assumes option **A** or **B** in step 2 — option **C** pulls a 7 GB\nimage and takes considerably longer.\n\n```\ngit clone https://github.com/g-33-L/anansi.git\ncd anansi\ndocker compose up -d\n```\n\nThat brings up PostgreSQL, Redis, and the API. Migrations run automatically on first\nboot. The API and docs serve at ** http://localhost:3000**.\n\n**Anansi needs an embedding model, and Compose does not start one for you.** Skip this\nstep and ingest will appear to succeed — it returns `202`\n\nbecause embedding is\nasynchronous — while retrieval fails with `503`\n\n. Open\n[ /status](http://localhost:3000/status); it reports the embedding backend explicitly,\nso you can see the problem rather than infer it.\n\nThree ways to satisfy it, ordered by how long they actually take. Only the first two keep this quickstart inside five minutes.\n\n**A. You already run Ollama on your host** — ~274 MB, under a minute\n\n```\nollama pull nomic-embed-text\n```\n\nNothing else to configure: Compose already points the container at\n`host.docker.internal:11434`\n\n. This is the fastest path and the one to prefer if you have\nOllama installed.\n\n**B. Hosted embeddings** — instant, needs a free Nomic key\n\n```\nprintf 'DEPLOYMENT_MODE=hybrid\\nINFERENCE_LOCATION=local\\nEMBEDDING_LOCATION=cloud\\nNOMIC_API_KEY=your_key_here\\n' >> .env\ndocker compose up -d api\n```\n\nAll four lines are required. `DEPLOYMENT_MODE`\n\ndefaults to `local`\n\n, which **forbids**\ncloud providers outright — supplying `NOMIC_API_KEY`\n\nwithout switching to `hybrid`\n\nis a\ndeliberate startup failure, not an oversight, so the container refuses to boot and tells\nyou so. `hybrid`\n\nis what lets you mix local inference with cloud embeddings.\n\nConfirm it took effect — the startup log states the resolved mode:\n\n```\n[startup] Deployment mode: hybrid (inference=local, embedding=cloud, telemetry=allowed)\n```\n\nNote that this sends the text you ingest to Nomic. Use A or C if that matters.\n\n**C. Ollama inside Compose** — fully self-contained, but a ~7 GB image pull first\n\n**This is the slow path.** `ollama/ollama:latest`\n\nis about **7 GB** because it ships GPU\nruntimes, and on a normal connection the pull alone takes ten minutes or more. Right\nchoice if you want everything in Compose and nothing on your host — not the right choice\nif you are trying Anansi for the first time.\n\n```\necho \"OLLAMA_BASE_URL=http://ollama:11434\" >> .env\ndocker compose --profile local-ai up -d       # pulls the 7 GB image\ndocker compose exec ollama ollama pull nomic-embed-text   # 274 MB\n```\n\nAppending is safe even if `OLLAMA_BASE_URL`\n\nis already set — Compose takes the last\ndefinition. If you set it *after* the API was already running, restart it with\n`docker compose up -d api`\n\nso it picks up the new address.\n\nWhichever you pick, `nomic-embed-text`\n\n(274 MB) is all that ingest and retrieval need. The\nmuch larger chat model (`llama3.1:8b`\n\n, ~4.7 GB) is only used to synthesize the `static`\n\nand\n`dynamic`\n\nprofiles — pull it later with `ollama pull llama3.1:8b`\n\nwhen you want those.\n\nKeys live in your own database — this does not contact any hosted service:\n\n```\ndocker compose exec api node dist/scripts/seed-dev-key.js you@example.com\n```\n\nIt prints a key beginning `ans_`\n\n. Export it:\n\n```\nexport ANANSI_API_KEY=ans_...\n```\n\nEach email address gets its **own workspace**, and memory never crosses between them.\nRe-running the command with the same email issues another key into the same workspace;\nrunning it with a *different* email gives you a key that cannot see anything you stored\nearlier. If your data seems to have vanished, check which email the key came from.\n\n```\ncurl -X POST http://localhost:3000/v1/ingest \\\n  -H \"Authorization: Bearer $ANANSI_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"userId\":\"user_123\",\"content\":\"User is building a voice agent. Prefers TypeScript. Team of 4.\",\"sourceType\":\"conversation\"}'\n```\n\nReturns `202`\n\nimmediately — embedding happens in the background.\n\n```\ncurl -G http://localhost:3000/v1/context \\\n  -H \"Authorization: Bearer $ANANSI_API_KEY\" \\\n  --data-urlencode \"userId=user_123\" \\\n  --data-urlencode \"q=what is the user building?\"\n```\n\n`relevant`\n\ncomes back populated:\n\n```\n{ \"relevant\": [ { \"content\": \"User is building a voice agent. Prefers TypeScript. Team of 4.\",\n                  \"similarity\": 0.4821 } ], \"static\": [], \"dynamic\": [] }\n```\n\nTwo things are worth knowing about that response:\n\nEmbedding is asynchronous, so a query issued within a second of ingest can be answered by keyword search alone. Ask again and you will see a real cosine score. Search is hybrid, so you get an answer either way rather than an empty result.`similarity: 0`\n\nmeans the embedding had not landed yet.until a chat model is available for synthesis (step 2). That is expected, not a failure.`static`\n\nand`dynamic`\n\nstay empty\n\nTo prove the semantic half is genuinely working, ask something that shares no words with what you stored:\n\n```\ncurl -G http://localhost:3000/v1/context \\\n  -H \"Authorization: Bearer $ANANSI_API_KEY\" \\\n  --data-urlencode \"userId=user_123\" \\\n  --data-urlencode \"q=which coding language do they like?\"\n```\n\nThat scores *higher* (`0.5915`\n\n) than the keyword-overlapping question, because nothing in\nit matches literally — only in meaning.\n\nCheck [ /status](http://localhost:3000/status) first: it reports Postgres, Redis, the\nqueue,\n\n**and** the embedding backend, and returns\n\n`503`\n\nwhen any of them is down. A `503`\n\nfrom `/v1/context`\n\nnames the failing dependency and how to fix it directly in the\nresponse body.| Local (no keys, no account) | Optional / external | |\n|---|---|---|\n| API, Postgres, Redis, workers | ✅ started by `docker compose up -d` |\n|\n| Embedding + synthesis (Ollama) | ✅ option A/C above | Nomic hosted embeddings (option B), Cerebras/GitHub Models for synthesis |\n| Everything in\n|\n\n`SENTRY_DSN`\n\n) is optional and off by defaultThe full local path — Docker + Ollama, no connectors configured — never sends ingested content off your machine. Once you add a cloud embedding/LLM provider or a connector, that surface's data leaves the box; see [Security](#security) for exactly what each `DEPLOYMENT_MODE`\n\nallows.\n\nThe Compose defaults are deliberately **development-only** and are sufficient to start a\ndisposable local stack without creating `.env`\n\n. The cryptographic values baked into\n`docker-compose.yml`\n\nare published in this repository and therefore public — never use\nthem outside local development. For anything persistent, copy\n[ .env.example](/g-33-L/anansi/blob/main/.env.example), generate distinct values for\n\n`ENCRYPTION_KEY`\n\n,\n`API_KEY_HMAC_SECRET`\n\n, `CSRF_SIGNING_KEY`\n\n, and `QUERY_API_KEY`\n\n(`openssl rand -hex 32`\n\neach), and read\n[.](/g-33-L/anansi/blob/main/docs/enterprise/self-hosting.md)\n\n`docs/enterprise/self-hosting.md`\n\n**Never change**— all stored connector tokens are encrypted with it.\n\n`ENCRYPTION_KEY`\n\nafter first installIf you run Ollama on your host rather than via the `local-ai`\n\nprofile, the Compose\ndefault (`host.docker.internal`\n\n) already points at it. A `.env`\n\nwritten for host-run\n`pnpm dev`\n\nwill contain `localhost:11434`\n\n, which inside a container means the container\nitself — Compose interpolates that file, so the API silently cannot reach your host\nOllama. The `503`\n\nbody names the address it tried, which is how you spot this.\n\nPrefer running from source with `pnpm dev`\n\n? See [ CONTRIBUTING.md](/g-33-L/anansi/blob/main/CONTRIBUTING.md) — note\nthat\n\n`pnpm test`\n\nneeds `DATABASE_URL`\n\nand `REDIS_URL`\n\nin your shell, and that the suite\n`TRUNCATE`\n\ns the local database, so do not point it at anything you care about.Using the TypeScript SDK ([ packages/sdk](/g-33-L/anansi/blob/main/packages/sdk)):\n\n``` python\nimport AnansiMemory from \"anansi-memory\";\n\nconst memory = new AnansiMemory({\n  apiKey: process.env.ANANSI_API_KEY,\n  baseUrl: \"http://localhost:3000\", // required when self-hosting — see below\n});\n\nawait memory.ingest({\n  userId: \"user_123\",\n  content: \"User is building a voice agent. Prefers TypeScript. Team of 4.\",\n  sourceType: \"conversation\",\n});\n\nconst ctx = await memory.context({ userId: \"user_123\", q: \"what is the user building?\" });\nconst systemPrompt = `You are a helpful assistant.\\n\\n${memory.formatForPrompt(ctx)}`;\n```\n\nSelf-hosters: set the base URL.Every client defaults to the hosted API at`https://anansimemory.com`\n\n(`packages/sdk/src/index.ts:173`\n\n,`packages/sdk-python/anansi_memory/client.py:77`\n\n). If you skip it, your calls go to the hosted service rather than your own instance, and your local key will not authenticate there. The option is`baseUrl`\n\n(TypeScript),`base_url`\n\n(Python), and`ANANSI_BASE_URL`\n\n(MCP).\n\nAlso shipped: Python (`anansi-memory`\n\n), MCP server (`anansi-mcp`\n\n), Vercel AI SDK\nmiddleware (`anansi-ai-sdk`\n\n), LangChain/LangGraph (`anansi-langchain`\n\n), and\nframework-agnostic tool definitions (`anansi-tools`\n\n). All are thin HTTP clients over the\nsame `/v1`\n\nAPI and contain no logic of their own.\n\nEleven routes, all under `/v1`\n\n, all in\n[ apps/api/src/routes/v1.ts](/g-33-L/anansi/blob/main/apps/api/src/routes/v1.ts):\n\n| Route | What it does |\n|---|---|\n`POST /v1/ingest` |\nStore content. Returns `202` . |\n`POST /v1/ingest/batch` |\nSame, many at once. |\n`GET /v1/context` |\nSynthesized profile + relevant chunks. The main read. |\n`POST /v1/search` |\nRaw hybrid search when you want chunks, not a profile. |\n`GET /v1/memories` |\nPaginated raw chunks for a user. |\n`GET /v1/entities` |\nThe entity graph, with `asOf` / `asOfKnowledge` . |\n`GET /v1/ledger` |\nCited claims as of a point in time. |\n`GET /v1/ledger/divergences` |\nWhere documented practice disagrees with observed practice. |\n`GET /v1/ledger/timeline` |\nWhen each answer was adopted and superseded. |\n`DELETE /v1/memory` |\nDelete memories (cascades to the entity graph). |\n`DELETE /v1/user` |\nHard-delete a user: chunks, profile, graph. |\n\nFull reference: [ docs/api/reference.md](/g-33-L/anansi/blob/main/docs/api/reference.md).\n\nThree things are worth understanding before you commit to this.\n\nEvery edge in the entity graph carries two independent time axes\n([ lib/db/schema.ts](/g-33-L/anansi/blob/main/apps/api/src/lib/db/schema.ts)):\n\n**valid time**(`valid_from`\n\n/`valid_until`\n\n) — when it was true in the world**knowledge time**(`recorded_at`\n\n/`valid_until_recorded_at`\n\n) — when the system learned it\n\nMost memory stores have one clock, or none, and overwrite on update. That silently\nrewrites history: if you learn in June that someone left in April, a single-axis store\nnow claims you always knew. Anansi keeps both, so\n`GET /v1/entities?asOf=…&asOfKnowledge=…`\n\nreconstructs the graph as it was true *and* as\nit was believed, at any instant. This is the bi-temporal model, borrowed from accounting\nsystems; the implementation is\n[ getEntitiesForUser in lib/ai/query-engine.ts](/g-33-L/anansi/blob/main/apps/api/src/lib/ai/query-engine.ts).\n\nAlongside the graph, Anansi keeps an append-only ledger of attestations\n([ lib/db/attestations-repo.ts](/g-33-L/anansi/blob/main/apps/api/src/lib/db/attestations-repo.ts)): trust-tiered\n(\n\n`observed`\n\n/ `candidate`\n\n) claims, each backed by a verbatim quote located in a specific\nsource chunk. Nothing is auto-published — confidence defaults to 0 and status defaults to\n`candidate`\n\n. Answers are never overwritten, only superseded.`GET /v1/ledger/divergences`\n\nis the payoff: it surfaces where a documented answer (wiki,\nrunbook) disagrees with observed reality (chat, tickets), and when the practice changed.\n\nRows, vectors, and BM25 all live in Postgres. Retrieval is pgvector cosine similarity\nfused with `ts_rank`\n\nBM25 by reciprocal rank fusion — a single SQL query, transactional\nwith everything else. There is no separate vector database to operate or keep in sync.\n\nVersion `0.3.1`\n\n. Honest read, component by component.\n\n**Solid.** The ingest → embed → synthesize → retrieve loop, the bi-temporal entity graph,\nhybrid retrieval, the SDKs, the API-key auth and rate limiting. 40 test files under\n`apps/api/src/test/`\n\n, roughly 417 assertions; `temporal-query.test.ts`\n\nis the executable\nspec for the bi-temporal semantics. This is the part that has been exercised.\n\n**Works, less proven.** The ledger endpoints are shipped and tested but young. The\nconnectors (Slack, Notion, Google Docs, Linear, transcript webhooks) work but each has\nhad limited real-world mileage. Synthesis quality with a local Ollama model has **not**\nbeen systematically validated — evaluate it against your own data before relying on\ngenerated profiles. Extraction quality is measured and the weaknesses are named in\n[ apps/api/scripts/eval/BENCHMARK.md](/g-33-L/anansi/blob/main/apps/api/scripts/eval/BENCHMARK.md); read it\nrather than taking a number from this page.\n\n**Experimental.** Executable skills / procedure extraction\n(`lib/ai/skill-extraction.ts`\n\n, `lib/skill/`\n\n) — schema and extraction exist, there are no\npublic routes. `apps/graph-explorer`\n\nis a demo UI over `GET /v1/entities`\n\n, useful but not\na supported product surface.\n\n**Not claimed.** No SOC 2, ISO 27001, or HIPAA certification. No data-residency\nenforcement beyond choosing where you deploy. No published DPA.\n\nThese exist. They are also newer and less exercised than the memory engine, so here is precisely what is and is not true.\n\n| Capability | Status |\n|---|---|\nOIDC SSO |\nImplemented. Authorize → callback → JIT provision → session, at `GET /sso/:slug/login` and `/callback` (`lib/enterprise/sso/oidc.ts` ). Not integration-tested against every major IdP. |\nSAML 2.0 SSO |\nImplemented via `@node-saml/node-saml` with `wantAssertionsSigned` and `wantAuthnResponseSigned` both enforced, no unsigned fallback path (`lib/enterprise/sso/saml.ts` ). Live at `POST /sso/:slug/acs` ; SP metadata at `/sso/:slug/metadata` . Unit tests cover config validation and profile mapping only — there is no end-to-end test against a real IdP. |\nSCIM 2.0 |\nUsers and Groups, per-org bearer token, mounted at `/scim/v2` (`lib/enterprise/scim/handler.ts` ). Users: list, get, create, PATCH/PUT `active` , delete (= suspend membership, not global delete). Groups map to teams: list and create only — no group-membership sync, no group update or delete. Filters support `userName eq` and `emails.value eq` ; anything else returns the full list. |\nRBAC (console) |\n6 roles (`owner` , `admin` , `member` , `billing` , `auditor` , `viewer` ) over 28 permissions, single source of truth in `lib/identity/roles.ts` , enforced by `requirePermission` on every `/console` route. |\nAPI key scopes (`/v1` ) |\nSeparate, coarser mechanism — five scopes (`ingest` , `read` , `entities` , `ledger` , `admin` ) enforced on all 11 `/v1` routes via `requireScope` (`routes/v1.ts:202` ); `validateApiKey` loads them in the existing auth query, so there is no extra round trip. A key with no scope rows is unrestricted — the console's documented back-compat rule, so no pre-existing key changes behaviour. Denial is `403` with `code: \"insufficient_scope\"` , plus `required_scope` and `key_scopes` in the body. See \"Authentication and key scopes\" in\n`docs/api/reference.md` |\nAudit log |\nAppend-only `audit_events` , never updated or deleted, NDJSON export by keyset (`lib/enterprise/audit.ts` ). Writes are best-effort and swallowed on failure by design. Emitted from console API-key, member, SSO, and enterprise-admin actions — not from the . Ingest and retrieval are not audited.`/v1` data plane |\nApproval workflow |\nGeneric approval queue for `skill_publish` , `role_grant` , `data_export` , `connector_add` (`lib/enterprise/governance.ts` ). Only one action currently enforces an approval: audit export, which returns 403 without an approved `data_export` request. The other kinds are recorded, not enforced. |\nConfigurable PII redaction |\nPer-org rules (named detectors or regex; mask / drop / hash) applied after the built-in secret scrubber, wired into both the `/v1/ingest` path and the ingestion worker (`lib/enterprise/redaction.ts` ). |\nSigned licenses |\ned25519-verified, organization-bound, fail-closed (`lib/enterprise/license.ts` ). See the note below. |\n\n**How the gate works, plainly.** Console enterprise routes sit behind `requireEnterprise`\n\n,\nwhich needs the org's `edition`\n\nto be `enterprise`\n\nplus — outside cloud mode — a license\nsigned by the key in `LICENSE_PUBLIC_KEY`\n\n. Because you set `LICENSE_PUBLIC_KEY`\n\nyourself\non a self-hosted install, you can generate an ed25519 keypair and mint your own license.\nThe gate is a deployment control, not a lock. That is deliberate and we would rather say\nit than have you discover it.\n\nVerifiable in this repo, not claims:\n\n**API keys** HMAC-SHA256 hashed at rest; the raw key is shown once and never stored (`lib/auth/api-auth.ts`\n\n).**Connector tokens** encrypted with AES-256-GCM under`ENCRYPTION_KEY`\n\n(`lib/utils/crypto.ts`\n\n).**SSRF guards** on every outbound fetch — URL ingestion and developer webhooks resolve DNS and reject private and loopback addresses, re-checking each redirect hop (`lib/infra/safe-fetch.ts`\n\n).**Rate limiting** per workspace via a Redis sliding-window sorted set in a single Lua script, with monthly quota on top (`lib/infra/rate-limit.ts`\n\n).**Input safety**— secret redaction and prompt-injection neutralization run on all end-user content before storage and before any LLM prompt (`lib/utils/sanitize.ts`\n\n).\n\n**Tenant isolation is application-layer.** Every query scopes by `workspace_id`\n\n/\n`developer_id`\n\n. Postgres RLS on `memory_chunks`\n\nis a NULL-guard backstop, not the\nboundary. If you are contributing, any new query must carry the scoping predicate. See\n[ docs/architecture/security-model.md](/g-33-L/anansi/blob/main/docs/architecture/security-model.md).\n\nDisclosure policy: [ SECURITY.md](/g-33-L/anansi/blob/main/SECURITY.md).\n\n`DEPLOYMENT_MODE`\n\ncontrols whether content can leave the machine\n(`lib/config/deployment.ts`\n\n):\n\n`local`\n\n— air-gapped. Inference and embeddings run on Ollama; content-exporting telemetry is off.**The server refuses to start** if a cloud LLM key, cloud embedding key, or Sentry DSN is set. This is enforced at boot, not documented and hoped for.`hybrid`\n\n— explicit per-capability mix via`INFERENCE_LOCATION`\n\nand`EMBEDDING_LOCATION`\n\n.`cloud`\n\n— the default; cloud providers when keys are present, local fallback otherwise.\n\n**The engine is MIT.** Ingestion, chunking, embedding, synthesis, the bi-temporal graph,\nthe ledger, hybrid retrieval, the connectors, the SDKs, and basic multi-user identity\n(organizations, members, API keys) within a single self-hosted org. You can run all of\nit, forever, without talking to us.\n\n**One layer is commercial, not MIT:** enterprise auth (SSO/SAML, SCIM provisioning),\naudit/governance/redaction workflows, team management, and the hosted control plane\n(billing, the staff ops console). Those files carry a header naming `LICENSE-EE`\n\n— you\ncan read and evaluate them freely, but running them in production requires a commercial\nlicense. See [ LICENSE-EE](/g-33-L/anansi/blob/main/LICENSE-EE) for the exact terms, and\n\n`LICENSE`\n\nfor the full\npath list. This is the same shape as GitLab CE/EE or Sentry's open-core split: the code\nis visible, the enterprise surface is licensed separately.**The hosted service adds** operations, not capability: managed Postgres/Redis and\nupgrades, self-serve signup and billing, managed connector OAuth apps (so you don't\nregister your own Slack/Notion/Google apps), support with a response time, and an\nissued enterprise license for the EE surface above.\n\n**Two things to know before you assume \"MIT means unlimited\":**\n\n-\n**The plan tiers exist in the engine, but they do not apply to you.**`lib/billing/plans.ts`\n\nand`feature-gate.ts`\n\nare MIT and part of the engine, and`routes/v1.ts`\n\ncalls`gateFeature()`\n\nat ten sites — that machinery is here because the same code runs the hosted service. On a self-hosted install it is inert: a workspace with no subscription row defaults to`enterprise`\n\n— unlimited, nothing expires, every retrieval feature on.The default is chosen by whether upgrades are actually purchasable, which is detected by whether Stripe is configured (\n\n`resolveDefaultPlan`\n\nin`lib/billing/plans.ts`\n\n). No Stripe, no metering. Set`ANANSI_DEFAULT_PLAN`\n\nif you genuinely want to meter your own install.This is worth stating plainly because it used to be the other way round: the default was\n\n`free`\n\neverywhere, which on your own hardware meant 1,000 ingests/month and a 7-day retention window that a background worker enforced by**deleting your data**. A memory engine that forgets after a week is not a product, and we fixed it rather than documenting it. -\n**Enterprise console routes sit behind an edition check**— self-hostable, but the code backing SSO/SCIM/audit/governance is`LICENSE-EE`\n\n, not MIT, so running it in production needs a license from us even if you mint your own signing key.\n\nWe are not going to relicense the *engine* or move existing MIT-licensed features behind\na paywall. That guarantee is about the code above the line, not the EE surface below it —\nthe MIT grant on the engine you already have is the part that is actually binding, not\nthis paragraph.\n\nAll public routes are prefixed `/v1`\n\nand every response carries an `API-Version`\n\nheader.\nWithin a major version there are no breaking changes. Breaking changes ship only under a\nnew major path (e.g. `/v2`\n\n), with 90 days' notice on the prior version.\n\nIssues and PRs welcome — [ CONTRIBUTING.md](/g-33-L/anansi/blob/main/CONTRIBUTING.md) has the setup, and\n\n[is the map. Good first reading:](/g-33-L/anansi/blob/main/ARCHITECTURE.md)\n\n`ARCHITECTURE.md`\n\n`routes/v1.ts`\n\n(the whole API in one file), then `lib/ai/query-engine.ts`\n\n, then\n`apps/api/src/test/temporal-query.test.ts`\n\n.Open-core. MIT for the engine, `LICENSE-EE`\n\nfor the enterprise surface described above\n— see [Open core: what's here, what isn't](#open-core-whats-here-what-isnt) for the full\npath list, [ LICENSE](/g-33-L/anansi/blob/main/LICENSE), and\n\n[.](/g-33-L/anansi/blob/main/LICENSE-EE)\n\n`LICENSE-EE`", "url": "https://wpnews.pro/news/show-hn-anansi-open-source-memory-api-for-llm-apps", "canonical_source": "https://github.com/g-33-L/anansi", "published_at": "2026-08-11 11:02:19+00:00", "updated_at": "2026-08-11 11:12:06.134331+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-infrastructure", "ai-tools", "ai-agents"], "entities": ["Anansi", "GitHub", "PostgreSQL", "Redis", "Ollama", "Nomic", "Docker"], "alternates": {"html": "https://wpnews.pro/news/show-hn-anansi-open-source-memory-api-for-llm-apps", "markdown": "https://wpnews.pro/news/show-hn-anansi-open-source-memory-api-for-llm-apps.md", "text": "https://wpnews.pro/news/show-hn-anansi-open-source-memory-api-for-llm-apps.txt", "jsonld": "https://wpnews.pro/news/show-hn-anansi-open-source-memory-api-for-llm-apps.jsonld"}}