Skip to content

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 MemoryAnomalyGate scores every incoming note before persistence using embedding similarity and character n-gram divergence. It ships in audit mode — anomalies are logged but writes continue. No operator action is required unless you want block or quarantine mode (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, and ZettelForgeSynthesizeTool as CrewAI BaseTool subclasses. Available with pip install zettelforge[crewai].

  • Telemetry attribution. The caller field is now supported in telemetry events. The previous actor field and method arguments remain backward-compatible — existing dashboards and tests are unaffected.

Steps

  1. Back up your data directory if you are on a production deployment:

    cp -a ~/.amem ~/.amem.pre-2.7
    
  2. Upgrade the package:

    pip install -U 'zettelforge>=2.7.0'
    
  3. (Optional) Install new optional extras:

    pip install 'zettelforge[osint]'    # OSINT Phase 1 collectors
    pip install 'zettelforge[crewai]'   # CrewAI integration tools
    
  4. Start ZettelForge. The memory defense is active in audit mode immediately. Review your log for memory_anomaly_detected events to calibrate before switching to block.

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=1 or ZETTELFORGE_RUN_PERFORMANCE_TESTS=1 to 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() and reloadConfig(), 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.

  • /config HTML page now renders (was raising NameError on _to_dict and silently delivering a blank YAML body).
  • PUT /api/config now reports nested restart-required fields correctly (was using top-level key comparison instead of dotted-path traversal).
  • /config HTML route is now auth-gated with require_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 to 0 to disable. Environment override: ZETTELFORGE_LIMITS_MAX_CONTENT_LENGTH.
  • Per-call-site max_tokens budgets moved to config. LLMConfig now exposes max_tokens_causal, max_tokens_synthesis, max_tokens_fact, max_tokens_ner, and max_tokens_evolution. Defaults match v2.5.2 values.

Steps

  1. pip install -U 'zettelforge>=2.6.0'
  2. Existing config.yaml overrides for llm.timeout continue to work.
  3. (Optional) Override per-call-site budgets:

    llm:
      max_tokens_causal: 12000     # default 8000
      max_tokens_synthesis: 4000   # default 2500
      max_tokens_fact: 4000        # default 2500
    

    See 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.


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_tokens bumped for reasoning-model headroom: causal extraction 300 → 8000, synthesis 800 → 2500, fact extraction 400 → 2500, LLM NER 300 → 2500, memory evolution 1024 → 2500.
  • llm.timeout default 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_response warnings stop appearing in your OCSF log. See LLM Budgets and Timeouts if they persist.

Steps

  1. pip install -U 'zettelforge>=2.5.2'
  2. If your config.yaml explicitly sets llm.timeout: 60.0, raise it to 180.0 or remove the override.

2.4.x → 2.5.0 (local backend selection + LiteLLM)

No data migration required.

  • Local LLM backend selection (RFC-011). provider: local now supports local_backend: llama-cpp-python (default, no change) or local_backend: onnxruntime-genai for ONNX models. Extra: pip install zettelforge[local-onnx].
  • LiteLLM unified provider (RFC-012). provider: litellm routes to 100+ LLM providers. Extra: pip install zettelforge[litellm].

Steps

  1. pip install -U 'zettelforge>=2.5.0'
  2. (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
    
  3. Existing config.yaml works unchanged if you are on the default provider: local with llama-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

  1. pip install -U 'zettelforge>=2.2.0'
  2. Back up your data directory:
    cp -a ~/.amem ~/.amem.pre-2.2
    
  3. Run the migration — see Migrate JSONL to SQLite.
  4. Confirm with mm.get_stats() that total_notes matches 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=False to opt out.
  • STIX alignment. APT/UNC/TA/FIN groups now store as intrusion_set. recall_actor() remains backward-compatible and searches actor, threat_actor, and intrusion_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.