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

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_weight is 0.0, so a default store runs no graph ranking queries. This is separate from graph_expansion_enabled, which controls candidate-pool widening over CONSOLIDATED_FROM edges.

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_prefix only 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_seconds gates archival and gc_restore_window_seconds gates 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_thought still defaults to deduplicate=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.