loomemDocs
Docs

Configuration Reference

All settings live in config.toml. There are no hardcoded defaults — every value must be explicitly configured.

Override config file path with LOOMEM_CONFIG environment variable.

Authentication is configured via [server].auth_token_env (see [server]) — there is no separate [auth] section.


[storage]

[storage]
data_dir = "./data"
vector_enabled = true
Key Type Default Description
data_dir string "./data" Root directory for RocksDB, Tantivy, embeddings
vector_enabled bool true Enable vector (embedding) search

[storage.rocksdb]

[storage.rocksdb]
max_open_files = 1000
compression = "lz4"
write_buffer_size = 67108864
max_write_buffer_number = 3
Key Type Description
max_open_files int File descriptor pool size
compression string "lz4" or "snappy" — LZ4 recommended
write_buffer_size int Bytes per write buffer (64 MB)
max_write_buffer_number int Number of write buffers

[storage.tantivy]

[storage.tantivy]
enabled = true
heap_size_mb = 128
Key Type Description
enabled bool Enable BM25 full-text search
heap_size_mb int Index writer memory pool

[storage.intent_log]

Write-ahead log for cross-store consistency.

[storage.intent_log]
enabled = true
dir = "wal"
max_size_mb = 10
sync_on_write = false
Key Type Description
enabled bool Enable WAL
dir string WAL directory (relative to data_dir)
max_size_mb int Rotate when log reaches this size
sync_on_write bool fsync every write (safer but slower)

[search]
top_k = 10
max_request_top_k = 1000      # HTTP top_k must be 1..this limit; MCP caps are separate
surprise_boost = 1.5
synonyms_file = "synonyms.toml"
entities_file = "entities.toml"
rerank_enabled = false
rerank_candidates = 20
rerank_model_dir = "models/reranker"
multi_query_enabled = false
stem_polish = true
evidence_fields = false
Key Type Description
top_k int Default number of search results
max_request_top_k int HTTP result limit ceiling (default 1000); zero or requests above it return 400 before retrieval. MCP caps remain separate.
surprise_boost f64 Novelty multiplier on ingest (Titans-inspired)
synonyms_file string Path to synonym expansion map
entities_file string Path to entity dictionary
rerank_enabled bool Enable cross-encoder reranking (~97ms/pair)
rerank_candidates int Send top N to reranker
rerank_model_dir string ONNX model directory
multi_query_enabled bool Decompose complex queries into sub-queries
stem_polish bool Enable Polish language stemming
evidence_fields bool Experimental, opt-in (default false). When true, every /v1/search hit and GET /api/memories/{id} item carries an additive evidence object (author, conversation_date, event_date, event_date_provenance, source_id, byte_start, byte_end), and each memory_search line carries a bracketed evidence tag before the score. When false, responses are byte-identical to pre-flag output. See API Reference.

[search.cache]

[search.cache]
enabled = true
max_entries = 500
ttl_secs = 300

Semantic query cache. Identical queries within TTL return cached results.

[search.graph]

[search.graph]
enabled = true
max_hops = 1
boost_factor = 0.3
max_graph_additions = 3
Key Type Description
enabled bool Enable graph-enhanced search
max_hops int 1 = direct neighbors, 2 = 2-hop
boost_factor f64 Score multiplier for graph-discovered results (0.0 – 1.0)
max_graph_additions int Max graph-only results added to search

[search.hybrid_weights]

[search.hybrid_weights]
vector = 0.6
bm25 = 0.4

Controls the fusion ratio. Must sum to 1.0.

[search.decay]

[search.decay]
l0_lambda = 0.05
l1_lambda = 0.03

Exponential decay rate per tier. Higher = faster decay. Half-life ≈ ln(2) / lambda days.

Tier Lambda Approximate half-life
L0 0.05 ~14 days
L1 0.03 ~23 days

[search.complexity]

[search.complexity]
enabled = false
simple_top_k = 3
medium_top_k = 10
complex_top_k = 20

Complexity-aware routing. When enabled, adjusts top_k based on query complexity classification. Currently disabled — all queries use full pipeline.


[worker]

[worker.consolidation]

