Skip to content

Configuration

Two independent layers:

  1. Environment variables, how the server runs. Set once, at boot.
  2. Project settings, how memory behaves. Editable at runtime, per project, with optional per-thread overrides.

Copy .env.example to .env in the repo root. Both the API and the dashboard read from it.

Variable Description
DATABASE_URL Postgres connection string. The database must have the vector extension.
GOOGLE_GENERATIVE_AI_API_KEY Gemini key. Required to boot, the module throws at import without it.
Variable Default Description
HOST localhost API bind address. Use 0.0.0.0 in a container.
PORT 3004 API port.
CORS_ORIGIN http://localhost:3000 Allowed browser origin(s), comma-separated.
MIGRATE_ON_START true Run pending migrations on boot.
ADMIN_USERNAME admin Username for the first-boot admin user.
ADMIN_PASSWORD randomly generated Password for that user. Printed once. Leave unset in production.
VITE_API_URL http://localhost:3004 API URL as the browser sees it. Dashboard build-time.

Read by new MemorySoda() in your application, not by the server.

Variable Description
MEMORY_SODA_BASE_URL e.g. http://localhost:3004
MEMORY_SODA_API_KEY ms_…

Full details: Environment variables.


Every project has settings for the episodic and semantic layers. They are merged over built-in defaults, so a project row only stores what you changed.

Edit them in the dashboard under Project Settings, or over the API:

Terminal window
curl -X PATCH http://localhost:3004/dashboard/projects/$PROJECT_ID/settings \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"semantic":{"factsInContext":12}}'
Setting Default What it does
semantic.factsInContext 8 How many facts recall() puts in the context block. The main quality/token dial.
semantic.retrievalMinConfidence 0.5 Facts below this extraction confidence are excluded from retrieval.
episodic.autoEpisodeIntervalMs 1800000 Idle time before extraction fires. Lower = fresher and costlier.
episodic.enabled / semantic.enabled true Turn a whole layer off.

These are retrieval-tuning constants. Changing them without measuring will usually make results worse. Documented in full at Project settings.

Setting Default
semantic.entityResolutionThreshold 0.88
semantic.factDedupThreshold 0.95
semantic.contradictionBandMin 0.80
semantic.anchorVectorMin 0.75
semantic.anchorVectorTopK 3
episodic.maxMessages 100
episodic.maxRetries 3
episodic.contextEpisodes 3
episodic.similarityWeight 0.7
episodic.recencyWeight 0.3

A thread can override the project’s episodic settings at creation. Useful for a thread that should extract on a different cadence, or not at all.

await memory.createThread({
dataset: 'user_42',
settings: {
episodic: {
autoEpisodeIntervalMs: 120_000, // extract after 2 minutes idle, not 30
},
},
});
// Ephemeral thread, never becomes long-term memory
await memory.createThread({
dataset: 'user_42',
settings: { episodic: { enabled: false } },
});

Resolution order, most specific wins:

built-in defaults ─► project.settings ─► thread.episodicSettings

Semantic settings are resolved the same way in the service layer, but there is currently no API to set them per thread, only episodic overrides are accepted by POST /v1/threads.


Off by default. Enable it per thread by setting a threshold:

await memory.createThread({
dataset: 'user_42',
autoCompactThreshold: 30, // summarise once 30 un-compacted messages accumulate
});

If you enable this, call prepare() with messageLimit >= autoCompactThreshold. Otherwise messages between the summary and the retrieved tail are silently dropped from context. prepare() returns a warning field when it detects this. See Handling long conversations.


Settings are validated on write. Requests outside these ranges return 400.

Setting Range
autoEpisodeIntervalMs (project) >= 1000, or null to disable
autoEpisodeIntervalMs (thread override) >= 1000, or null
maxMessages 10–1000
contextEpisodes 1–20
factsInContext 1–100
all 0..1 thresholds 0–1 inclusive
anchorVectorTopK 1–10
autoCompactThreshold >= 2