Skip to content

Troubleshoot ZettelForge

Use this decision path when ZettelForge fails to start, reads the wrong data, returns no memories, produces a synthesis fallback, or does not connect to an MCP client. Start by capturing the running version and effective configuration; then follow the symptom that matches your failure.

Prerequisites

  • Use ZettelForge 2.8.0. Complete the Quickstart before you diagnose an existing installation.
  • Run every command in the same shell, virtual environment, container, and working directory as the failing process.
  • Keep the Configuration reference and Governance controls reference open. They distinguish parsed settings from settings that the runtime consumes.

1. Capture the version and parsed configuration

Run this snapshot without printing credentials:

python3 -m zettelforge version

python3 - <<'PY'
from zettelforge.config import get_config

cfg = get_config()
for key, value in (
    ("storage.data_dir", cfg.storage.data_dir),
    ("backend", cfg.backend),
    ("embedding.provider", cfg.embedding.provider),
    ("embedding.model", cfg.embedding.model),
    ("llm.provider", cfg.llm.provider),
    ("llm.model", cfg.llm.model),
    ("llm_ner.enabled", cfg.llm_ner.enabled),
    ("retrieval.default_k", cfg.retrieval.default_k),
    ("retrieval.similarity_threshold", cfg.retrieval.similarity_threshold),
    ("logging.level", cfg.logging.level),
):
    print(f"{key}={value}")
PY

The first line must report ZettelForge v2.8.0. Keep warnings from stderr: an env_var_not_found warning identifies an unresolved ${NAME} reference in the loaded configuration.

Treat this output as the parser's view, not proof that every setting is active. In 2.8.0, storage and structured-log paths read AMEM_DATA_DIR directly and fall back to ~/.amem. The base storage factory creates SQLite even when the parsed backend value names another backend. Use the Configuration reference for every other runtime-wiring exception.

2. Confirm the active data and diagnostic files

List the database and logs from the directory the runtime actually reads, then look for LLM and background-enrichment failures:

data_dir="${AMEM_DATA_DIR:-$HOME/.amem}"
printf 'data directory: %s\n' "$data_dir"
find "$data_dir" -maxdepth 2 -type f \( \
  -name 'zettelforge.db' -o \
  -name 'zettelforge.log*' -o \
  -name 'audit.log*' -o \
  -name 'telemetry_*.jsonl' \
\) -print
grep -E '"event": "(all_llm_backends_failed|llm_call_empty_response|enrichment_failed|llm_ner_failed|neighbor_evolution_failed)"' \
  "$data_dir/logs/zettelforge.log" | tail -n 20

No zettelforge.db means the failing process is using another data directory or has not initialized storage. No grep output means the current log contains none of those events; reproduce the failure once and run the command again. Structured events go to logs/zettelforge.log; warning-and-higher events also appear on stderr.

3. Follow the matching symptom

Import or version command fails

The python3 interpreter in this shell cannot import the same installation as the failing process. Activate that process's environment and repeat step 1. If the version is not 2.8.0, return to the Quickstart and align the installation before changing configuration.

remember() raises GovernanceViolationError

Use the exception text to identify the control that rejected the write. The 2.8.0 validator checks input type, the maximum-content limit, prompt-injection patterns, and optional PII policy. The parsed governance.enabled and governance.min_content_length fields do not disable or configure those observed checks. Apply the matching control from the Governance controls reference; do not disable governance to work around the error.

remember() succeeds but LLM-derived enrichment is absent

Compare the effective llm.provider, llm.model, and llm_ner.enabled values from step 1 with the intended configuration. Then use step 2 to find provider, empty-response, or enrichment events. A direct library call defaults to evolve=False and sync=False; set either option only when your workflow requires its additional LLM work.

recall() returns no memories

Confirm that ingestion and retrieval use the same AMEM_DATA_DIR, and confirm that zettelforge.db exists there. The 2.8.0 base runtime uses SQLite; a parsed backend value does not activate a different store. After ruling out a data-root mismatch, retry the call with exclude_superseded=False. The method defaults to excluding superseded notes. Change retrieval thresholds only after these checks; use the Retrieval policies reference to select a deliberate value.

synthesize() returns No specific answer found for: ...

Troubleshoot recall() first when the result reports zero sources. When sources exist, search the structured log for all_llm_backends_failed, llm_call_empty_response, and parse_failed with schema set to synthesis. The fallback means the provider returned no usable JSON or raised an exception; it does not prove that retrieval failed.

An MCP client cannot find ZettelForge

Test the stdio transport outside the client:

printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
  | python3 -m zettelforge.mcp \
  | python3 -c 'import json, sys; response=json.load(sys.stdin); print("jsonrpc={} tools={}".format(response["jsonrpc"], len(response["result"]["tools"])))'

ZettelForge 2.8.0 prints jsonrpc=2.0 tools=7. If this succeeds, set the MCP client command to python3 and its arguments to -m, zettelforge.mcp in the same environment. If it fails, fix the import or version problem first. A successful tools/list response verifies the stdio server and tool registry; it does not initialize storage, embeddings, or an LLM.