[worker.consolidation]
interval_secs = 300
batch_size = 200
concurrency = 2
timeout_secs = 300
min_chunks_to_consolidate = 3
min_age_secs = 60
prompt_version = 1
consolidation_style = "structured"
similarity_threshold = 0.20
Key Type Description
interval_secs int How often to run (5 min)
batch_size int Max chunks per stream per run
concurrency int Parallel streams
min_chunks_to_consolidate int Skip streams with fewer chunks
min_age_secs int Only consolidate chunks older than this
consolidation_style string "structured" = typed facts with date resolution, "observation" = granular facts, "summary" = paragraph
similarity_threshold f64 Cosine similarity for topic grouping before compress (0.0 = one per chunk, 1.0 = all in one)

[worker.decay_worker]

[worker.decay_worker]
interval_secs = 3600
factor = 0.995
l0_factor = 0.990
l1_factor = 0.995
dormant_threshold = 0.01
access_boost = true
adaptive_enabled = true
adaptive_dampening = 0.5
adaptive_cap = 200
Key Type Description
interval_secs int Run every hour
l0/l1_factor f64 Decay multiplier per hour per tier
dormant_threshold f64 Score below this = marked dormant
access_boost bool Reset score on search hit
adaptive_enabled bool ACT-R: frequently accessed decay slower
adaptive_dampening f64 How much access_count influences decay
adaptive_cap int access_count ceiling for adaptive effect

[worker.compaction]

[worker.compaction]
interval_secs = 3600
timeout_secs = 300

RocksDB background compaction trigger.

[worker.clustering]

[worker.clustering]
interval_secs = 21600
max_iterations = 1000
timeout_secs = 600

Clustering worker runs k-means on L1 embeddings to group related memories.


[scheduler]

[scheduler]
enabled = true

Master switch for all background workers.


[llm]

[llm]
provider = "openai"                  # completions (consolidation / reflect)
api_key_env = "OPENAI_API_KEY"
embedding_provider = "local"         # "local" (on-device ONNX) or "openai"
embedding_model = "text-embedding-3-small"   # used when embedding_provider = "openai"
# embedding_model_path = "/abs/path"  # local model dir; default ~/.loomem/models/multilingual-e5-small
embedding_dim = 384                  # MUST match the active embedding model
compression_model = "gpt-4.1-mini"
timeout_secs = 10
fallback_to_regex = true
Key Type Description
provider string Completions provider for consolidation / reflect ("openai")
embedding_provider string "local" (on-device ONNX, no API key) or "openai". Missing in older configs → treated as "openai"
api_key_env string Environment variable name for the API key
embedding_model string Embedding model name when embedding_provider = "openai"
embedding_model_path string? Directory with model.onnx + tokenizer.json for local embeddings. Unset → ~/.loomem/models/multilingual-e5-small
embedding_dim int Embedding vector dimensions — must match the active model
compression_model string Model for consolidation, extraction, dream
timeout_secs int API call timeout
fallback_to_regex bool Consolidation only: regex compression when the completions API call fails (does not cover a missing key)

Local embeddings (default)

Fresh installs use embedding_provider = "local": embeddings are computed on-device with a quantization-free ONNX model (default multilingual-e5-small, 384-dim, good multilingual/Polish recall) via the pure-Rust tract runtime. No API key is required and no text leaves the machine. The model ships in the default build; obtain the model files with:

./scripts/fetch-embedding-model.sh         # → ~/.loomem/models/multilingual-e5-small

The completions LLM (consolidation, fact extraction, dream) is independent. With the default config (those subsystems enabled) and no OPENAI_API_KEY, the server refuses to start unless LOOMEM_ALLOW_KEYLESS=1 is set, and then memory_ingest may store the source text but returns a missing-key error and extracts no facts; LLM-based entity extraction is off and dream/consolidation fail, while memory_store and semantic search work fully locally. Disabling those subsystems in config.toml lifts the startup check, and with knowledge_extraction.enabled = false memory_ingest stores the raw transcript instead. To use a local LLM for completions too, point provider at an OpenAI-compatible endpoint (future cycle).

Embedding dimension & re-embedding

embedding_dim must match the active embedding model (multilingual-e5-small = 384, OpenAI text-embedding-3-small = 1536). The dimension a database was built with is recorded in its metadata; on a mismatch the server refuses to start rather than silently mixing vector sizes. To switch providers/models on an existing database, re-embed it:

loomem-server --reembed   # recompute all vectors with the configured provider (run with the server stopped)

To use the API instead of local embeddings, set embedding_provider = "openai", embedding_dim = 1536, export OPENAI_API_KEY, and re-embed.


[server]

