cd /news/artificial-intelligence/show-hn-anansi-open-source-memory-ap… · home topics artificial-intelligence article
[ARTICLE · art-91833] src=github.com ↗ pub= topic=artificial-intelligence verified=true sentiment=· neutral

Show HN: Anansi – open-source memory API for LLM apps

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.

read17 min views1 publishedAug 11, 2026
Show HN: Anansi – open-source memory API for LLM apps
Image: source

A self-hostable memory engine for AI agents that can distinguish what was true from what the agent knew at the time.

Anansi gives an AI system a durable understanding of how an organization actually works — and how that changed over time.

You feed it the exhaust your company already produces: conversations, docs, tickets, meeting transcripts. Anansi turns that into structured memory your agent can read before it answers, and keeps every version of it. So you can ask not just "what is our escalation process?" but "what did we think it was in March, and when did it change?" — and get an answer with a citation.

TL;DR:MIT-licensed self-hostable core, with a commercial enterprise/hosted layer. See[License].

Two API calls do the work:

POST /v1/ingest    # remember this
GET  /v1/context   # what do you know that's relevant right now?

ingest

returns 202

immediately and does the expensive work on a queue, so it never sits in your response path. context

returns a compact, already-synthesized profile you can paste straight into a system prompt — not a pile of chunks to rank yourself.

Self-hosted, MIT licensed, runs on Postgres and Redis. About five minutes to a working instance, no signup.

What that citation actually buys you — the entity graph carries two independent time axes, so you can ask what was true and what the system knew, separately:

The entity graph and temporal

query results (the two-axis reasoning above) are a Pro+ feature. Self-hosted installs default to the enterprise

plan (see Plan limits) and get it automatically; on the hosted service it requires a paid tier.

No account and no API key from us. Everything below runs on your machine.

Timings are measured, not aspirational: the API image builds from source in about 90 seconds on a warm Docker, and the embedding model is a 274 MB download. The five-minute figure assumes option A or B in step 2 — option C pulls a 7 GB image and takes considerably longer.

git clone https://github.com/g-33-L/anansi.git
cd anansi
docker compose up -d

That brings up PostgreSQL, Redis, and the API. Migrations run automatically on first boot. The API and docs serve at ** http://localhost:3000**.

Anansi needs an embedding model, and Compose does not start one for you. Skip this step and ingest will appear to succeed — it returns 202

because embedding is asynchronous — while retrieval fails with 503

. Open /status; it reports the embedding backend explicitly, so you can see the problem rather than infer it.

Three ways to satisfy it, ordered by how long they actually take. Only the first two keep this quickstart inside five minutes.

A. You already run Ollama on your host — ~274 MB, under a minute

ollama pull nomic-embed-text

Nothing else to configure: Compose already points the container at host.docker.internal:11434

. This is the fastest path and the one to prefer if you have Ollama installed.

B. Hosted embeddings — instant, needs a free Nomic key

printf 'DEPLOYMENT_MODE=hybrid\nINFERENCE_LOCATION=local\nEMBEDDING_LOCATION=cloud\nNOMIC_API_KEY=your_key_here\n' >> .env
docker compose up -d api

All four lines are required. DEPLOYMENT_MODE

defaults to local

, which forbids cloud providers outright — supplying NOMIC_API_KEY

without switching to hybrid

is a deliberate startup failure, not an oversight, so the container refuses to boot and tells you so. hybrid

is what lets you mix local inference with cloud embeddings.

Confirm it took effect — the startup log states the resolved mode:

[startup] Deployment mode: hybrid (inference=local, embedding=cloud, telemetry=allowed)

Note that this sends the text you ingest to Nomic. Use A or C if that matters.

C. Ollama inside Compose — fully self-contained, but a ~7 GB image pull first

This is the slow path. ollama/ollama:latest

is about 7 GB because it ships GPU runtimes, and on a normal connection the pull alone takes ten minutes or more. Right choice if you want everything in Compose and nothing on your host — not the right choice if you are trying Anansi for the first time.

echo "OLLAMA_BASE_URL=http://ollama:11434" >> .env
docker compose --profile local-ai up -d       # pulls the 7 GB image
docker compose exec ollama ollama pull nomic-embed-text   # 274 MB

Appending is safe even if OLLAMA_BASE_URL

is already set — Compose takes the last definition. If you set it after the API was already running, restart it with docker compose up -d api

so it picks up the new address.

Whichever you pick, nomic-embed-text

