All configuration is via environment variables, defined in .env (loaded by Docker Compose via env_file). See .env.example for a commented template.
| Variable | Example | Description |
|---|---|---|
BRAIN_API_KEY |
openssl rand -hex 32 |
Admin API key. Required for startup (the server exits FATAL without it). Full access, no agent identity binding. |
PORT |
8084 |
Express server port. |
| Variable | Default | Description |
|---|---|---|
HOST |
127.0.0.1 |
Bind address. Set to 0.0.0.0 for LAN/Docker access. The docker-compose.yml overrides this to 0.0.0.0 inside the container. |
API_BIND |
127.0.0.1 |
Docker port binding address. Set to 0.0.0.0 in .env to expose the API on all interfaces from the host. |
A single admin key (BRAIN_API_KEY) authenticates every caller. Agent identity is declarative: each write carries its own validated source_agent, and briefings, filters, and cross-agent corroboration key off that identity — give every agent in your fleet a stable name. The key does not bind identity (any caller with the key can write as any agent); scoped per-agent keys are on the roadmap.
| Variable | Default | Description |
|---|---|---|
TRUST_PROXY |
(unset) | Express trust proxy setting (true/false, hop count, or IP/CIDR list). Set when running behind a reverse proxy so the failed-auth IP throttle keys on the real client IP. |
| Variable | Default | Description |
|---|---|---|
RATE_LIMIT_WRITES |
60 |
Max write requests (POST/PUT/PATCH/DELETE) per minute per API key |
RATE_LIMIT_READS |
120 |
Max read requests (GET) per minute per API key |
RATE_LIMIT_CONSOLIDATION |
10 |
Max consolidation + research runs per hour per API key |
POST /consolidate and POST /research share the consolidation bucket (both run LLM loops), so they can't be hammered like ordinary writes.
Vectors live in Postgres via the pgvector extension (HNSW index) — there is no separate vector container or service. The structured store and the vector store are the same Postgres database (see POSTGRES_URL below). No vector-store-specific environment variables are required.
| Variable | Default | Description |
|---|---|---|
EMBEDDING_PROVIDER |
openai |
Provider: openai, gemini, ollama |
| Variable | Default | Description |
|---|---|---|
OPENAI_API_KEY |
(required if provider=openai) | OpenAI API key |
OPENAI_EMBEDDING_MODEL |
text-embedding-3-small |
OpenAI embedding model name |
OPENAI_EMBEDDING_DIMS |
768 |
Output dimensions for the OpenAI embedder |
Uses text-embedding-3-small at 768 dimensions by default.
| Variable | Default | Description |
|---|---|---|
GEMINI_API_KEY |
(required if provider=gemini) | Google Gemini API key |
GEMINI_EMBEDDING_MODEL |
gemini-embedding-2-preview |
Gemini embedding model name |
GEMINI_EMBEDDING_DIMS |
1536 |
Output dimensions. Supports Matryoshka: 3072, 1536, 768 (.env.example ships 1536) |
Gemini uses task-specific embeddings: RETRIEVAL_DOCUMENT for storage, RETRIEVAL_QUERY for search. This improves retrieval quality but means you cannot mix providers between store and search.
Dimension tradeoffs: 3072 gives best quality but uses more storage (~12KB per vector). 1536 is a good balance and the .env.example default. 768 for minimal footprint.
| Variable | Default | Description |
|---|---|---|
OLLAMA_URL |
http://localhost:11434 |
Ollama API URL |
OLLAMA_MODEL |
nomic-embed-text |
Ollama embedding model |
Dimensions are auto-detected from the model on first embed.
| Variable | Default | Description |
|---|---|---|
STRUCTURED_STORE |
postgres |
Backend. Only postgres is supported; initStore() throws for any other value. |
| Variable | Default | Description |
|---|---|---|
POSTGRES_URL |
(required) | Connection string: postgresql://user:pass@host:5432/dbname |
POSTGRES_PASSWORD |
brain_secret |
Used by docker-compose for the Postgres container |
PGPOOL_MAX |
10 |
Max connections per pg.Pool (applies to both the vector-store and structured-store pools) |
PG_STATEMENT_TIMEOUT_MS |
30000 |
Per-connection statement_timeout in ms |
Postgres holds events, facts, statuses, entities, aliases, and relationships, provides full-text search via tsvector with a GIN index, and (via the pgvector extension) stores the embedding vectors. One container backs both the structured store and the vector store.
| Variable | Default | Description |
|---|---|---|
CONSOLIDATION_ENABLED |
true |
Set to false to disable entirely |
CONSOLIDATION_INTERVAL |
0 */6 * * * |
Cron expression for scheduled runs (default: every 6 hours) |
CONSOLIDATION_MIN_CORPUS |
1500 |
Skip scheduled runs if active corpus is below this size. Manual POST /consolidate always runs. |
CONSOLIDATION_LLM |
openai |
LLM provider: openai, anthropic, ollama, gemini |
CONSOLIDATION_MODEL |
gpt-4o-mini |
Model name |
ANTHROPIC_API_KEY |
(for anthropic provider) | Anthropic API key |
CONSOLIDATION_MAX_MEMORIES |
500 |
Per-run backlog cap, processed oldest-first; the remainder defers to the next run |
LLM_MAX_TOKENS |
8192 |
Max output tokens per consolidation LLM call (truncation now throws a typed error instead of failing JSON-parse) |
LLM_RETRY_BASE_MS |
500 |
Base delay for the single-retry exponential backoff on 429/5xx/network errors |
The consolidation engine processes memories in batches of 50, grouped by client_id. It:
- Merges duplicate facts
- Flags contradictions
- Discovers and links entities
- Reclassifies knowledge categories
- Creates entity relationships
- Cleans up old events
Scheduled consolidation is now gated by corpus size. If the active corpus is below CONSOLIDATION_MIN_CORPUS (default 1500), the scheduled run is skipped — consolidation has almost no work to do on small corpora and the LLM calls are wasted. To force a run regardless of size, POST /consolidate manually.
- High volume (>100 memories/day): Consider
0 */3 * * *(every 3 hours) - Low volume (<10 memories/day):
0 0 * * *(once daily) is sufficient - Manual only: Set
CONSOLIDATION_ENABLED=falseand trigger via API when needed
POST /research and the brain_research MCP tool run an iterate-until-sufficient retrieval loop (several LLM calls per request, using the same provider as consolidation). It is off by default and never touches the GET /memory/search hot path.
| Variable | Default | Description |
|---|---|---|
RESEARCH_ENABLED |
false |
Set to true to enable POST /research. When unset the endpoint returns 503. |
MAX_RESEARCH_ITERS |
2 |
Retrieval rounds before synthesis (clamped 1-4). |
RESEARCH_FETCH_PER_ITER |
15 |
Memories fetched per iteration. |
RESEARCH_MAX_CONTEXT |
40 |
Max memories sent to the LLM per call. |
The LLM provider initializes at startup when either CONSOLIDATION_ENABLED is on or RESEARCH_ENABLED=true, so research works even with the consolidation cron disabled.
| Variable | Default | Description |
|---|---|---|
DECAY_FACTOR |
0.98 |
Per-day decay multiplier. Only affects fact and status types. |
Formula: effective_confidence = confidence * (DECAY_FACTOR ^ days_since_last_access)
| DECAY_FACTOR | Days to 50% | Behavior |
|---|---|---|
| 0.99 | 69 days | Very slow decay, memories stay relevant longer |
| 0.98 | 34 days | Default. Unused facts fade in ~1 month |
| 0.95 | 14 days | Aggressive. Facts need access every 2 weeks to stay relevant |
Events and decisions never decay -- they are historical records.
| Variable | Default | Description |
|---|---|---|
EVENT_TTL_DAYS |
30 |
Auto-expire events after N days if never accessed and medium/low importance |
The consolidation cleanup only expires events that meet ALL criteria:
type: eventactive: trueaccess_count: 0created_atolder than TTLimportance: mediumorimportance: low
Critical and high-importance events are never auto-expired. Events that have been accessed at least once are never auto-expired.
| Variable | Default | Description |
|---|---|---|
ENTITY_MIN_CONFIDENCE |
0.80 |
Confidence threshold for confidence-gated NER. Extracted entities below this score are discarded. |
| Variable | Default | Description |
|---|---|---|
MULTI_PATH_SEARCH |
true |
Enable parallel vector + full-text search with RRF fusion |
RRF_K |
60 |
RRF smoothing constant. Range 50-100. Higher = more equal weighting across ranks. |
SEARCH_SCORE_FLOOR |
0.55 |
Minimum vector similarity, on the 0.5 + cosine/2 scale (0.55 ≈ cosine 0.1) |
RANK_W_SIM |
0.6 |
Final-ranking blend weight for vector similarity |
RANK_W_RRF |
0.4 |
Final-ranking blend weight for the normalized RRF score |
RANK_ACCESS_BOOST_CAP |
2.0 |
Cap on the access-frequency multiplier (prevents popularity runaway) |
RANK_KEYWORD_ONLY_SIM |
0.55 |
Similarity stand-in for results found only by the keyword path |
BRAIN_TIMEZONE |
(server zone) | IANA timezone used to resolve "today"/"this week" queries to civil-day boundaries |
RRF_K tuning: Lower values (50) give more weight to top-ranked items in each list. Higher values (100) flatten the distribution, giving later-ranked items more influence. Default of 60 works well for most cases.
Ranking blend: the final order is (RANK_W_SIM × similarity + RANK_W_RRF × rrf/max_rrf) × confidence-decay × capped-access-boost × temporal-proximity × importance-weight (api/src/services/ranking.js). A/B any change with the eval harness before shipping it.
Graph BFS search was removed in v4. At the current scale (~500 active memories), vector + full-text was catching everything the graph path was contributing, and the graph machinery (entity-relationships table, BFS traversal, co-occurrence scoring) added complexity without measurable retrieval lift. The entities table and alias cache are still maintained for coreference and stats.
| Variable | Default | Description |
|---|---|---|
BRAIN_API_URL |
http://localhost:8084 |
Base URL the MCP server uses to reach the API. |
BRAIN_MCP_TIMEOUT |
15000 |
Default timeout for MCP-to-API calls in ms |
BRAIN_MCP_CONSOLIDATION_TIMEOUT |
120000 |
Timeout for sync consolidation and reflect calls in ms |
The MCP server applies these timeouts to all fetch() calls to the API. Consolidation and reflect use the longer timeout because they involve LLM calls that can take 30-60 seconds.
- Architecture -- how these variables map to components
- Operations -- deployment and monitoring
- Data Model -- decay formula, scoring details