[server]
host = "127.0.0.1"
port = 3030
auth_token_env = "LOOMEM_AUTH_TOKEN"
Key Type Description
host string Bind address (0.0.0.0 for production)
port int HTTP port (overridden by PORT env var)
auth_token_env string Name of the env var holding the API Bearer key (default LOOMEM_AUTH_TOKEN). If that env var is unset or empty, auth is disabled and the server runs in local passthrough mode

[resource_guards]

[resource_guards]
max_cpu_cores = 1.0
max_memory_mb = 512
min_disk_space_mb = 1024
llm_timeout_secs = 30
worker_timeout_secs = 120

Startup checks. Server refuses to start if resources are below thresholds.


[streams] and [namespaces]

[streams]
shared = "001"

[streams.agents]
assistant = { raw = "100", compressed = "101" }

[namespaces]
personal = "100"
work = "110"

streams — agent-specific raw/compressed stream IDs. namespaces — human-readable name → stream ID mapping. Returned by memory_namespaces.

Data written without an explicit stream goes to the built-in default stream __user_default__.


[pii]

[pii]
enabled = true
redact_phones = true
redact_emails = true
redact_ids = true
blocklist_file = "pii_blocklist.txt"
audit_log = true

PII is stripped before every LLM API call (consolidation, extraction, dream). Original content in RocksDB remains untouched.


[cost]

[cost]
daily_cap_usd = 15.00
alert_threshold_usd = 10.00
anomaly_multiplier = 3.0
persist = true
Key Type Description
daily_cap_usd f64 Hard stop — no LLM calls beyond this
alert_threshold_usd f64 Warning threshold
anomaly_multiplier f64 3x typical daily cost = anomaly alert
persist bool Save cost counters across restarts

[knowledge_extraction]

[knowledge_extraction]
enabled = true
model = "gpt-4.1-mini"
max_transcript_tokens = 20000
dedup_cosine_threshold = 0.92
contradiction_check = true
contradiction_cosine_min = 0.5
max_facts_per_transcript = 20
max_tokens = 2000
input_chunk_tokens = 0
split_on_truncation = false

Controls the memory_ingest LLM pipeline.

Key Type Default Description
max_tokens int 2000 Completion cap per extraction call. A reply that hits it (finish_reason: "length") is rejected, never partially admitted
input_chunk_tokens int 0 Input budget per extraction call (4 chars ≈ 1 token). 0 keeps the legacy pipeline: one call per transcript up to max_transcript_tokens. Any other value splits the transcript at line boundaries into chunks of at most min(input_chunk_tokens, max_transcript_tokens) tokens, one call each
split_on_truncation bool false When true, a chunk whose completion was truncated is re-extracted as two line-aligned halves (depth 2, at most 4 extra calls per chunk). When false, the truncated chunk is rejected and reported, with no retry

The three keys are additive: with the defaults above, extraction issues exactly the calls it did before they existed. A truncated reply is never admitted in either mode.


[fragments]

Experimental, opt-in. Off by default; existing deployments and clients keep byte-identical behavior.

[fragments]
enabled = false
max_fragment_tokens = 400
prefix_reserve_tokens = 16
provenance_role = "claim"
token_counter = "embedder"
Key Type Default Description
enabled bool false Persist source fragments on memory_ingest
max_fragment_tokens int 400 Token budget for a fragment's text, as measured by token_counter. Must be positive
prefix_reserve_tokens int 16 Headroom above the text budget. The local embedder adds exactly two special tokens to the content (no passage: prefix is prepended); the rest absorbs the gap between the chosen counter and the embedder's own tokenizer
provenance_role string "claim" Role stamped on every fragment chunk: "claim" (full weight), "evidence" (×0.5) or "cue" (×0.8). Any other value fails config load
token_counter string "embedder" "embedder" (default and recommended: the loaded local embedding model's own tokenizer, the only counter whose budget is the embedder's), "approx" (ceil(chars / 3), model-free) or "tiktoken" (cl100k_base BPE) — the two model-free counters are for non-local embedding providers and offline tests. Any other value fails config load