(274 MB) is all that ingest and retrieval need. The much larger chat model (llama3.1:8b

, ~4.7 GB) is only used to synthesize the static

and dynamic

profiles — pull it later with ollama pull llama3.1:8b

when you want those.

Keys live in your own database — this does not contact any hosted service:

docker compose exec api node dist/scripts/seed-dev-key.js you@example.com

It prints a key beginning ans_

. Export it:

export ANANSI_API_KEY=ans_...

Each email address gets its own workspace, and memory never crosses between them. Re-running the command with the same email issues another key into the same workspace; running it with a different email gives you a key that cannot see anything you stored earlier. If your data seems to have vanished, check which email the key came from.

curl -X POST http://localhost:3000/v1/ingest \
  -H "Authorization: Bearer $ANANSI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"userId":"user_123","content":"User is building a voice agent. Prefers TypeScript. Team of 4.","sourceType":"conversation"}'

Returns 202

immediately — embedding happens in the background.

curl -G http://localhost:3000/v1/context \
  -H "Authorization: Bearer $ANANSI_API_KEY" \
  --data-urlencode "userId=user_123" \
  --data-urlencode "q=what is the user building?"

relevant

comes back populated:

{ "relevant": [ { "content": "User is building a voice agent. Prefers TypeScript. Team of 4.",
                  "similarity": 0.4821 } ], "static": [], "dynamic": [] }

Two things are worth knowing about that response:

Embedding 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

means the embedding had not landed yet.until a chat model is available for synthesis (step 2). That is expected, not a failure.static

anddynamic

stay empty

To prove the semantic half is genuinely working, ask something that shares no words with what you stored:

curl -G http://localhost:3000/v1/context \
  -H "Authorization: Bearer $ANANSI_API_KEY" \
  --data-urlencode "userId=user_123" \
  --data-urlencode "q=which coding language do they like?"

That scores higher (0.5915

) than the keyword-overlapping question, because nothing in it matches literally — only in meaning.

Check /status first: it reports Postgres, Redis, the queue,

and the embedding backend, and returns

503

when any of them is down. A 503

from /v1/context

names the failing dependency and how to fix it directly in the response body.| Local (no keys, no account) | Optional / external | | |---|---|---| | API, Postgres, Redis, workers | ✅ started by docker compose up -d | | | Embedding + synthesis (Ollama) | ✅ option A/C above | Nomic hosted embeddings (option B), Cerebras/GitHub Models for synthesis | | Everything in |

SENTRY_DSN

) 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 for exactly what each DEPLOYMENT_MODE

allows.

The Compose defaults are deliberately development-only and are sufficient to start a disposable local stack without creating .env

. The cryptographic values baked into docker-compose.yml

are published in this repository and therefore public — never use them outside local development. For anything persistent, copy .env.example, generate distinct values for

ENCRYPTION_KEY

, API_KEY_HMAC_SECRET

, CSRF_SIGNING_KEY

, and QUERY_API_KEY

(openssl rand -hex 32

each), and read .

docs/enterprise/self-hosting.md

Never change— all stored connector tokens are encrypted with it.

ENCRYPTION_KEY

after first installIf you run Ollama on your host rather than via the local-ai

profile, the Compose default (host.docker.internal

) already points at it. A .env

written for host-run pnpm dev

will contain localhost:11434

, which inside a container means the container itself — Compose interpolates that file, so the API silently cannot reach your host Ollama. The 503

body names the address it tried, which is how you spot this.

Prefer running from source with pnpm dev

? See CONTRIBUTING.md — note that

pnpm test

needs DATABASE_URL

and REDIS_URL

in your shell, and that the suite TRUNCATE

s the local database, so do not point it at anything you care about.Using the TypeScript SDK ( packages/sdk):

import AnansiMemory from "anansi-memory";

const memory = new AnansiMemory({
  apiKey: process.env.ANANSI_API_KEY,
  baseUrl: "http://localhost:3000", // required when self-hosting — see below
});

await memory.ingest({
  userId: "user_123",
  content: "User is building a voice agent. Prefers TypeScript. Team of 4.",
  sourceType: "conversation",
});

const ctx = await memory.context({ userId: "user_123", q: "what is the user building?" });
const systemPrompt = `You are a helpful assistant.\n\n${memory.formatForPrompt(ctx)}`;

Self-hosters: set the base URL.Every client defaults to the hosted API athttps://anansimemory.com

