Running in one command, useful in five
Postgres with pgvector, the API, the migrations and the outbox worker all come up together. No AI provider key is required to get a working system — the fallbacks are deterministic rather than absent.
- Docker Compose v2.24+
- Postgres 16 with pgvector
- Python 3.11+ · Node 20+
Bring it up
The development compose profile builds the API image, starts Postgres with the pgvector extension, applies the migrations and starts the outbox worker. The only value you must supply is a Postgres password.
One caveat worth knowing before you build anything on it: the
development profile uses the Postgres image's bootstrap superuser,
and a superuser bypasses row-level security even on tables declared
FORCE ROW LEVEL SECURITY. That makes it a single-tenant
development profile only. Production needs a separate
NOSUPERUSER NOBYPASSRLS runtime role, which the
self-hosting guide walks through.
openssl rand -base64 32 # → POSTGRES_PASSWORD
docker compose -f docker-compose.dev.yml \
--env-file .env up --build
# liveness, then a real Postgres round trip
curl -fsS localhost:8080/healthz
curl -fsS localhost:8080/readyz
open http://localhost:8080/ui/
The first five calls
In the order that makes the system make sense, rather than the order the reference lists them.
-
Store something
POST /v1/memory with text and a memory type. The write is pseudonymised, audit-logged into the hash chain, and given an embedding. An Idempotency-Key makes the call safe to retry.
-
Ask for it back
POST /v1/context with a query and a mode. You get the packed context block, the plan the router chose, and a manifest naming every record used.
-
Record a decision
POST /v1/fabric/write with entry_type "decision", the evidence identifiers, and a project. Later, POST /v1/fabric/why returns the chain rather than a paraphrase.
-
Subscribe to changes
POST /v1/webhooks with the event types you care about and a secret. Deliveries arrive signed with X-Pcnaid-Signature and every attempt is recorded.
-
Hand it to an agent
Point any stdio-capable MCP client at the server. Eighteen tools appear, along with a prompt teaching the agent that retrieved memory is evidence and never an instruction.
What every response looks like
Consistent envelopes, request identifiers you can trace into the audit log, and rate-limit headers that tell you where you stand.
Idempotency-Key: 6b1f0c…
Authorization: Bearer pcnaid_…
200 OK
X-Request-ID: 9d2a…
X-RateLimit-Remaining: 58
{
"id": "m_77e1",
"mem_type": "semantic",
"pinned": false
}
{
"error": "quota_exceeded",
"metric": "memories",
"limit": 1000,
"upgrade_url": "…"
}
429 Too Many Requests
Retry-After: 12
// per-IP and per-tenant buckets
The full surface — 141 endpoints across 32 route families — is browsable as OpenAPI on the running service at /docs, with the raw document at /openapi.json.
The settings that actually matter
There are many environment variables. These are the eight that change how the system behaves rather than where it points.
POSTGRES_DSNPOSTGRES_MIGRATION_DSNLLM_PROVIDER_MODELLM_BASE_URLENABLE_GRAPH_MEMORYENABLE_KB_RETRIEVALFERNET_KEYALLOW_SEMANTIC_FORGETEverything above runs locally, today
No account, no key, no call with us. If it does not do what this page says, tell us and we will fix the page or the product.