Configuration
Engram can be configured via the CLI (`engram configure set <key> <value>`) or through environment variables. The CLI persists settings in ~/.engram/config.json and passes them to the server automatically on `engram start`.
Environment variables
| Variable | Default | Description |
|---|---|---|
PORT | 4901 | HTTP API port the server listens on. |
HOST | 127.0.0.1 | Bind address. Loopback-only by default; set 0.0.0.0 to accept connections from other machines — do this only together with ENGRAM_API_KEY. |
ENGRAM_DATABASE | sqlite | Storage backend. postgresql is not implemented yet — selecting it fails fast with an explanatory error instead of breaking on the first write. SQLite is the only supported backend. |
ENGRAM_DB_PATH | ./engram.db | Path to the SQLite database file (sqlite mode). |
DATABASE_URL | none | PostgreSQL connection URL. Only read when ENGRAM_DATABASE=postgresql, which currently refuses to start — see below. |
NODE_ENV | development | Set to production for production deployments. |
ENGRAM_DECAY_INTERVAL | 3600000 | Time between automatic decay sweeps in milliseconds. Set to 0 to disable auto-decay. Default: 1 hour. |
ENGRAM_DECAY_THRESHOLD | 0.05 | Retention score below which memories are archived during a decay sweep (0–1). |
ENGRAM_NAMESPACE | none | Optional namespace for memory isolation. When set, all operations are scoped to this namespace. |
ENGRAM_ALLOWED_ORIGINS | localhost dashboard origins | Comma-separated browser origins allowed to call the API (CORS + WebSocket). Non-browser clients (CLI, MCP, curl) are unaffected. Required if you open the docs playground against your own server. |
ENGRAM_API_KEY | none | When set, every route except /api/health requires the key via X-API-Key or Authorization: Bearer. Unset means the API is open — the local-first default. |
ENGRAM_WEBHOOK_ALLOW_PRIVATE | false | Allow webhook delivery to loopback/private addresses. Denied by default to prevent SSRF; set true if your webhook consumers run on localhost or a private network. |
ENGRAM_PG_SCHEMA_READY | false | Unsupported escape hatch: set true only if you provisioned a compatible PostgreSQL schema yourself. |
ENGRAM_UPSTREAM_TIMEOUT_MS | 300000 | Ollama proxy: abort an upstream request that never responds. Raise it for very long generations. |
ENGRAM_INDEX_PATH | {dbPath}.index | Path to persist the vector index for fast startup. Default: same path as DB with .index suffix. |
ENGRAM_EMBEDDING_MODEL | Xenova/all-MiniLM-L6-v2 | Embedding model for vectorization. Supported: Xenova/all-MiniLM-L6-v2 (384-dim), Xenova/bge-small-en-v1.5 (384), Xenova/bge-base-en-v1.5 (768). |
OLLAMA_PROXY_PORT | 11435 | Port for the Ollama memory injection proxy. |
OLLAMA_TARGET | http://localhost:11434 | Real Ollama server URL the proxy forwards to. |
ENGRAM_MAX_TOKENS | 1500 | Max context tokens injected per Ollama proxy request. |
CLI configuration
The easiest way to configure Engram:
# Set the database path engram configure set dbPath ~/.engram/engram.db # Change the API port engram configure set port 4901 # Scope memory to a namespace engram configure set namespace my-project # View current config engram configure show
Reflection needs no configuration — Engram runs no LLM. When a reflection cycle is due, the AI connected over MCP does the reasoning via request_reflection → store_reflection.
All settings are persisted in ~/.engram/config.json and passed to the server as environment variables when you run `engram start`.
Starting with environment variables
Alternatively, pass variables inline or export them before starting the server:
# Inline — start with a custom DB path PORT=4901 ENGRAM_DB_PATH=/data/engram/prod.db node apps/server/dist/index.js # Or export first export PORT=4901 export ENGRAM_DB_PATH=/var/lib/engram/engram.db export NODE_ENV=production node apps/server/dist/index.js
Running with PM2 (production)
PM2 keeps Engram alive across restarts and provides log management. Create an ecosystem.config.js or pass env vars directly:
# Install PM2 globally
npm install -g pm2
# Start with environment variables
pm2 start server.js --name engram \
--env PORT=4901 \
--env ENGRAM_DB_PATH=/var/lib/engram/engram.db \
--env NODE_ENV=production
# Or use an ecosystem file
# ecosystem.config.js
module.exports = {
apps: [{
name: 'engram',
script: 'server.js',
env: {
PORT: 4901,
ENGRAM_DB_PATH: '/var/lib/engram/engram.db',
NODE_ENV: 'production',
},
}],
}
# Then start with:
pm2 start ecosystem.config.js
pm2 save # persist across reboots
pm2 startup # generate system startup scriptRunning with systemd
For servers managed by systemd, create a unit file:
# /etc/systemd/system/engram.service [Unit] Description=Engram Memory Server After=network.target [Service] Type=simple User=engram WorkingDirectory=/opt/engram ExecStart=/usr/bin/node server.js Restart=on-failure RestartSec=5 Environment=PORT=4901 Environment=ENGRAM_DB_PATH=/var/lib/engram/engram.db Environment=NODE_ENV=production [Install] WantedBy=multi-user.target
# Enable and start sudo systemctl daemon-reload sudo systemctl enable engram sudo systemctl start engram # Check status and logs sudo systemctl status engram journalctl -u engram -f
Database defaults
Engram uses SQLite with the following pragmas applied automatically at startup. These are hardcoded for optimal performance and cannot be changed.
| Pragma | Value | Effect |
|---|---|---|
journal_mode | WAL | Write-Ahead Logging enables 10k+ writes/sec and concurrent reads. |
synchronous | NORMAL | Balanced durability — safe with WAL mode, faster than FULL. |
cache_size | 10000 | ~40 MB page cache for faster repeated queries. |
foreign_keys | ON | Enforces referential integrity across tables. |
A passive WAL checkpoint runs automatically before stats queries to ensure read consistency when concurrent writes are active.
PostgreSQL (not implemented)
Use the default SQLite backend. If you have provisioned a compatible schema yourself, the unsupported escape hatch ENGRAM_PG_SCHEMA_READY=true lets the server start at your own risk. Proper PostgreSQL support (a pgTable schema, a PostgreSQL Drizzle config, a migrator run at connect time and BYTEA embedding storage) is on the roadmap.
Internal scoring algorithm
When recalling memories, Engram ranks results using a weighted composite score. These weights are hardcoded in the core scoring engine and provided here for reference.
| Factor | Weight | Details |
|---|---|---|
| Similarity | 45% | Cosine similarity between the query embedding and stored memory embedding. |
| Recency | 25% | Exponential decay with a 7-day half-life. Recently accessed memories score higher. |
| Importance | 20% | User-assigned or auto-computed importance value (0–1). |
| Access frequency | 10% | How often a memory has been recalled. Frequently accessed memories are boosted. |
Recall defaults
| Parameter | Default | Context |
|---|---|---|
| Similarity threshold | 0.3 | Minimum cosine similarity to include a result. |
| topK (search) | 10 | Maximum results for /api/search. |
| topK (recall) | 20 | Maximum results for /api/recall. |
| maxTokens (recall) | 2000 | Token budget for recall response content. |
| Graph expansion depth | 2 | Hops traversed in the knowledge graph when expanding results. |
Decay & garbage collection
Engram includes an automatic memory decay and garbage collection system inspired by the Ebbinghaus forgetting curve. Memories that are old, rarely accessed, and low-importance are progressively decayed and eventually archived.
Retention score formula
retentionScore = importance × recencyFactor × accessFactor Where: recencyFactor = exp(−ln(2) × daysSinceLastTouch / halfLifeDays) accessFactor = min(1.0, 0.3 + 0.7 × log10(accessCount + 1) / 3) When retentionScore < archiveThreshold → memory is archived (soft-deleted)
Default decay policy
| Parameter | Default | Description |
|---|---|---|
halfLifeDays | 7 | Ebbinghaus half-life — how many days until recency factor halves. |
archiveThreshold | 0.05 | Retention score below which a memory is archived. |
decayIntervalMs | 3600000 | Auto-sweep interval (1 hour). Set to 0 to disable. |
batchSize | 200 | Memories evaluated per batch during a sweep. |
importanceDecayRate | 0.01 | Daily importance reduction (1% per day without access). |
importanceFloor | 0.05 | Minimum importance value after decay. |
Protection rules
The following memories are never archived by the decay engine, regardless of their retention score:
- Semantic memories with importance ≥ 0.8
- Procedural memories with confidence ≥ 0.9
- Memories accessed in the last 24 hours
- Memories tagged with "pinned" or "protected"
- Memories whose source is exactly claude-code, ollama or rest-api — the rule is an exact match, so other client identifiers (for example cursor or windsurf) are not protected
Auto-consolidation
After each decay sweep, Engram can automatically consolidate clusters of old episodic memories into semantic summaries. This is enabled by default and only runs on episodes older than 24 hours with at least 3 similar memories (similarity ≥ 0.6).
Contradiction detection
When a new memory is stored, Engram checks for conflicts with existing memories using vector similarity and content analysis (negation, value changes, temporal overrides, opposite sentiment). Configuration is managed at runtime via the API:
| Setting | Default | Description |
|---|---|---|
enabled | true | Toggle contradiction checking on/off. |
similarityThreshold | 0.65 | Minimum vector similarity to consider two memories as same-topic. |
confidenceThreshold | 0.4 | Minimum confidence to flag a contradiction. |
maxCandidates | 10 | Max candidate memories to evaluate per store. |
defaultStrategy | keep_both | Default resolution: keep_newest, keep_oldest, keep_important, keep_both, manual. |
autoResolve | false | Automatically resolve using the default strategy. |
Update at runtime: PUT /api/contradictions/config
Index persistence
Engram persists the in-memory vector index to disk on shutdown and reloads it on startup. This enables fast incremental startup — only new memories since the last save are loaded from the database.
| Setting | Default | Description |
|---|---|---|
ENGRAM_INDEX_PATH | {ENGRAM_DB_PATH}.index | Where to save the index file. |
With a persisted index, startup for 1k memories goes from ~1.2s to ~45ms (27x faster). Use POST /api/index/rebuild to force a full rebuild if the index becomes stale.
Webhooks
Subscribe external systems to memory events. Configure via the API:
curl -X POST http://localhost:4901/api/webhooks \
-H "Content-Type: application/json" \
-d '{"url":"https://hooks.slack.com/...","events":["stored","contradiction"]}'Events: stored, forgotten, decayed, consolidated, contradiction, reflected. Deliveries include HMAC-SHA256 signing when a secret is configured. Failed deliveries retry 3 times with exponential backoff. After 10 consecutive failures, the webhook is auto-disabled. Delivery to loopback and private addresses is rejected unless ENGRAM_WEBHOOK_ALLOW_PRIVATE=true.
Plugins
Extend Engram with lifecycle hooks. Plugins run in-process and fire at 6 lifecycle points: onStore, onRecall, onForget, onDecay, onStartup, onShutdown.
// Register a plugin programmatically
brain.registerPlugin({
id: 'my-org/logger',
name: 'Memory Logger',
version: '1.0.0',
hooks: {
onStore: async (ctx) => console.log('Stored:', ctx.memory.id),
},
});