Get started
Self-hosting
A Next.js app over SQLite, so there is nothing to orchestrate. What there is, is a configuration surface worth reading in full — the defaults below are the numbers the design arguments are made against.
Run it
Node.js 22.19 or newer. The SDK is a workspace dependency, so build it before the app on a fresh checkout.
pnpm installpnpm --filter cogmemory buildpnpm --filter cognitive-memory devOpen http://localhost:3000, sign up, and mint a key. Every environment value is optional in development — the service creates its SQLite file and its schema on first connection, and a key is still required in production.
cp .env.example .env.localBehind a proxy
A browser asks for https://memory.example.com. Your proxy asks the app for http://localhost:3000. The app therefore has to be told which of the two is its public name, or the session cookie loses its Secure flag and sign-ins are rejected as an invalid origin.
The proxy says so in two headers. Forward both, and the app works out its own public origin:
proxy_set_header Host $host;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Proto $scheme;Next.js fills X-Forwarded-Proto in itself when no proxy sent one, and what it fills in is the scheme of the internal hop — so a proxy that terminates TLS and forwards only the host leaves the app thinking it is served over http. Set BETTER_AUTH_URL to the public URL in that case; it wins over everything the request says.
One deployment, one origin. A build that serves several hostnames — a preview and a custom domain — reads the right one per request, and the session cookie is named after the scheme it was issued on, so switching schemes signs everyone out once.
Configuration
These set the deployment defaults. Each organisation can override the three budgets from the dashboard, and null means it follows the deployment again — see the dashboard.
| Variable | Default |
|---|---|
| COGNITIVE_MEMORY_DATABASE_PATHSQLite file. The directory is created on first connection. | .cognitive-memory/cognitive-memory.sqlite |
| COGNITIVE_MEMORY_MAX_TOTAL_TOKENSCeiling on everything injected into one prompt, index and bodies together. When the cap bites the response reports truncated: true. | 2000 |
| COGNITIVE_MEMORY_MAX_INDEX_ITEMSIndex lines per prompt, so a large store cannot fill the window. | 60 |
| COGNITIVE_MEMORY_DEFAULT_RECALL_LIMITDefault result count for POST /v1/recall. | 8 |
| COGNITIVE_MEMORY_MODEL_API_KEYEnables model-backed extraction. With no key the service still learns from deterministic patterns. | — |
| COGNITIVE_MEMORY_MODEL_BASE_URLAny OpenAI-compatible gateway, including a local one. | OpenAI |
| COGNITIVE_MEMORY_MODEL_NAME | gpt-4o-mini |
| COGNITIVE_MEMORY_ENVThe environment half of a minted key: cmi_dev_… or cmi_prod_…. | dev / prod |
| BETTER_AUTH_SECRETRequired in production. Generate with openssl rand -base64 32. | — |
| BETTER_AUTH_URLThe URL this deployment is served from. Set it when the app is reached on a different name than the request carries. | the host of the request |
| NEXT_PUBLIC_COGNITIVE_MEMORY_URLOnly for a dashboard on a different host than its auth server. | the origin that served the page |
With no model key the service is not degraded, it is rules-only: deterministic patterns still learn URLs, assignments and stated requirements, and GET /api/v1/health reports extractor: "rules-only". Check that field before concluding that extraction is broken.
Budgets
The per-prompt ceiling is the single most consequential number in the deployment, and it is the one most likely to be set by copying the default. The useful question is not “how big can this be” but “how much of this store is worth a full body” — because index lines and bodies have very different prices and very different values.
A build that spends its whole budget on index lines and no bodies is not a memory that needs a bigger budget. It is a store with nothing worth promoting, and the two problems have opposite fixes. Analytics splits the spend by reason precisely so this is visible rather than guessed at.
Migrations and operations
| Command | What it does |
|---|---|
| pnpm --filter cognitive-memory db:generate | drizzle-kit generate |
| pnpm --filter cognitive-memory db:migrate | Apply migrations. |
| pnpm --filter cognitive-memory db:studio | Browse the database. |
| pnpm --filter cognitive-memory auth:generate | Regenerate the Better Auth schema. |
| pnpm --filter cognitive-memory test | Engine, dashboard, rules (56 tests). |
| pnpm --filter cognitive-memory typecheck | |
| pnpm --filter cognitive-memory measure | Regenerate the recall and token figures. |
| pnpm --filter cognitive-memory smoke | In-process smoke run. |
| pnpm --filter cognitive-memory smoke:http | 63 checks over real HTTP. |
measure is worth running against your own store rather than trusting the published numbers, because the ratio between index lines and full bodies depends entirely on what your agents have been told.
GET /api/v1/health // liveness, limits, extractor mode. No key spent.What it is built on
| Piece | Used for |
|---|---|
| Next.js 16 | App Router, route handlers, Turbopack |
| Effect 4 | Services, layers, typed error channel, ManagedRuntime at the HTTP boundary |
| Drizzle | Schema, migrations, synchronous queries over better-sqlite3 |
| Better Auth | Users, sessions, organisations |
| effect/ai | Optional model-backed extraction and reconciliation |
The server has no await in its domain logic: every service returns an Effect, one respond helper maps a tagged error to a status, and route handlers are ordinary async functions that await a Response. The consequence for you is that an error is a value you can match on — which is what makes the error shape uniform enough to branch on.