Reproduce benchmarks¶
ZettelForge ships benchmark scripts you can run against a fresh install and compare to the reference numbers below. All benchmarks use the same binary you installed via pip install zettelforge; no special build is required.
These reference numbers are a snapshot from prior ZettelForge versions (v2.0.0–v2.2.0 for most suites; a v2.7.0 performance session in June 2026). Your numbers will vary with hardware, LLM provider, and embedding model.
Prerequisites¶
Install ZettelForge and clone the source repository (the benchmark scripts live in benchmarks/):
pip install zettelforge
git clone https://github.com/rolandpg/zettelforge.git
cd zettelforge
Set the deterministic-ingestion flag before running any benchmark. The flag suppresses background LLM enrichment so results are reproducible across runs:
export ZETTELFORGE_ENRICHMENT_ENABLED=false
The benchmark scripts also set this flag programmatically, but setting it in the shell avoids subtle timing differences when the environment is not inherited.
1. CTI retrieval benchmark¶
This is the primary domain benchmark. It tests the queries an analyst would actually ask: threat actor attribution, CVE linkage, tool mapping, campaign tracking, and temporal reasoning across 8 real-world-style CTI reports and 20 queries.
No external data is required. The CTI corpus is embedded in the script.
python benchmarks/cti_retrieval_benchmark.py
Reference result (v2.0.0, 2026-04-10):
| Category | Queries | Accuracy |
|---|---|---|
| Attribution | 5 | 100% |
| Multi-hop | 3 | 100% |
| CVE linkage | 4 | 75% |
| Temporal | 3 | 66.7% |
| Tool attribution | 5 | 40% |
| Overall | 20 | 75.0% |
p50 latency: 620 ms — 8 notes stored
Tool attribution scores 40% because queries that expect multiple tools ("What tools does APT28 use?") require all tool names to appear in the retrieved context. When a report mentions tools across multiple sentences, the keyword judge scores partial matches as 0.5 rather than 1.0.
CTI p50 with v2.7.0 optimizations
A June 2026 performance session on DGX Spark GB10 (keyword judge, no LLM) measured v2.7.0 at 79 ms p50 on the CTI corpus; an optimized build reached 39 ms p50. Accuracy held at 75.0% in both cases.
2. LOCOMO benchmark¶
LOCOMO (ACL 2024) is a conversational memory benchmark — not ZettelForge's design domain. It tests recall over personal dialogue: person names, hobbies, life events. ZettelForge's entity extractor is tuned for CTI entities (CVEs, APT groups, tools), so graph traversal rarely fires on conversational queries.
Requires the LOCOMO dataset at:
~/.openclaw/workspace-nexus/Locomo-Plus/data/locomo10.json
Download the locomo10.json file from the LoCoMo project and place it at that path before running.
# Quick run (20 samples per category, keyword judge)
python benchmarks/locomo_benchmark.py
# Full dataset (100 QA pairs)
python benchmarks/locomo_benchmark.py --full
# Use Ollama as judge for more accurate scoring
python benchmarks/locomo_benchmark.py --judge ollama
Reference results:
| Version | Judge | Accuracy | p50 latency |
|---|---|---|---|
| v1.3.0 | keyword | 14.0% | 238 ms |
| v1.5.0 | keyword | 15.0% | 344 ms |
| v2.0.0 | keyword | 18.0% | 1,240 ms |
| v2.1.1 | Ollama | 22.0% | — |
Why LOCOMO scores are low
Conversational entities (person names, hobbies) are not in ZettelForge's extraction vocabulary. Additionally, the supersession logic marks 264 of 272 LOCOMO sessions as superseded because sessions share speaker names. The benchmark runs with exclude_superseded=False to work around this. ZettelForge is built for CTI, not chatbot memory — the 75% CTI accuracy reflects the intended use case.
v2.7.0 baseline (keyword judge)
A June 2026 performance session on DGX Spark measured v2.7.0 at 7.0% accuracy with the keyword judge (no LLM) at 336 ms p50, and 11.0% after optimization at 170 ms p50. The 22% figure (v2.1.1) used an Ollama cloud judge — a different scoring path, not a direct comparison.
3. MemPalace comparison¶
Runs LOCOMO against MemPalace for a head-to-head comparison on the same dataset and scoring.
Requires the LOCOMO dataset (same path as above) and:
pip install chromadb mempalace
python benchmarks/mempalace_benchmark.py --samples 20
Reference result (v2.0.0 snapshot, 2026-04-10):
| System | Accuracy | p50 latency |
|---|---|---|
| MemPalace | 26% | 130 ms |
| ZettelForge | 18% | 1,240 ms |
MemPalace wins on LOCOMO because it chunks at 800 characters (MemPalace granularity) and uses pure ChromaDB vector search with no intent classification, graph traversal, or blending overhead. On conversational data without CTI entities, these extra stages add latency without accuracy.
ZettelForge wins on CTI queries where graph traversal, entity indexing, and typed relationships are load-bearing. MemPalace has no knowledge graph and cannot answer multi-hop attribution queries.
4. RAGAS retrieval quality¶
RAGAS measures retrieval quality, not answer quality. It computes keyword presence and string similarity between retrieved context and expected answers. No LLM judge is required.
Requires the LOCOMO dataset.
python benchmarks/ragas_benchmark.py
Reference result (v2.0.0, 2026-04-10):
| Metric | v1.5.0 | v2.0.0 |
|---|---|---|
| Keyword presence | 75.9% | 78.1% |
| String similarity | 17.7% | 18.2% |
The high keyword presence (78%) confirms that retrieved context contains relevant information. The LOCOMO accuracy gap is in answer extraction (the keyword judge scoring), not retrieval. ZettelForge retrieves the right passages; the local 3B model struggles to extract precise answers from them.
5. CTIBench ATE¶
CTIBench (NeurIPS 2024) tests ATT&CK Technique Extraction: given a natural-language description of an adversarial technique, retrieve the correct MITRE ATT&CK T-code.
Requires the datasets Python package:
pip install datasets
The script downloads the AI4Sec/cti-bench dataset from HuggingFace automatically on first run. The ATT&CK matrix JSON files (enterprise-attack.json, mobile-attack.json) are included in the benchmarks/ directory.
python benchmarks/ctibench_benchmark.py --task ate
Reference result (v2.2.0, 2026-04-16):
| Version | F1 |
|---|---|
| v2.0.0 | 0.000 (adapter bug: T-code regex only) |
| v2.2.0 | 0.146 (fixed ingestion + ICS noise removed) |
F1 = 0.146 is a lower bound driven by low recall: many ATT&CK techniques map to multiple paraphrases and the scoring function rewards exact T-code matches. A semantic matcher over technique descriptions would lift this further.
6. MemoryAgentBench (optional)¶
MemoryAgentBench (ICLR 2026) tests memory systems on four splits: Accurate Retrieval (AR), Conflict Resolution (CR), Test-Time Learning (TTL), and Long-Range Understanding (LRU).
Requires the datasets package and a capable LLM provider:
pip install datasets
# AR + CR splits (fast)
python benchmarks/memoryagentbench.py
# All splits (significantly longer)
python benchmarks/memoryagentbench.py --split all
Reference result (2026-04-10):
| Split | Qwen2.5-3B (local) | nemotron-3-super (Ollama) |
|---|---|---|
| Accurate Retrieval F1 | 0.012 | 0.328 |
| Conflict Resolution F1 | 0.012 | 0.032 |
| Overall F1 | 0.007 | 0.180 |
Retrieval latency was 128–333 ms for both models — the improvement is entirely in answer generation quality, not retrieval. This confirms the retrieval pipeline works; the local 3B model is the bottleneck on this benchmark.
CR scores remain low because Conflict Resolution requires multi-hop chain reasoning (e.g., "country of citizenship of the spouse of the author of...") that needs explicit graph traversal over entity relationships.
Reference numbers at a glance¶
| Benchmark | Version | Key result |
|---|---|---|
| CTI Retrieval | v2.0.0 | 75.0% accuracy, 620 ms p50 |
| LOCOMO | v2.0.0, keyword | 18.0% accuracy |
| LOCOMO | v2.1.1, Ollama | 22.0% accuracy |
| MemPalace vs ZettelForge | v2.0.0 | MemPalace 26%, ZettelForge 18% |
| RAGAS keyword presence | v2.0.0 | 78.1% |
| CTIBench ATE | v2.2.0 | F1 = 0.146 |
| MemoryAgentBench | v2.7.0 | F1 = 0.180 (cloud model) |
What affects your results¶
| Factor | Effect |
|---|---|
| LLM provider (local vs cloud judge) | LOCOMO accuracy varies widely; keyword judge is the reproducible baseline |
ZETTELFORGE_ENRICHMENT_ENABLED |
Must be false for deterministic ingestion |
| Embedding model | Results assume nomic-ai/nomic-embed-text-v1.5-Q (768-dim, ONNX) |
| Storage backend | SQLite is the default; all published results use SQLite |
| Hardware (thread count) | ONNX thread pinning affects latency; see BENCHMARK_REPORT.md section 0 for thread-tuning details |
For ThreatRecall.ai SaaS deployments, benchmark numbers will differ because the SaaS uses managed infrastructure and optional TypeDB graph backend. Contact support for SaaS-specific benchmarking guidance.