claude-mem/sdk is the in-process programmatic interface to the same
capture-and-compression engine that powers the Claude Code plugin. It is
the only supported way to embed claude-mem in your own Node application:
capture an event, run inline AI compression, and search the resulting
observations — all in one process, with no worker, daemon, or message
queue.
Headline
claude-mem worker start, no claude-mem server start, no Redis,
no Express HTTP layer.
Quick start
Install (the SDK ships in the sameclaude-mem package the plugin uses):
examples/sdk-node/ in the
claude-mem repository.
createCmemClient options
| Option | Type | Default | Notes |
|---|---|---|---|
databaseUrl | string | process.env.CLAUDE_MEM_SERVER_DATABASE_URL | Required if pool is not supplied. The SDK creates and owns the pool when this is set. |
pool | pg.Pool | — | A consumer-supplied pg.Pool. The SDK does NOT close it on client.close(). |
teamId | string (UUID) | bootstrapped default | Tenant team id. If omitted, the SDK creates a “default” team and persists the id to $CLAUDE_MEM_DATA_DIR/sdk-tenant.json. |
projectId | string (UUID) | bootstrapped default | Tenant project id. Same bootstrap rules as teamId. |
teamName | string | 'default' | Used only when the SDK has to create a default team. |
projectName | string | 'default' | Used only when the SDK has to create a default project. |
provider | CmemProvider | CmemProviderConfig | env-driven (see Provider configuration) | A pre-built provider, a { apiKey, model?, provider? } config, or omit to resolve from environment. |
chroma | ChromaOptions | {} | Tuning only (collection prefix, mcpPath). There is no enabled: false toggle — Chroma is required. |
Architecture
src/storage/postgres/* for Postgres I/O, src/server/generation/*
for inline compression, and src/services/sync/ChromaSync for the
semantic index. Everything that the server runtime’s HTTP/queue/auth
shell adds is intentionally absent here.
Chroma is required
createCmemClient will reject if uvx chroma-mcp cannot start. There is
no chroma.enabled = false toggle. claude-mem without semantic search is
broken — observations are unsearchable in the way users actually search
them.
To use the SDK, install uv:
uvx then resolves the chroma-mcp Python package on first SDK launch
and caches it. You do not need to install Chroma separately — the SDK
spawns the local chroma-mcp subprocess for you.
If the subprocess dies between createCmemClient and a later
client.search, the search call falls through to Postgres full-text
search and returns { chroma: false, degraded: true }. A
logger.error('CHROMA', …) is emitted so an operator can investigate.
The next cold-start createCmemClient will reject again until
uvx chroma-mcp can launch.
Tenancy
Every SDK call writes against a(teamId, projectId) tenant scope. You
have two options:
-
Pass explicit IDs (recommended for production). Allocate a team
and project once in your application bootstrap and store the UUIDs
somewhere you control. Pass them to every
createCmemClientcall: -
Let the SDK bootstrap a default. If you omit
teamId/projectId, the SDK creates a"default"team + project on first run and persists the resolved UUIDs to$CLAUDE_MEM_DATA_DIR/sdk-tenant.json. Subsequent runs reuse the same IDs from disk. This is fine for local development and single-tenant CLIs; it is not appropriate for multi-tenant servers.
capture, generate, startSession,
captureAndGenerate) are tenant-scoped and reads
(search, context, repos.observations.listByProject) only return
rows belonging to that (teamId, projectId) pair.
Provider configuration
The SDK resolves a generation provider in this order:-
options.provider = aPreBuiltProvider— anything implementingServerGenerationProvider(i.e. hasproviderLabel: 'claude' | 'gemini' | 'openrouter'and an async.generate(context)method). Useful for tests where you want to feed deterministic XML. -
options.provider = { apiKey, model?, provider? }— the SDK instantiates the matching concrete provider. Defaults toprovider: 'claude'. -
Environment — when
options.providerisundefined, the SDK reads:CLAUDE_MEM_SERVER_PROVIDER('claude','gemini','openrouter')ANTHROPIC_API_KEY/GEMINI_API_KEY/OPENROUTER_API_KEYCLAUDE_MEM_SERVER_MODEL(optional model id override)
createCmemClient
still succeeds (capture, search, and repos access keep working) but
client.generate(...) will reject with a clear “no generation provider
is configured” error.
Lifecycle (close())
client.close():
- Calls
chromaSync.close()and shuts down the per-processuvx chroma-mcpsubprocess. - Closes the Postgres pool only if the SDK owns it (i.e. you
constructed via
databaseUrl, notpool). - Is idempotent — calling it twice is a no-op.
close(), every method on the client (including capture,
search, generate, startSession, endSession) throws
"cmem-sdk: client is closed". There is no reopen — construct a new
client.
If you supplied your own pg.Pool, the SDK never touches its lifecycle.
Your code owns the open/close.
Error handling
A few specific failure modes are worth knowing about:-
Chroma init failure.
createCmemClientrejects with a message including"chroma-mcp could not start". The error is fatal at construction; there is no half-working fallback client. Installuv/uvx, then retry. -
No provider configured.
client.generate(jobId)orclient.captureAndGenerate(...)rejects with"no generation provider is configured". The fix is to passoptions.provideror setANTHROPIC_API_KEY/GEMINI_API_KEY/OPENROUTER_API_KEY. -
Transient Chroma failure (FTS fallback).
client.search({...})returns{ observations, chroma: false, degraded: true, error }when the runningchroma-mcpsubprocess dies mid-request. The SDK re-issues the search against Postgres full-text search so the call still returns hits — but the result is explicitly marked degraded so your code can decide whether to retry, surface a banner, or fail the caller’s own request.client.context({...})propagates thedegradedflag identically. -
Job not claimable.
client.generate(jobId)rejects with"is not claimable (status=...)"if the job has already been claimed by another worker, already completed, or terminally failed. The legal transition isqueued → processing; the SDK enforces it viatransitionStatus. -
Parse error / provider failure. If the provider throws, or returns
text that
parseAgentXmlcannot understand,client.generate(...)rejects. Before re-throwing, the SDK transitions the job to the terminalfailedstatus and records the cause in the row’slast_error— so the job is left in a diagnosable terminal state rather than stuck inprocessing(which thequeued-only claim guard could never reclaim).
See also
examples/sdk-node/— the runnable demo of the headline requirement.- The implementation plan:
plans/2026-05-25-cmem-sdk-and-server-rename.md.

