Configure LanceDB vector search¶
Use LanceDB settings when you need to verify the embedding model, move the local vector index, tune active reranking controls, or plan a safe embedding-model migration.
As verified against the official ZettelForge 2.8.0 wheel and release source on August 22, 2026, the default self-hosted path stores structured data in SQLite and semantic vectors in LanceDB under {data_dir}/vectordb. A clean runtime test stored two CTI notes, created the notes_cti LanceDB table, and recalled both notes.
Retrieval tuning is partially in progress
retrieval.rerank_* and retrieval.entity_max_fanout are active runtime controls. retrieval.default_k, retrieval.similarity_threshold, retrieval.entity_boost, and retrieval.max_graph_depth load into config, but the verified MemoryManager path still uses method or constructor defaults for those values. Pass k directly to recall() today, and treat the other three fields as in progress until the runtime wiring lands.
Prerequisites¶
- ZettelForge installed, or a local ZettelForge source checkout.
- A local data directory. By default this is
~/.amem; setAMEM_DATA_DIRto use a different directory. - Stored notes to test recall against. If you need a minimal note first, see Store a threat actor.
Steps¶
1. Confirm the active embedding defaults¶
The default embedding provider is fastembed, using the in-process ONNX model nomic-ai/nomic-embed-text-v1.5-Q with 768 dimensions.
embedding:
provider: fastembed
model: nomic-ai/nomic-embed-text-v1.5-Q
dimensions: 768
threads: 8
Environment variables override the provider, model, URL, and data directory:
export ZETTELFORGE_EMBEDDING_PROVIDER=fastembed
export AMEM_EMBEDDING_MODEL=nomic-ai/nomic-embed-text-v1.5-Q
export AMEM_EMBEDDING_URL=http://127.0.0.1:11434
export AMEM_DATA_DIR=/data/zettelforge
AMEM_EMBEDDING_URL is used when embedding.provider is ollama. The default fastembed provider runs locally and does not call an embedding server.
Verify the embedding output before you rebuild or ingest production notes:
from zettelforge.vector_memory import get_embedding
vector = get_embedding("APT28 uses Cobalt Strike for command and control")
print(f"Embedding dimensions: {len(vector)}")
Verified output:
Embedding dimensions: 768
Before you change embedding.model or embedding.dimensions on an existing store, read Treat SQLite-native vector rebuilds as in progress. Existing vectors are not comparable to vectors from a different model or dimensionality, and ZettelForge 2.8.0 does not ship a working rebuild path for its default SQLite store.
2. Place the vector index with AMEM_DATA_DIR¶
ZettelForge writes the LanceDB vector index under the active data directory:
{data_dir}/
zettelforge.db
vectordb/
The verified default is ~/.amem. This override moves both SQLite and LanceDB data for the current process:
export AMEM_DATA_DIR=/data/zettelforge
In a runtime smoke test with AMEM_DATA_DIR set to a temporary directory, ZettelForge created zettelforge.db, created vectordb/, created a notes_cti LanceDB table after storing a CTI note, and recalled that note through MemoryManager.recall().
3. Tune the active reranking controls¶
Cross-encoder reranking runs after the initial vector and graph retrieval blend. These fields are read by MemoryManager.recall() at runtime:
retrieval:
rerank_enabled: true
rerank_max_candidates: 8
rerank_doc_chars: 256
rerank_model: Xenova/ms-marco-MiniLM-L-6-v2
rerank_threads: 8
entity_max_fanout: 25
Use these controls when recall returns the right candidate notes but the best note is not ranked first:
| Key | Default | Effect |
|---|---|---|
rerank_enabled |
true |
Enables the cross-encoder pass. Set false to keep the blended order and avoid reranker inference cost. |
rerank_max_candidates |
8 |
Limits how many head candidates the reranker scores. |
rerank_doc_chars |
256 |
Limits how much note text is passed to the reranker. |
rerank_model |
Xenova/ms-marco-MiniLM-L-6-v2 |
Selects the cross-encoder model. |
rerank_threads |
8 |
Sets ONNX threads for reranking. 0 uses the runtime default. |
entity_max_fanout |
25 |
Drops query entities that map to too many notes before graph and entity augmentation. |
You can disable reranking for a process without editing config.yaml:
export ZETTELFORGE_RERANK_ENABLED=false
4. Treat non-wired retrieval fields as in progress¶
These fields exist in config.yaml and load into get_config().retrieval, but the verified MemoryManager path does not fully consume customized values yet:
retrieval:
default_k: 10
similarity_threshold: 0.25
entity_boost: 2.5
max_graph_depth: 2
Current verified behavior:
| Field | Current runtime behavior |
|---|---|
default_k |
MemoryManager.recall() still has a method default of k=10. Pass k directly when you call recall(). |
similarity_threshold |
VectorRetriever() still initializes with constructor default 0.15 in the verified path. |
entity_boost |
VectorRetriever() still initializes with constructor default 2.5 in the verified path. |
max_graph_depth |
MemoryManager still calls graph retrieval with source-level depth 2. |
For high precision today, pass a smaller k and ask a narrower query:
from zettelforge.memory_manager import MemoryManager
mm = MemoryManager()
notes = mm.recall("APT28 Cobalt Strike C2", domain="cti", k=5)
print(f"Results: {len(notes)}")
For high recall today, pass a larger k:
from zettelforge.memory_manager import MemoryManager
mm = MemoryManager()
notes = mm.recall("APT28 Cobalt Strike C2", domain="cti", k=25)
print(f"Results: {len(notes)}")
5. Configure LanceDB cleanup¶
ZettelForge creates a LanceDB version-cleanup helper when MemoryStore initializes and starts it on the first vector write. It reads these fields:
lance:
cleanup_interval_minutes: 60
cleanup_older_than_seconds: 3600
| Key | Default | Effect |
|---|---|---|
cleanup_interval_minutes |
60 |
Interval between cleanup passes. Set 0 to disable cleanup. |
cleanup_older_than_seconds |
3600 |
Versions older than this are eligible for cleanup. |
For one-shot compaction, inspect first:
DATA_DIR="${AMEM_DATA_DIR:-$HOME/.amem}"
python -m zettelforge.scripts.compact_lance --data-dir "$DATA_DIR" --all --dry-run
Then optimize after you quiesce writers:
DATA_DIR="${AMEM_DATA_DIR:-$HOME/.amem}"
python -m zettelforge.scripts.compact_lance \
--data-dir "$DATA_DIR" --all --mode optimize --force
The --force flag is required for a mutating run. The script exits with 0 on success, 1 if a table reports an error, and 2 for argument or environment failures.
Default compact mode is in progress
In the standard ZettelForge 2.8.0 wheel, the script's default compact mode exits with code 1 because compact_files() requests the undeclared pylance library. The optimize command above completed against the same standard installation. Use the verified mode until the package declares and tests the missing dependency or changes its default.
6. Treat SQLite-native vector rebuilds as in progress¶
Do not run scripts/rebuild_index.py against the default SQLite store and assume it rebuilt your vectors. In ZettelForge 2.8.0, that source-checkout helper reads notes.jsonl; it does not read zettelforge.db.
The verification store contained two SQLite notes and a two-row notes_cti table but no notes.jsonl. Both the default helper invocation and its explicit --jsonl form exited with code 0 after reporting Found 0 notes in JSONL. Neither invocation rebuilt the SQLite-backed notes.
An automated SQLite-native rebuild is in progress. Until it ships, keep the existing data directory intact, create a separate AMEM_DATA_DIR for the new embedding model, re-ingest from your original source material, validate recall there, and only then switch your deployment to the new directory. Do not delete the old vector index as a rebuild shortcut.