A library, not a service. src/ is the whole product: a memory add / search /
list SDK that mounts onto a host Interchange app. There is no server,
port, or process entrypoint here, and there never should be.
bun install # Bun 1.2+ required
bun run typecheck # tsc --noEmit
bun run test # bun test ./src (no network, no Postgres)
bun run db:setup # apply migrations (needs DATABASE_URL)
docker compose up -d # local pgvector + model endpoints for manual runsCI runs typecheck + test — both must pass before any push.
-
src/index.ts— public surface:createMemory(optionalappregisters HTTP),registerMemoryRoutes -
src/mount-config.ts/src/config.ts— mount config + engine config -
src/routes/— Hono routes (add,search,list,feed, retentionforget/purge/retention-class) -
src/tools/— InterchangedefineToolfactories (@corbits/memory/tools); HTTP clients for mounted routes (env credentials; no in-process plane) -
src/services/— capture / search / transform internals (not public verbs) -
src/ports/—DocumentStore/SourceProvider+ fakes -
src/core/— embed/rerank clients, merge, arktype schemas -
src/db/+migrations/— Drizzle schema + SQL migrations (pgvector,memory.*) -
packages/— removed; DocumentStore adapters and Linear tools are sibling packages (@corbits/mem0-memory-adapter,@corbits/supermemory-memory-adapter,@corbits/linear-tools).
-
Authenticate nothing. Identity defaults to
c.get("principal")from the Interchange context; a host may instead supplycallerResolver(src/routes/deps.ts) to resolve a non-browser caller (e.g. a workflow-run child's own sidecar bearer token) — but resolving that token is 100% host logic, called through the seam, never implemented here. Either way authorization goes through the host's grant store (@intx/authz) via the samerequireGrantpath. Never add API keys, sessions, or OAuth here.ResolvedCaller(thecallerResolverreturn type) is frozen at exactly{ tenantId, principalId }. It carries no roles, no grants, no authorization hints of any kind — it is a shape conversion (host identity in, context principal/tenant out), never an authorization decision. A resolved caller traverses the identicalrequireGrant/grantGuardpath a browser caller does and can never bypass it. Before widening this type — "let it carry roles too," "let a trusted caller skipgrantGuard" — stop: either change turns the conversion shim into the library making an authorization decision, which IS the invariant this rule exists to name. If a host needs richer machine-caller authorization, that logic belongs in the host's own grant store /callerResolverclosure, resolved down to{ tenantId, principalId }before it ever reaches this package — not in a widerResolvedCaller. -
One Postgres:
DATABASE_URL, the engine's own vector plane, under thememoryschema — never the host's control-plane DB. No foreign keys into control-plane tables; cross-refs (tenant_id,principal_id) are plaintext. -
Never embed in-process. Embedding/reranking are outbound HTTP calls to configured endpoints. A model endpoint is a trusted URL, same as the database URL — no self-host flags, no SSRF filtering here.
-
Validate at the edges with arktype — route bodies and model-endpoint responses. Keep the version range compatible with Interchange's catalog (currently
^2.1.29).
PRODUCT.md— what this is and what is out of scope (read first)ARCHITECTURE.md— why an SDK, design decisionsIMPLEMENTATION.md— env vars, data model, service internals
Keep all three current when behavior changes (/scribe maintains them).
License is LGPL-2.1 (LICENSE); contributions go through CLA.md.