Configuration
engrava supports YAML-based configuration for production deployments. This document covers the v0.6 configuration surface most users need on the site. For the exhaustive package reference, see the OSS configuration docs.
Configuration File
Create an engrava.yaml file:
database:
path: "./engrava.db"
wal_mode: true
search:
default_fts_weight: 0.30
default_vector_weight: 0.55
default_recency_weight: 0.10
default_priority_weight: 0.05
default_graph_weight: 0.00 # opt-in graph signal
recency_half_life: 50 # cycles (cognitive-cycle recency axis)
recency_now_half_life_seconds: 604800 # seconds (transaction-time axis)
priority_boost_p1: 1.0
priority_boost_p2: 0.6
priority_boost_p3: 0.3
priority_boost_p4: 0.0
graph_edge_decay: 0.5 # 1-hop distance penalty
max_neighbors_per_candidate: 5 # safety cap
reflection_boost: 1.0 # REFLECTION score multiplier
reflection_topk_cap: 0.3 # max fraction of top-K that may be REFLECTIONs
collapse_pool_factor: 4 # arm-budget widening when collapse_key is set
vec0_overfetch_factor: 4 # sqlite-vec over-fetch before live-row trim
extensions:
vector:
backend: numpy
dimension: 384
dreaming:
enabled: true
schedule_every_n_cycles: 100
promote_threshold: 0.7
candidates_limit: 200
gates:
min_confirmations: 2
min_age_cycles: 1
max_promoted_per_run: 20
allow_zero_confirmation: true
Loading Configuration
from engrava import load_config, SqliteEngravaCore
config = load_config("engrava.yaml")
async with await SqliteEngravaCore.from_config("engrava.yaml") as store:
thought = await store.get_thought("abc")
Full Factory Method
from engrava.config import load_config, resolve_embedding_provider
config = load_config("engrava.yaml")
# resolve_embedding_provider takes the EmbeddingConfig, i.e. config.embeddings
provider = resolve_embedding_provider(config.embeddings)
Configuration Reference
database
| Key | Type | Default | Description |
|---|---|---|---|
database.path |
str |
required | Path to the SQLite database file (no default — omitting it raises ConfigError) |
database.wal_mode |
bool |
true |
Enable WAL journal mode for concurrent reads |
search
Controls hybrid search behavior (FTS5 + vector + recency + priority).
Every SearchConfig field is settable here, and every one has a default — so
the whole section is optional.
Signal weights and per-priority boosts:
| Key | Type | Default | Description |
|---|---|---|---|
default_fts_weight |
float |
0.30 |
Weight for FTS5/BM25 text score |
default_vector_weight |
float |
0.55 |
Weight for vector similarity score |
default_recency_weight |
float |
0.10 |
Weight for recency-based score |
default_priority_weight |
float |
0.05 |
Weight for priority signal |
default_graph_weight |
float |
0.0 |
Weight for 1-hop graph signal. 0.0 means the graph signal is off by default; raise it, or pass graph_weight= per call, to opt in. |
recency_half_life |
int |
50 |
Cycles for the cognitive-cycle recency score to halve |
recency_now_half_life_seconds |
int |
604800 |
Wall-clock seconds for the transaction-time recency score to halve (7 days). Applies when a query passes recency_now; override per call with recency_now_half_life=. |
priority_boost_p1 |
float |
1.0 |
Score multiplier for P1 thoughts |
priority_boost_p2 |
float |
0.6 |
Score multiplier for P2 thoughts |
priority_boost_p3 |
float |
0.3 |
Score multiplier for P3 thoughts |
priority_boost_p4 |
float |
0.0 |
Score multiplier for P4 thoughts |
graph_edge_decay |
float |
0.5 |
Decay factor for 1-hop neighbour boost |
max_neighbors_per_candidate |
int |
5 |
Max neighbours considered per candidate |
Reflection handling:
| Key | Type | Default | Description |
|---|---|---|---|
reflection_boost |
float |
1.0 |
Score multiplier applied to REFLECTION thoughts retrieved by search_hybrid(). 1.0 is neutral; above 1.0 upranks them. Overridable per call with reflection_boost=. |
reflection_topk_cap |
float |
0.3 |
Maximum fraction of the final top-K that may be REFLECTION thoughts. Excess lower-scoring reflections are evicted and backfilled. 1.0 disables the cap. |
Graph expansion over consolidation edges:
| Key | Type | Default | Description |
|---|---|---|---|
graph_expansion_enabled |
bool |
true |
When true, expand the candidate pool by traversing CONSOLIDATED_FROM edges from top-ranked reflections. |
graph_expansion_top_n |
int |
5 |
Number of top-ranked reflections used as expansion seeds per query |
graph_expansion_propagation_factor |
float |
0.7 |
Multiplier on the parent reflection score when computing propagated source scores |
graph_expansion_max_sources_per_reflection |
int |
20 |
Cap on source observations pulled per reflection |
graph_expansion_reflection_source_ceiling |
int |
50 |
Reflections with more than this many sources are skipped during expansion |
Bounded pool multipliers:
| Key | Type | Default | Description |
|---|---|---|---|
collapse_pool_factor |
int |
4 |
Bounded multiplier applied to each arm’s candidate budget only when collapse_key is passed to search_hybrid() / recall(). Must be >= 1. |
vec0_overfetch_factor |
int |
4 |
Bounded multiplier applied to top_k when the sqlite-vec backend serves search_similar(). Must be >= 1. |
Weights are redistributed proportionally when a signal is unavailable
(e.g. no explicit current_cycle, no configured cycle provider and no
recency_now → recency skipped). Set any weight to 0.0 to disable that signal
entirely.
The graph signal is off by default.
default_graph_weightis0.0, so a default store runs no graph ranking queries. This is separate fromgraph_expansion_enabled, which controls candidate-pool widening overCONSOLIDATED_FROMedges.
See Hybrid Search for the full 5-signal ranking model.
embeddings
Embedding provider configuration. (The YAML key is embeddings, plural.) The
vector dimension lives under extensions.vector.dimension, not here.
| Key | Type | Default | Description |
|---|---|---|---|
provider |
str |
null |
Provider type: "sentence-transformer", "openai-compatible", "ollama", "huggingface" |
model |
str |
null |
Model name or identifier |
auto_embed |
bool |
false |
Auto-embed on create_thought / update_thought |
require_embedding |
bool |
false |
Turn an auto-embed provider failure into a typed hard error. With the default false, the thought remains committed and unembedded if the provider fails. |
device |
str |
"cpu" |
Compute device for local providers ("cpu", "cuda") |
batch_size |
int |
32 |
Batch encoding size for local providers |
base_url |
str |
null |
Base URL for remote providers |
api_key |
str |
null |
API key for remote providers (supports ${ENV_VAR}) |
query_prefix |
str |
null |
Optional instruction prefix prepended to a search query before embedding. Applies to local/Ollama/HuggingFace providers; OpenAI-compatible providers ignore it. |
document_prefix |
str |
null |
Optional instruction prefix prepended to stored thought text before embedding. Changing it on an existing store requires a deliberate re-embed. |
Asymmetric prefixes are opt-in. Use
query_prefix/document_prefixonly for instruction-tuned embedding models that require role prefixes, such as"query: "and"passage: ". Leave both empty for symmetric models.
dreaming
Memory consolidation configuration.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
false |
Enable dreaming consolidation |
schedule_every_n_cycles |
int |
100 |
Consolidation cadence (every N cycles) |
promote_threshold |
float |
0.7 |
Weighted-score cutoff for promotion |
candidates_limit |
int |
200 |
Max thoughts to evaluate per pass |
dreaming.gates
Gate thresholds — a thought must pass all active gates to be scored.
| Key | Type | Default | Description |
|---|---|---|---|
min_confirmations |
int |
2 |
Minimum confirmation count. Bypassed when allow_zero_confirmation is true. |
min_age_cycles |
int |
1 |
Minimum current_cycle - created_cycle. Always enforced. |
max_promoted_per_run |
int |
20 |
Cap on promotions per consolidation run |
allow_zero_confirmation |
bool |
true |
Bypass the confirmation gate for single-write batches. Set to false only when your application explicitly tracks confirmations. |
dreaming.edges
Edge creation from dreaming. Promoted thoughts create
ASSOCIATED edges to their nearest neighbours.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
true |
Create edges on promotion |
top_k |
int |
1 |
Max neighbours to link per promoted thought |
min_similarity |
float |
0.7 |
Cosine threshold for edge creation |
edge_weight_factor |
float |
0.5 |
edge.weight = factor * similarity |
See Dreaming for details.
services
Multi-service isolation (one database file per named service, stored under a
shared data_dir as <name>.db).
| Key | Type | Default | Description |
|---|---|---|---|
data_dir |
str |
required | Directory holding the per-service <name>.db files |
default_service |
str |
"main" |
Default service name when --service is omitted |
configs |
dict |
{} |
Map of service name → per-service config |
Each service entry under configs supports a single optional override (there
is no per-service db_path — the file is derived as <data_dir>/<name>.db):
| Key | Type | Default | Description |
|---|---|---|---|
embeddings |
dict |
— | Per-service embedding-provider override (same shape as the top-level embeddings section) |
journal
The optional hash-chain journal. Off by default; when enabled, thought/edge mutations and action state transitions are recorded as hash-linked journal entries you can later verify for tamper-evidence. This is not a whole-database audit.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
false |
Record journaled mutations as hash-linked entries |
verify_on_open |
bool |
false |
Re-walk the persisted hash chain when opening via from_config and raise JournalIntegrityError if it does not verify. Independent of enabled; adds an O(entries) cost per open. |
journal:
enabled: true
verify_on_open: true
For the integrity-verification API, see Observability → What to alert on. The full audit journal reference lives in the OSS docs (audit-trail.md).
ttl
Time-to-live / auto-expiry of thoughts.
| Key | Type | Default | Description |
|---|---|---|---|
strategy |
str |
"archive" |
What cleanup_expired does to expired thoughts: "archive" (soft, marks ARCHIVED) or "delete" (hard) |
check_every_n_operations |
int |
0 |
Run auto-cleanup every N store operations (0 = manual only, via cleanup_expired() / engrava gc --expired) |
default_ttl_seconds |
int | null |
null |
Default TTL applied to new thoughts with no explicit expires_at (null = no default) |
ttl:
strategy: archive # or "delete"
check_every_n_operations: 100
default_ttl_seconds: 2592000 # 30 days
hygiene_policy
The deterministic memory-hygiene loop is default-off. When enabled, it can archive cold, low-value thoughts and, separately, garbage-collect hygiene-archived rows after a restore window. It is a no-LLM storage-management mechanic, not a retrieval-quality claim.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
false |
Master switch. When false, the loop is inert. |
eviction_threshold |
float |
0.20 |
Archive a thought when its eviction score falls below this threshold. |
protected_priorities |
list[str] |
["P1"] |
Priorities never auto-archived or auto-GC’d. Pinning is the hard never-forget marker. |
signal_weights |
map[str, float] |
see below | Keep-score weights over recency, frequency, confirmation, confidence, and staleness. |
check_every_n_cycles |
int |
1 |
Cadence for the convenience pass from consolidate(); an explicit run_hygiene() bypasses it. |
max_evictions_per_run |
int |
100 |
Cap for each archival/GC stage per run. |
auto_gc_enabled |
bool |
false |
Whether the physical-delete stage runs. Enabling hygiene never implicitly enables deletion. |
gc_min_archive_age_cycles |
int |
10 |
Cycle-based restore window before a hygiene-archived thought is GC-eligible. |
gc_restore_window_seconds |
int |
2592000 |
Wall-clock restore window (30 days) required in addition to gc_min_archive_age_cycles before the irreversible GC stage may delete a hygiene-archived thought, measured off the explicit archived_at column. A row archived before that column existed has no timestamp and is never GC-eligible while this window is active (fail closed). 0 disables the wall-clock window. |
min_inactivity_age_seconds |
int |
604800 |
Minimum wall-clock inactivity (7 days) before a thought may be archived at all — measured as now - COALESCE(last_accessed_at, updated_at, created_at). Below it the thought is protected, exactly like protected_priorities. This is an eligibility gate, not a scoring change; it stops a fresh or bulk-imported store, where cycle-recency degenerates into ingest order, from archiving its earliest-ingested rows. 0 disables the gate. |
dry_run |
bool |
false |
Preview mode: compute what would be archived without mutating or journaling. |
hygiene_policy:
enabled: false
eviction_threshold: 0.20
protected_priorities: ["P1"]
signal_weights:
recency: 0.30
frequency: 0.25
confirmation: 0.20
confidence: 0.15
staleness: 0.10
check_every_n_cycles: 1
max_evictions_per_run: 100
auto_gc_enabled: false
gc_min_archive_age_cycles: 10
gc_restore_window_seconds: 2592000 # 30 days
min_inactivity_age_seconds: 604800 # 7 days
dry_run: false
Both wall-clock fields are safety gates on a destructive path.
min_inactivity_age_secondsgates archival andgc_restore_window_secondsgates permanent deletion — leaving either at a lower value than you meant widens what hygiene may remove. Review them before the first run on a store that already had hygiene enabled: omitting them resolves to the defaults above, not to the pre-0.6 unguarded behaviour.
There is one further gate you cannot configure. A hygiene run archives nothing
unless at least one usage-history signal — frequency, confirmation, or
action_outcome — has a data source in the candidate pool. Cycle-based signals
are deliberately excluded from that test: they exist on any store, so on a bulk
import with no usage history they would let ingest order alone drive eviction.
Garbage collection here is cognitive hygiene, not compliance deletion. For the full mechanics, see the OSS Memory Hygiene docs.
ingest
Ingest-layer behaviour (content-hash deduplication).
| Key | Type | Default | Description |
|---|---|---|---|
deduplication_enabled |
bool |
true |
Whether ingest pipelines should pass deduplicate=True so identical content collapses into one thought (bumping confirmation_count) instead of a duplicate row |
This flag advises ingest-layer callers; the persistence-layer
create_thoughtstill defaults todeduplicate=False, so existing callers keep their behaviour unless they read this flag and forward it.
hooks
Wire a custom EngravaHooksProtocol implementation by dotted path. See
Extensions.
| Key | Type | Default | Description |
|---|---|---|---|
class |
str | null |
null |
Dotted import path to a hooks class, last segment is the class name (e.g. "my_package.hooks.MyHooks"), instantiated and used by from_config |
hooks:
class: "my_package.hooks.MyHooks"
The path is split on the final dot (module.path + ClassName) — this is a
plain dotted path, not the module.path:ATTRIBUTE colon form used by
manifests.paths below.
manifests
Load extension manifests (their hooks + schema migrations). Accepts a plain
list of dotted paths, or a mapping with discover / paths. See
Extensions.
| Key | Type | Default | Description |
|---|---|---|---|
paths |
list[str] |
[] |
Dotted module.path:ATTRIBUTE references to ExtensionManifest objects |
discover |
bool |
false |
Also scan the engrava.extensions entry-point group for manifests |
# list form
manifests:
- "my_plugin.manifest:MANIFEST"
# or mapping form
manifests:
discover: true
paths:
- "my_plugin.manifest:MANIFEST"
The
metrics:section (latency window size, enable/disable) is documented in Observability.
Environment Variables
Both are read by the engrava CLI only as fallbacks for the --config /
--db flags. They do not affect load_config() or
SqliteEngravaCore.from_config(), which read configuration solely from the YAML
file you pass them.
| Variable | Description |
|---|---|
ENGRAVA_CONFIG |
Fallback path to the YAML config file when --config is omitted (--config > ENGRAVA_CONFIG > none) |
ENGRAVA_DB |
Fallback database-file path for CLI commands when --db is omitted (--db > ENGRAVA_DB > ./engrava.db) |
Multi-Service Usage
from engrava import EngravaManager, load_config
config = load_config("engrava.yaml")
async with EngravaManager.from_config(config.services) as mgr:
store = await mgr.get_store("main")
# Use store normally...
See the CLI --service flag for command-line multi-service access.