When enabled, memory_ingest stores, right after the whole-conversation source and before extraction, one chunk per conversation turn (long turns are split further at paragraph, sentence and whitespace boundaries). Each fragment carries the author role, the conversation date and an exact byte range into the stored source. Fragments are consolidated from birth (never consolidation or dream input), are excluded from the recent section of memory_context, carry no event date of their own (their memory_search line shows the source's observed:/recorded: date), and are deleted together with their source. The tool result gains a trailing Stored N fragments. sentence (see MCP Tools). Fragment writes are additive and never block fact extraction.

Validation at startup: max_fragment_tokens must be positive, and with embedding_provider = "local" the sum max_fragment_tokens + prefix_reserve_tokens must not exceed the local embedder's 512-token window. enabled = true with token_counter = "embedder" (the default) requires embedding_provider = "local" — with another provider, set token_counter = "approx" or "tiktoken" explicitly or config load fails. With token_counter = "embedder" and no local model loaded, segmentation fails and the memory_ingest text says so (there is no silent fallback to an approximation).

provenance_role affects ranking only on fused retrieval (vector-enabled, non-simple queries). The BM25-only path applies no provenance multiplier, so on short queries claim and evidence fragments score identically.


[entity_extraction]

[entity_extraction]
enabled = true
model = "gpt-4.1-mini"
batch_size = 20
flush_interval_secs = 3
queue_capacity = 200
confidence_threshold = 0.7
max_tokens_per_batch = 2000

Async LLM NER for entities not in the dictionary.


[contradiction]

[contradiction]
enabled = true
similarity_threshold = 0.70
max_candidates = 5
model = "gpt-4.1-mini"

When a new fact arrives, top max_candidates similar existing facts are checked via LLM for contradiction. Verdict: updates (supersede), extends (keep both), none (no relation).


[conversation_extraction]

[conversation_extraction]
enabled = true
model = "gpt-4.1-mini"
max_tokens = 2000
confidence_threshold = 0.7
dedup_threshold = 0.80
max_extractions_per_request = 30

[profile]

[profile]
enabled = true
model = "gpt-4.1-mini"
max_chunks = 100
cache_ttl_secs = 3600
max_static_facts = 30
max_recent_items = 15

User profile synthesis. Cached for cache_ttl_secs.


[retention]

[retention]
soft_delete_days = 30
hard_purge_interval_secs = 86400
Key Type Description
soft_delete_days int Days to keep soft-deleted chunks before hard purge
hard_purge_interval_secs int How often the purge worker runs (seconds)

The hard-purge worker removes expired soft-deleted chunks from RocksDB, Tantivy, and graph.


[associator]

[associator]
enabled = true
k_clusters = 0
max_clusters = 50
max_iterations = 100
min_serendipity = 0.1
max_associations = 3
Key Type Description
enabled bool Enable ECA serendipity engine and auto-clustering
k_clusters int Fixed cluster count (0 = auto)
max_clusters int Upper bound on auto-determined clusters
min_serendipity f64 Minimum score to include an association
max_associations int Max serendipitous results per search

[dream]

[dream]
enabled = true
batch_size = 50
min_group_size = 2
model = "gpt-4.1-mini"
cost_cap_usd_per_run = 0.10
Key Type Description
batch_size int Chunks per dream run
min_group_size int Min chunks per subject to merge
cost_cap_usd_per_run f64 Safety cap per dream session

[memory_generator]

[memory_generator]
enabled = true
max_chunks = 200
max_sections = 20
model = "gpt-4.1-mini"

Controls generate-memory-md endpoint output.


Environment variables

Variable Purpose Required
LOOMEM_AUTH_TOKEN API Bearer key (name configurable via server.auth_token_env) No (auth disabled if unset — local passthrough mode)
OPENAI_API_KEY LLM API key (name configurable via llm.api_key_env) Only for OpenAI completions (provider = "openai") or OpenAI embeddings (embedding_provider = "openai"). With local embeddings and no key, the default config starts only with LOOMEM_ALLOW_KEYLESS=1 and the LLM steps are unavailable (memory_store and search still work).
LOOMEM_CONFIG Config file path No (default: config.toml)
PORT Override server port No
SERVER_ORIGIN OAuth redirect base URL No (for MCP Remote)
LOOMEM_LOG_FORMAT "json" for structured logs No
LOOMEM_AT_REST_MASTER_KEY Master key enabling at-rest encryption (32-byte base64) No
LOOMEM_AT_REST_EXPECT_ENABLED Refuse to start without a master key when set No
LOOMEM_AMBIENT_CACHE_TTL_SECS Cache TTL for /v1/ambient responses No (default 60)
TELEGRAM_BOT_TOKEN, LOOMEM_TELEGRAM_CHAT_ID Optional cost-alert webhook No

For at-rest encryption, see SECURITY.md — Data at rest.

Loomem · Apache-2.0Edit on GitHub