Common symptoms, their cause, and the fix. Each entry shows the error (or the surprising behaviour) you actually see, then what to change.
If your problem is a platform constraint rather than a mistake (macOS extension loading, the ~100k brute-force ceiling, FTS5 availability), see Known Limitations instead.
Symptom. Writes succeed, but the first get_thought / search call raises:
AttributeError: 'tuple' object has no attribute 'keys'
Cause. The aiosqlite connection has no row factory, so rows come back as
plain tuples. Engrava maps rows to records by column name and needs
aiosqlite.Row. The failure surfaces on read, not on connect or write,
which makes it look unrelated to setup.
Fix. Set the row factory immediately after connecting:
import aiosqlite
conn = await aiosqlite.connect("engrava.db")
conn.row_factory = aiosqlite.Row # requiredSqliteEngravaCore.from_config(...) opens the connection for you and sets this
correctly — the manual snippet above only applies when you construct the store
from your own connection.
Symptom.
ValueError: 'INSIGHT' is not a valid ThoughtType
Cause. A string was passed that is not a member of the enum. The valid
ThoughtType members are TASK, OBSERVATION, BELIEF, REFLECTION,
OUTPUT_DRAFT, and NOTE — there is no INSIGHT. The same applies to
Priority (P1–P4), EdgeType, LifecycleStatus, etc.
Fix. Use a real enum member, ideally the symbol rather than a string literal:
from engrava import ThoughtType
ThoughtType.BELIEF # preferred
ThoughtType("BELIEF") # also valid — must match a real memberSee Core Concepts for the full taxonomy and when to use each type.
Symptom. search_hybrid / search_fts returns an empty or short result
list even though matching thoughts exist.
Cause. A signal you assumed was active was silently skipped, so the query ran on fewer signals than you expected. Engrava skips a signal rather than erroring when its prerequisite is missing. Work through this checklist:
| If… | then… |
|---|---|
No embedding_provider is configured |
the vector signal is skipped — only FTS/priority run. A purely semantic query with no shared keywords may find nothing. |
You pass query_text but no provider and no query_vector |
same as above — there is no vector to compare against. |
current_cycle is None |
the recency signal is skipped (it cannot compute an age). |
recency_weight is 0.0 |
recency is disabled even if current_cycle is set. |
| The query shares no FTS tokens with any thought | FTS legitimately returns nothing — this is a real miss, not a bug. |
Inspect which signals actually ran via HybridSearchResult.backends_used:
result = await store.search_hybrid("python async", top_k=5, current_cycle=10)
print(sorted(result.backends_used)) # e.g. ['fts5', 'priority', 'recency']If 'vector' is missing and you expected semantic matching, configure an
embedding provider (see the Embeddings guide). If
'recency' is missing, pass a non-None current_cycle and a
recency_weight > 0.
Symptom. run_consolidation(...) returns promoted_count == 0 every time.
Cause. Promotion requires a candidate to clear two independent bars, and either one alone keeps the count at zero:
- The age gate. A thought is eligible only when
current_cycle - created_cycle >= min_age_cycles(default1). If you never advance your cycle counter — every thought stays at the samecurrent_cycleyou created it in —0 >= 1is false and nothing is ever eligible. This is the most common cause. See Core Concepts → Cycle. - The promotion threshold. Even after the gate passes, a candidate's
weighted signal score must reach
promote_threshold. Brand-new, unconfirmed, never-accessed thoughts score low, so a high threshold promotes nothing.
Fix.
from engrava.config import DreamingConfig, DreamingGates
from engrava.extensions.dreaming import DreamingExtension
config = DreamingConfig(
enabled=True,
promote_threshold=0.4, # lower it if nothing clears the bar
gates=DreamingGates(
allow_zero_confirmation=True, # essential for single-write ingest
min_age_cycles=1,
),
)
ext = DreamingExtension(config=config)
# Advance current_cycle past the thoughts' created_cycle so the age gate passes:
result = await ext.run_consolidation(store, current_cycle=10)
print(result.promoted_count)See Dreaming for the full gate-and-signal model.
Symptom. A store that worked before now raises EmbeddingModelMismatchError
on startup or first embed.
Cause. Engrava records the embedding model name and dimension in the database the first time it embeds. If you later open that same database with a different model name or a different dimension, the stored vectors are incompatible with new ones, so it refuses rather than silently mixing dimensions (which would corrupt similarity results).
Fix. Use the same embedding model the database was created with, or re-embed the corpus under the new model. The CLI does this safely:
engrava restore --re-embed # validates model consistency, re-embedsSee Known Limitations → Embedding Dimension Consistency.
Symptom. Creating an edge to a thought that doesn't exist raises:
referential integrity violation: edge.to_thought_id='...' does not reference an existing thought
…and the obvious import fails:
from engrava import ReferentialIntegrityError # ImportError!Cause (two parts).
- The error itself means one endpoint of an edge (
from_thought_idorto_thought_id) is not a real thought id. Create both thoughts before the edge that links them. - The import:
ReferentialIntegrityErroris not re-exported from the top-levelengravapackage. It lives inengrava.domain.exceptions.
Fix. Import it from its real module, and ensure both endpoints exist first:
from engrava.domain.exceptions import ReferentialIntegrityError
try:
await store.create_edge(edge)
except ReferentialIntegrityError:
... # one endpoint is missing — create the thought, then retryThe exceptions that are re-exported at the top level are EngravaError (the
base), ConfigError, EmbeddingModelMismatchError, ExtensionMigrationError,
InvalidTransitionError, MindQLParseError, ReadOnlyViolationError,
StaleDataError, and ThoughtNotFoundError. Anything else lives under
engrava.domain.exceptions.
- Re-read the relevant guide: Core Concepts, Search, Embeddings, Dreaming.
- Check the FAQ for "is this supposed to work this way?" questions.
- Confirm it isn't a documented constraint in Known Limitations.
- Open an issue with a minimal reproduction.