(packages/sdk/src/index.ts:173

,packages/sdk-python/anansi_memory/client.py:77

). 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 isbaseUrl

(TypeScript),base_url

(Python), andANANSI_BASE_URL

(MCP).

Also shipped: Python (anansi-memory

), MCP server (anansi-mcp

), Vercel AI SDK middleware (anansi-ai-sdk

), LangChain/LangGraph (anansi-langchain

), and framework-agnostic tool definitions (anansi-tools

). All are thin HTTP clients over the same /v1

API and contain no logic of their own.

Eleven routes, all under /v1

, all in apps/api/src/routes/v1.ts:

Route What it does
POST /v1/ingest
Store content. Returns 202 .
POST /v1/ingest/batch
Same, many at once.
GET /v1/context
Synthesized profile + relevant chunks. The main read.
POST /v1/search
Raw hybrid search when you want chunks, not a profile.
GET /v1/memories
Paginated raw chunks for a user.
GET /v1/entities
The entity graph, with asOf / asOfKnowledge .
GET /v1/ledger
Cited claims as of a point in time.
GET /v1/ledger/divergences
Where documented practice disagrees with observed practice.
GET /v1/ledger/timeline
When each answer was adopted and superseded.
DELETE /v1/memory
Delete memories (cascades to the entity graph).
DELETE /v1/user
Hard-delete a user: chunks, profile, graph.

Full reference: docs/api/reference.md.

Three things are worth understanding before you commit to this.

Every edge in the entity graph carries two independent time axes ( lib/db/schema.ts):

valid time(valid_from

/valid_until

) — when it was true in the worldknowledge time(recorded_at

/valid_until_recorded_at

) — when the system learned it

Most memory stores have one clock, or none, and overwrite on update. That silently rewrites history: if you learn in June that someone left in April, a single-axis store now claims you always knew. Anansi keeps both, so GET /v1/entities?asOf=…&asOfKnowledge=…

reconstructs the graph as it was true and as it was believed, at any instant. This is the bi-temporal model, borrowed from accounting systems; the implementation is getEntitiesForUser in lib/ai/query-engine.ts.

Alongside the graph, Anansi keeps an append-only ledger of attestations ( lib/db/attestations-repo.ts): trust-tiered (

observed

/ candidate

) claims, each backed by a verbatim quote located in a specific source chunk. Nothing is auto-published — confidence defaults to 0 and status defaults to candidate

. Answers are never overwritten, only superseded.GET /v1/ledger/divergences

is the payoff: it surfaces where a documented answer (wiki, runbook) disagrees with observed reality (chat, tickets), and when the practice changed.

Rows, vectors, and BM25 all live in Postgres. Retrieval is pgvector cosine similarity fused with ts_rank

BM25 by reciprocal rank fusion — a single SQL query, transactional with everything else. There is no separate vector database to operate or keep in sync.

Version 0.3.1

. Honest read, component by component.

Solid. The ingest → embed → synthesize → retrieve loop, the bi-temporal entity graph, hybrid retrieval, the SDKs, the API-key auth and rate limiting. 40 test files under apps/api/src/test/

, roughly 417 assertions; temporal-query.test.ts

is the executable spec for the bi-temporal semantics. This is the part that has been exercised.

Works, less proven. The ledger endpoints are shipped and tested but young. The connectors (Slack, Notion, Google Docs, Linear, transcript webhooks) work but each has had limited real-world mileage. Synthesis quality with a local Ollama model has not been systematically validated — evaluate it against your own data before relying on generated profiles. Extraction quality is measured and the weaknesses are named in apps/api/scripts/eval/BENCHMARK.md; read it rather than taking a number from this page.

Experimental. Executable skills / procedure extraction (lib/ai/skill-extraction.ts

, lib/skill/

) — schema and extraction exist, there are no public routes. apps/graph-explorer

is a demo UI over GET /v1/entities

, useful but not a supported product surface.

Not claimed. No SOC 2, ISO 27001, or HIPAA certification. No data-residency enforcement beyond choosing where you deploy. No published DPA.

These exist. They are also newer and less exercised than the memory engine, so here is precisely what is and is not true.

Capability Status
OIDC SSO
Implemented. Authorize → callback → JIT provision → session, at GET /sso/:slug/login and /callback (lib/enterprise/sso/oidc.ts ). Not integration-tested against every major IdP.
SAML 2.0 SSO
Implemented 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.
SCIM 2.0
Users 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.
RBAC (console)
6 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.
API key scopes (/v1 )
Separate, 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
docs/api/reference.md
Audit log
Append-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
Approval workflow
Generic 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.
Configurable PII redaction
Per-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 ).
Signed licenses
ed25519-verified, organization-bound, fail-closed (lib/enterprise/license.ts ). See the note below.

