ANN index¶
Engram can use hnswlib for approximate cosine nearest-neighbor search over stored embeddings. it is a retrieval cache; the database remains the source of memory records.
HNSW avoids comparing every vector on each query, but its speed and recall depend on the data, settings and hardware. approximate search can miss candidates. measure it against your workload rather than assuming a fixed latency or perfect recall. when no ready ANN index is attached to the store, dense retrieval uses the database embedding pool instead.
settings¶
ann:
enabled: true
m: 32
ef_construction: 200
ef_search: 100
max_elements: 500000
index_path: ~/.local/share/engram/hnsw.index
m controls graph connectivity; ef_construction controls build effort;
ef_search controls search effort. larger values can trade memory or time for
recall, without guaranteeing a particular result.
the example shows the package default index path. init derives an index path
from the new database location, so inspect your effective ann.index_path with
config show. changing embedding models requires compatible dimensions and a
fresh index; see model changes.
initialization¶
Store.init_ann_index() first tries to load the configured index and its metadata.
if loading fails or the files are absent, it builds from the database's
non-forgotten records that have embeddings. this pool is not restricted to
status: active; normal retrieval applies eligibility rules afterward.
store.init_ann_index(background=True) # build in the background if loading fails
store.init_ann_index(background=False) # wait for a required build
web and MCP startup call this initialization path. merely constructing a Store
does not attach an index. initialization returns without an index when ANN is
disabled or hnswlib is unavailable. on a store with no embedded records, there
may be no ready or persisted index yet.
writes and forgetting¶
save_memory() updates the index only when that process has an attached, ready
index and the saved memory has an embedding. an update to an already-indexed ID
marks its previous hnswlib label deleted and adds a new label.
forget_memory() marks the database record forgotten and removes its vector
from an attached, ready index. these are updates to that process's in-memory
index, not a mechanism for synchronizing every running process. an index loaded
by another process can be stale.
persistence¶
a ready attached index is saved by store.close() or an explicit
ann_index.save(). the binary index has a companion metadata file obtained by
replacing its final suffix with .meta.json:
| index path | companion metadata |
|---|---|
memory.hnsw.index |
memory.hnsw.meta.json |
the metadata holds memory-ID/label mappings, the next label, a saved element count and a timestamp. keep both files together. a loadable cache does not by itself establish that it matches the current database or embedding model.
CLI behavior in 0.8.1¶
engram --config /absolute/path/to/config.yaml index status
engram --config /absolute/path/to/config.yaml index rebuild
index status reads the file's saved metadata, size and next label. its saved
element count is not a fresh database count and can include deleted hnswlib
labels.
despite its name, index rebuild currently calls the same load-or-build
initialization path. an existing loadable index can be reused. the CLI does not
force its replacement. reembed and import also finish through that path, so
their status messages alone do not prove an existing index was rebuilt.
for a fresh build without deleting the old cache:
- stop other processes using the store and retain the old config/index files;
- set
ann.enabled: trueand choose a newann.index_pathwhose index and companion metadata files do not exist; - run
index rebuildwith that config; - check
index status, restart clients with the same config, and verify representative queries.
for example, edit the intended config to use a new filename:
then run:
engram --config /absolute/path/to/config.yaml index rebuild
engram --config /absolute/path/to/config.yaml index status
the Python store API also exposes rebuild_ann_index() for a synchronous rebuild
of an already-attached index. it is not what the current CLI dispatch calls.
implementation¶
engram/ann_index.py
wraps hnswlib with integer-label mappings, locking around index operations,
capacity growth, persistence and cosine-distance conversion. hnswlib distances
are converted to similarity with 1 - distance.
| caller | use |
|---|---|
| retrieval dense search | ANN candidates when a ready index is attached |
| surprise scoring | nearest-neighbor comparison when available |
Store.save_memory() |
update an attached ready index |
Store.forget_memory() |
remove from an attached ready index |
| web and MCP startup | load or build the configured index |