Upgrade ZettelForge¶
Use this checklist whenever you move between minor releases. For the full list of changes per release see CHANGELOG.md.
Upgrade matrix¶
| From → To | Required action | Data migration? |
|---|---|---|
| 2.6.x → 2.7.0 | pip install -U zettelforge (read v2.7.0 notes below) |
No |
| 2.5.x → 2.6.x | pip install -U zettelforge (read v2.6.x notes below) |
No |
| 2.4.x → 2.5.x | pip install -U zettelforge (read v2.5.x notes below) |
No |
| 2.2.x → 2.4.x | pip install -U zettelforge |
No |
| 2.1.x → 2.2.x | pip install -U zettelforge + run JSONL → SQLite migration |
Yes |
| 2.0.x → 2.2.x | Upgrade in two hops via 2.1.x is recommended but not required | Yes |
| < 2.0 | Not supported — export notes manually, fresh-install 2.2.x | — |
2.6.x → 2.7.0 (memory defense + OSINT + CrewAI)¶
This is a security and OSINT release. No data migration is required.
What's new¶
-
Write-time memory anomaly defense (SEC-011 / MemSAD). A new
MemoryAnomalyGatescores every incoming note before persistence using embedding similarity and character n-gram divergence. It ships inauditmode — anomalies are logged but writes continue. No operator action is required unless you wantblockorquarantinemode (see Enable memory defense modes below). -
OSINT layer — Phase 1 collectors (RFC-016). DNS, WHOIS, BGP, certificates, ports, breach, people, social, and tech collector contracts are now included. Available with
pip install zettelforge[osint]. -
CrewAI integration (issue #40). Exposes
ZettelForgeRecallTool,ZettelForgeRememberTool, andZettelForgeSynthesizeToolas CrewAIBaseToolsubclasses. Available withpip install zettelforge[crewai]. -
Telemetry attribution. The
callerfield is now supported in telemetry events. The previousactorfield and method arguments remain backward-compatible — existing dashboards and tests are unaffected.
Steps¶
-
Back up your data directory if you are on a production deployment:
cp -a ~/.amem ~/.amem.pre-2.7 -
Upgrade the package:
pip install -U 'zettelforge>=2.7.0' -
(Optional) Install new optional extras:
pip install 'zettelforge[osint]' # OSINT Phase 1 collectors pip install 'zettelforge[crewai]' # CrewAI integration tools -
Start ZettelForge. The memory defense is active in
auditmode immediately. Review your log formemory_anomaly_detectedevents to calibrate before switching toblock.
Enable memory defense modes¶
The memory defense requires a calibration corpus before it can reliably
gate writes. Until min_calibration_notes domain notes exist (default 50),
the gate short-circuits and all writes proceed.
Once your deployment has enough notes, set mode in config.yaml:
governance:
memory_defense:
mode: audit # log anomalies only (default)
# mode: block # reject anomalous writes with an error
# mode: quarantine # write flagged notes to a separate JSONL file
quarantine_path: "" # set a path for quarantine mode
Environment overrides:
| Variable | Effect |
|---|---|
ZETTELFORGE_MEMORY_DEFENSE_ENABLED |
Set to false to disable entirely |
ZETTELFORGE_MEMORY_DEFENSE_MODE |
audit, block, or quarantine |
ZETTELFORGE_MEMORY_DEFENSE_MIN_CALIBRATION_NOTES |
Minimum calibration corpus size (default 50) |
ZETTELFORGE_MEMORY_DEFENSE_KAPPA |
Anomaly threshold multiplier (default 2.0) |
See Governance Controls for the full schema and scoring formula.
Operational impact¶
- Audit mode (default): none. Anomalous writes are logged and proceed.
- Block mode: writes flagged as anomalous return an error to the caller. Turn on only after reviewing audit logs for false positives.
- Quarantine mode: flagged notes are written to JSONL and hidden from all recall, entity lookup, LanceDB, and graph traversal paths.
- LLM integration and performance test suites are now explicit opt-in.
Set
ZETTELFORGE_RUN_LLM_INTEGRATION=1orZETTELFORGE_RUN_PERFORMANCE_TESTS=1to run environment-sensitive tests. The default regression suite (742 passed, 13 skipped) runs without LLM credentials.
2.6.2 (config UI fix — no config changes required)¶
Released 2026-04-27. Fixes the /config web page.
- The Save Changes button was silent (called
saveConfigForm()andreloadConfig(), neither of which existed). It now submits a real form. - Enum-type config fields (
backend,llm.provider,logging.level,synthesis.default_format, etc.) render as dropdowns instead of free-text. - The YAML editor now accepts both YAML and JSON, and skips redacted
***secrets on submit.
No steps required. Upgrade with pip install -U 'zettelforge>=2.6.2'.
2.6.1 (config API and auth hotfix — no config changes required)¶
Released 2026-04-25. Fixes three blockers in the RFC-015 web GUI.
/configHTML page now renders (was raisingNameErroron_to_dictand silently delivering a blank YAML body).PUT /api/confignow reports nested restart-required fields correctly (was using top-level key comparison instead of dotted-path traversal)./configHTML route is now auth-gated withrequire_api_guard.
No steps required. Upgrade with pip install -U 'zettelforge>=2.6.1'.
2.5.x → 2.6.0 (content limits + config-driven token budgets)¶
What's new¶
- Configurable content size limits (RFC-014).
governance.limits.max_content_length(default 50 MB) prevents oversized content from exhausting memory or blocking the enrichment queue. Set to0to disable. Environment override:ZETTELFORGE_LIMITS_MAX_CONTENT_LENGTH. - Per-call-site
max_tokensbudgets moved to config.LLMConfignow exposesmax_tokens_causal,max_tokens_synthesis,max_tokens_fact,max_tokens_ner, andmax_tokens_evolution. Defaults match v2.5.2 values.
Steps¶
pip install -U 'zettelforge>=2.6.0'- Existing
config.yamloverrides forllm.timeoutcontinue to work. -
(Optional) Override per-call-site budgets:
llm: max_tokens_causal: 12000 # default 8000 max_tokens_synthesis: 4000 # default 2500 max_tokens_fact: 4000 # default 2500See LLM Budgets and Timeouts for the full table.
Operational impact¶
None if you're on v2.5.2 defaults. Budget values are unchanged; they are now configurable instead of hardcoded. If you were on v2.5.0 or v2.5.1, include the v2.5.2 steps below in the same upgrade.
2.5.x → 2.5.2 (recommended for reasoning-model deployments)¶
Read this first if you run ZettelForge against qwen3 (any variant),
nemotron, or any other reasoning model that emits <think>...</think>
tokens. Pre-2.5.2 deployments with these models were silently failing every
causal-extraction, synthesis, and LLM-NER call — the per-call-site
max_tokens budgets were too small for the reasoning phase.
What changed¶
- Per-call-site
max_tokensbumped for reasoning-model headroom: causal extraction 300 → 8000, synthesis 800 → 2500, fact extraction 400 → 2500, LLM NER 300 → 2500, memory evolution 1024 → 2500. llm.timeoutdefault bumped from 60 s to 180 s.
Operational impact¶
- Causal extraction now takes 60–140 s per call on a 9B-Q4_K_M reasoning
model.
remember(sync=True)blocks 1–3 minutes per note. The default async path is unaffected. llm_call_empty_responsewarnings stop appearing in your OCSF log. See LLM Budgets and Timeouts if they persist.
Steps¶
pip install -U 'zettelforge>=2.5.2'- If your
config.yamlexplicitly setsllm.timeout: 60.0, raise it to180.0or remove the override.
2.4.x → 2.5.0 (local backend selection + LiteLLM)¶
No data migration required.
- Local LLM backend selection (RFC-011).
provider: localnow supportslocal_backend: llama-cpp-python(default, no change) orlocal_backend: onnxruntime-genaifor ONNX models. Extra:pip install zettelforge[local-onnx]. - LiteLLM unified provider (RFC-012).
provider: litellmroutes to 100+ LLM providers. Extra:pip install zettelforge[litellm].
Steps¶
pip install -U 'zettelforge>=2.5.0'- (Optional) Install new extras:
pip install 'zettelforge[local-onnx]' # ONNX local inference pip install 'zettelforge[litellm]' # LiteLLM cloud routing pip install 'zettelforge[local-all]' # both local backends - Existing
config.yamlworks unchanged if you are on the defaultprovider: localwithllama-cpp-python.
2.1.x → 2.2.x (SQLite migration — data migration required)¶
The default storage backend changed from JSONL files to SQLite. Notes, the
knowledge graph, and the entity index now live in
<data_dir>/zettelforge.db instead of loose JSONL files.
Steps¶
pip install -U 'zettelforge>=2.2.0'- Back up your data directory:
cp -a ~/.amem ~/.amem.pre-2.2 - Run the migration — see Migrate JSONL to SQLite.
- Confirm with
mm.get_stats()thattotal_notesmatches the migration log.
What else changed in v2.2.0¶
- Causal chain retrieval now works for
why did X happen?queries.edge_type="causal"is now filterable if downstream code iterates edges. - Memory evolution is on by default once the store has ≥ 3 notes.
Pass
evolve=Falseto opt out. - STIX alignment. APT/UNC/TA/FIN groups now store as
intrusion_set.recall_actor()remains backward-compatible and searchesactor,threat_actor, andintrusion_set.
Rollback¶
All of ZettelForge's state lives in the configured data directory plus the installed package. To roll back:
# 1. Pin the previous version
pip install 'zettelforge==2.6.2'
# 2. Restore the data directory backup
rm -rf ~/.amem
cp -a ~/.amem.pre-2.7 ~/.amem
The SQLite database file (zettelforge.db) is harmless if left in place —
older versions do not read it, and deleting it does not affect JSONL data.