How the gate works, plainly. Console enterprise routes sit behind requireEnterprise

, which needs the org's edition

to be enterprise

plus — outside cloud mode — a license signed by the key in LICENSE_PUBLIC_KEY

. Because you set LICENSE_PUBLIC_KEY

yourself on a self-hosted install, you can generate an ed25519 keypair and mint your own license. The gate is a deployment control, not a lock. That is deliberate and we would rather say it than have you discover it.

Verifiable in this repo, not claims:

API keys HMAC-SHA256 hashed at rest; the raw key is shown once and never stored (lib/auth/api-auth.ts

).Connector tokens encrypted with AES-256-GCM underENCRYPTION_KEY

(lib/utils/crypto.ts

).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

).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

).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

).

Tenant isolation is application-layer. Every query scopes by workspace_id

/ developer_id

. Postgres RLS on memory_chunks

is a NULL-guard backstop, not the boundary. If you are contributing, any new query must carry the scoping predicate. See docs/architecture/security-model.md.

Disclosure policy: SECURITY.md.

DEPLOYMENT_MODE

controls whether content can leave the machine (lib/config/deployment.ts

):

local

— 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

— explicit per-capability mix viaINFERENCE_LOCATION

andEMBEDDING_LOCATION

.cloud

— the default; cloud providers when keys are present, local fallback otherwise.

The engine is MIT. Ingestion, chunking, embedding, synthesis, the bi-temporal graph, the ledger, hybrid retrieval, the connectors, the SDKs, and basic multi-user identity (organizations, members, API keys) within a single self-hosted org. You can run all of it, forever, without talking to us.

One layer is commercial, not MIT: enterprise auth (SSO/SAML, SCIM provisioning), audit/governance/redaction workflows, team management, and the hosted control plane (billing, the staff ops console). Those files carry a header naming LICENSE-EE

— you can read and evaluate them freely, but running them in production requires a commercial license. See LICENSE-EE for the exact terms, and

LICENSE

for the full path list. This is the same shape as GitLab CE/EE or Sentry's open-core split: the code is visible, the enterprise surface is licensed separately.The hosted service adds operations, not capability: managed Postgres/Redis and upgrades, self-serve signup and billing, managed connector OAuth apps (so you don't register your own Slack/Notion/Google apps), support with a response time, and an issued enterprise license for the EE surface above.

Two things to know before you assume "MIT means unlimited":

The plan tiers exist in the engine, but they do not apply to you.lib/billing/plans.ts

andfeature-gate.ts

are MIT and part of the engine, androutes/v1.ts

callsgateFeature()

at 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 toenterprise

— 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 (

resolveDefaultPlan

inlib/billing/plans.ts

). No Stripe, no metering. SetANANSI_DEFAULT_PLAN

if 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

free

everywhere, which on your own hardware meant 1,000 ingests/month and a 7-day retention window that a background worker enforced bydeleting your data. A memory engine that forgets after a week is not a product, and we fixed it rather than documenting it. - Enterprise console routes sit behind an edition check— self-hostable, but the code backing SSO/SCIM/audit/governance isLICENSE-EE

, not MIT, so running it in production needs a license from us even if you mint your own signing key.

We are not going to relicense the engine or move existing MIT-licensed features behind a paywall. That guarantee is about the code above the line, not the EE surface below it — the MIT grant on the engine you already have is the part that is actually binding, not this paragraph.

All public routes are prefixed /v1

and every response carries an API-Version

header. Within a major version there are no breaking changes. Breaking changes ship only under a new major path (e.g. /v2

), with 90 days' notice on the prior version.

Issues and PRs welcome — CONTRIBUTING.md has the setup, and

is the map. Good first reading:

ARCHITECTURE.md

routes/v1.ts

(the whole API in one file), then lib/ai/query-engine.ts

, then apps/api/src/test/temporal-query.test.ts

.Open-core. MIT for the engine, LICENSE-EE

for the enterprise surface described above — see Open core: what's here, what isn't for the full path list, LICENSE, and

.

LICENSE-EE

── more in #artificial-intelligence 4 stories · sorted by recency
── more on @anansi 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-anansi-open-…] indexed:0 read:17min 2026-08-11 ·