FAQ

Short answers to the questions that come up most. For “is this the right tool” see Positioning.

Does engrava call an LLM? Do I need an API key?

No. engrava never calls a language model and needs no API key to run. It stores and retrieves what your agent gives it; deciding what to remember (extraction, summarisation) is your agent’s job, above the storage layer. The one feature that synthesises new thoughts — dreaming — is purely structural (clustering, centroids, keyword counts), with no LLM involved.

An API key is only relevant if you choose a remote embedding provider (e.g. an OpenAI-compatible endpoint) — and that’s for embeddings, not for any engrava-side reasoning.

Does it need network access or any running service?

No required service. engrava is an embedded library built on SQLite — one pip install, runs in-process, no Engrava server. The only time network is involved is if you configure a remote embedding provider yourself.

Are embeddings required?

No. Without an embedding provider, search runs on FTS5/BM25 (keyword), priority, and recency signals — semantic vector matching is simply skipped. Add a provider (local or remote) when you want semantic retrieval. Note that storing on write only embeds when you set both an embedding provider and auto_embed=True.

How large a corpus can it handle?

The default vector backend brute-forces cosine similarity in Python, which keeps small and medium stores simple to inspect. When corpus size makes brute-force vector search too expensive, install the sqlite-vec backend (pip install engrava[vec], then set extensions.vector.backend: sqlite-vec) for indexed vector search. FTS5 scales independently. See Known Limitations.

Can multiple processes or tasks use the same store at once?

A single process can drive many async tasks against one store safely — aiosqlite serialises them on its background thread, and WAL mode lets readers and a single writer coexist. SQLite is single-writer, so heavy concurrent writes from multiple processes are out of scope. For multi-tenant isolation, give each tenant its own database file via EngravaManager (each has its own lock). See Known Limitations → Concurrent Write Safety.

How do I scope search to one user or session?

Retrieval is unscoped by default. In v0.5+, search_hybrid() and recall() accept filters= and visibility= so you can scope the ranked path to metadata such as a session, project, or visibility class.

Those filters are query refinements, not access control: they authenticate nothing and are bypassable by the caller. For real cross-tenant isolation, use one store per tenant via EngravaManager. The tradeoffs are laid out in the migration guide’s scoping section.

When should I enable dreaming?

Enable dreaming when memory accumulates over time and you want the store to surface and link what matters: it promotes important thoughts to P1, builds associative edges, and clusters related thoughts into REFLECTION summaries. It is not useful on a tiny or write-once store. Run it periodically (every N cycles, a cron job, or manually) — never on the hot CRUD path. For single-write batch ingest, keep allow_zero_confirmation=True or nothing will ever pass the confirmation gate.

What is a “cycle” and do I have to manage it?

A cycle is a consumer-owned monotonic logical clock — your agent’s tick. engrava never advances or persists it for you; you pass current_cycle into search and consolidation. It drives the recency signal and the dreaming age gate. On restart, recover it with await store.max_cycle(), which unions MAX(thought.updated_cycle) with MAX(edge.created_cycle) — an edge stamped at a higher cycle than any thought would otherwise under-report the mark and let the resumed counter go backwards.

Two ways to get it wrong have different effects: leaving current_cycle at its None default with no configured cycle provider and no recency_now makes the recency signal inactive — it is dropped from the ranking. Passing a constant (e.g. always 0, never advancing created_cycle/updated_cycle) keeps recency active but useless — every thought’s age collapses to the same value, so nothing looks more recent than anything else, and the dreaming age gate (min_age_cycles) never opens. Advance the cycle each turn.

How do I back up the database safely?

Because engrava uses WAL mode, a naive copy of just the .db file can miss in-flight data in the -wal file. Use a WAL-safe approach — checkpoint then copy, VACUUM INTO, or SQLite’s backup API. Note that a logical snapshot does not include the audit journal. See the OSS backup-and-recovery docs for current guidance.

What does the audit journal prove?

It proves tamper evidence for the journaled chain. The journal is a keyless in-file SHA-256 hash chain: it reliably detects accidental corruption and naive edits or truncation, but a write-capable actor who rewrites the whole file and recomputes the chain is out of its threat model. Treat it as integrity evidence with OS file permissions and periodic off-box verification, not as a cryptographic guarantee against a privileged attacker. It is a thought/edge journal, not a whole-database audit. It is off by default (journal.enabled: false). Verify the chain with store.verify_journal() or engrava verify when journaling has been enabled. See Configuration → journal and the OSS Audit Trail docs.

Is engrava production-ready?

engrava is published on PyPI and maintained to a strict quality bar (typed, linted, high test coverage). For production, the things to plan are the same as for any embedded SQLite system: pick the right vector backend for your corpus size, respect the single-writer model, set up WAL-safe backups, monitor with the Observability snapshot API, and (if you need it) enable and verify the audit journal. The OSS Known Limitations page is the honest list of constraints to design around.