Quickstart: your first memory¶
You will stand up a working ZettelForge instance, store three pieces of threat intelligence about APT28 and Lazarus Group, and recall them by natural-language query and by actor name. The whole path runs locally with no cloud account and no API keys.
What you learn
- Storing CTI facts with
remember() - Recalling notes with
recall()(blended vector plus graph retrieval) - Looking up an actor directly with
recall_actor()
Prerequisites
- Python 3.10 or newer
- Git and an internet connection for the clone, Python packages, and initial embedding-model download
The tutorial runs embeddings in-process and stores your notes in a local SQLite database plus a LanceDB vector index. You do not need Docker, TypeDB, Ollama, or an API key.
Step 1: Install ZettelForge¶
Clone the repository and install it into a virtual environment.
git clone https://github.com/rolandpg/zettelforge.git
cd zettelforge
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
The virtual environment keeps ZettelForge isolated from your system packages. It also avoids the externally managed Python restriction used by current Debian and Ubuntu releases.
Confirm the install:
python -c "import zettelforge; print(zettelforge.__version__)"
You see the installed version:
2.8.0
Step 2: Keep this run offline¶
The base package enables optional LLM enrichment by default. Disable that slow path for this tutorial so ZettelForge does not try to contact an Ollama server:
export ZETTELFORGE_ENRICHMENT_ENABLED=false
Keep this shell open for the remaining steps.
Step 3: Store and recall your first memories¶
The embedding model downloads automatically the first time you construct a MemoryManager. This is a one-time download; subsequent runs load it from cache.
Create a file called quickstart.py with the following content. The CTI facts
and example.com references are synthetic tutorial data.
from zettelforge import MemoryManager
mm = MemoryManager()
note1, status1 = mm.remember(
"APT28 (Fancy Bear) deployed a modified X-Agent implant against "
"Ukrainian government networks in March 2026. The implant used "
"CVE-2025-21298 for initial access via spearphishing attachments.",
source_type="report",
source_ref="https://example.com/apt28-ukraine-2026",
domain="cti",
)
note2, status2 = mm.remember(
"Lazarus Group conducted Operation DreamJob targeting defense "
"contractors in South Korea and Japan during Q1 2026. The campaign "
"used trojanized job offer PDFs delivering the DreamJob backdoor "
"via LinkedIn messages.",
source_type="report",
source_ref="https://example.com/lazarus-dreamjob-2026",
domain="cti",
)
note3, status3 = mm.remember(
"APT28 was also observed using Cobalt Strike beacons for lateral "
"movement after initial X-Agent deployment. The beacons called back "
"to infrastructure hosted on bulletproof providers in Moldova.",
source_type="report",
source_ref="https://example.com/apt28-cobalt-strike",
domain="cti",
)
print(f"Stored: {status1}, {status2}, {status3}")
print("\nQuery recall:")
results = mm.recall("What tools does APT28 use?", domain="cti", k=5)
for note in results:
print(f"- {note.content.source_ref}")
print("\nActor recall:")
apt28_notes = mm.recall_actor("APT28", k=5)
for note in sorted(apt28_notes, key=lambda item: item.content.source_ref):
print(f"- {note.content.source_ref}")
Step 4: Run the tutorial¶
Run the file from the same shell:
python quickstart.py
The verified run produced this standard output:
Stored: created, created, created
Query recall:
- https://example.com/apt28-cobalt-strike
- https://example.com/apt28-ukraine-2026
- https://example.com/lazarus-dreamjob-2026
Actor recall:
- https://example.com/apt28-cobalt-strike
- https://example.com/apt28-ukraine-2026
remember() returns a (MemoryNote, status) tuple. The three created
statuses confirm that the write path accepted each note. recall() blends
vector and graph retrieval; the verified query ranks both APT28 notes before
the unrelated Lazarus note. recall_actor() performs a direct entity-index
lookup and returns the two notes indexed under the canonical actor name.
On the verified 2.8.0 build, startup also writes warnings to standard error for unset optional TypeDB credentials and the current FastEmbed model revision. Those warnings do not stop the SQLite tutorial.
Why the fast path matters
This tutorial disables optional LLM enrichment, but the core write path still embeds the note, writes it to SQLite and LanceDB, extracts heuristic entities, and updates heuristic graph edges. LLM causal extraction, richer entity extraction, and note evolution stay outside this success path.
Current offline lookup boundary
Use canonical actor labels such as APT28 for this tutorial. In the
verified offline run, recall_cve("CVE-2025-21298") returned its note, but
recall_actor("Fancy Bear"), recall_actor("Lazarus Group"), and
recall_tool("Cobalt Strike") returned no matches. Configure and verify LLM
enrichment before relying on alias or richer tool-name lookup. See the
Configuration reference.
What you built¶
You now have a working ZettelForge instance with:
- Three stored notes about APT28 and Lazarus Group activity, embedded and persisted to a local SQLite database and LanceDB vector index
- Entity index entries for the canonical APT28 actor, the CVE, and several other heuristically extracted entities
- Blended retrieval that combines vector similarity with graph traversal and ranks both APT28 notes first for the tutorial query
The tutorial data lives under ~/.amem by default in zettelforge.db and the
vectordb/ directory. The verified run completed the core flow without an
external service.
Next steps¶
- Synthesize answers from memory. Configure an LLM before you depend on a
model-generated answer. In the no-provider verification run,
mm.synthesize(query, format="direct_answer")returned a structured fallback withconfidenceset to0.0, not a model-generated synthesis. See the Configuration reference. - Ingest your first CTI report: chunk and store a long-form threat report with
remember_report(). - Memory Manager API reference: every public method, argument, and return shape.
- Why the hybrid TypeDB plus LanceDB architecture: the design rationale behind blended retrieval.
ZettelForge is the open-source core documented on this site. ThreatRecall.ai is the hosted SaaS built on the same engine for teams that want managed recall without running infrastructure.