Upgrade ZettelForge¶
Use this checklist whenever you move between minor releases. For the full list of changes per release see CHANGELOG.md. The current PyPI release verified on 2026-09-09 is ZettelForge 2.8.0.
Before you start¶
You need Python 3.10 or newer, pip in your active virtual environment, shell
access to the ZettelForge host, and enough free space to copy the configured
data directory. The examples use the default data directory, ~/.amem. Replace
that path if you set storage.data_dir to another location.
Upgrade matrix¶
| From → To | Required action | Data migration? |
|---|---|---|
| 2.7.x → 2.8.0 | pip install -U zettelforge (read v2.8.0 notes below) |
No |
| 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.7.x → 2.8.0 (passive OSINT + prompt-injection guards)¶
This is a security and enrichment release. No data migration is required.
What's new¶
- Prompt-injection and retrieval-poisoning guardrails (AGE-127). The memory pipeline now treats stored memory, CTI text, query text, and retrieved context as untrusted input at the remember, recall, synthesis, extraction, indexing, note-construction, intent-classification, update, and evolution boundaries.
- Passive OSINT executor (RFC-016 Phase 1.5). Collector tuples are
validated before persistence, OSINT alias caching is scoped to each
KnowledgeGraphinstance, and organization values are canonicalized to reduce WHOIS/RDAP duplicates. - Live OSINT enrichers (AGE-120). WHOIS/DNS, username discovery,
breach lookup, and blockchain wallet transaction collectors are available
through the optional
osintextra. They are opt-in, key-gated where a provider requires credentials, fail closed, and are not triggered automatically byremember(). - Enrichment job ledger foundation (RFC-018 Phase 0). The release adds durable SQLite job metadata for enrichment queue state and regression tests for the new ledger path.
- Bulk detection-rule ingest hardening. Bulk Sigma/YARA ingest can defer
enrichment and drain once with
MemoryManager.flush().flush()now waits for in-flight enrichment work as well as queued work.
Steps¶
-
Back up your data directory if you are on a production deployment:
cp -a ~/.amem ~/.amem.pre-2.8 -
Upgrade the package:
pip install -U 'zettelforge>=2.8.0' -
If you use passive OSINT collectors, refresh the optional extra:
pip install -U 'zettelforge[osint]' -
Confirm the installed version:
python -c "from importlib.metadata import version; print(version('zettelforge'))"
Operational impact¶
- No data migration. Existing SQLite, LanceDB, and JSONL-era migrated data stay in place.
- Passive OSINT remains explicit. Network collectors do not run during
ordinary
remember()calls. Invoke the OSINT executor from your own workflow and provide provider credentials for key-gated collectors. - Stored context is treated as untrusted evidence. Keep the guard wording that ZettelForge adds around retrieved memory when you pass context to an external LLM.
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_extraction,max_tokens_ner, andmax_tokens_evolve. 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_extraction: 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.7.0'
# 2. Preserve the post-upgrade data and restore your backup
mv ~/.amem ~/.amem.failed-2.8
cp -a ~/.amem.pre-2.8 ~/.amem
The package pin does not restore your data. Moving the post-upgrade directory aside keeps it recoverable while the backup restores the matching pre-upgrade state.