Memory Manager API reference¶
MemoryManager is the main programmatic interface to ZettelForge memory. It owns
note creation, recall, the knowledge graph, and synthesis. This page documents the
public methods, their verified signatures, and return shapes.
Signatures on this page are taken from source (src/zettelforge/memory_manager.py,
ZettelForge 2.7.0). Return-value examples that depend on a live LLM are marked; see
feature state.
Getting an instance¶
from zettelforge.memory_manager import MemoryManager, get_memory_manager
# Explicit instance (tests, custom data paths)
mm = MemoryManager()
# Process-wide singleton (the usual path)
mm = get_memory_manager()
MemoryManager() initializes the SQLite storage backend, the LanceDB vector index,
and a background enrichment worker thread. The worker drains on process exit, so
short-lived scripts call flush() before they return when they need
LLM enrichment to finish.
MemoryManager(jsonl_path: str | None = None, lance_path: str | None = None)
| Argument | Default | Purpose |
|---|---|---|
jsonl_path |
None |
Override the JSONL data path. The data directory is derived from its parent. |
lance_path |
None |
Override the LanceDB index path. |
Write API¶
remember¶
remember(
content: str,
source_type: str = "conversation",
source_ref: str = "",
domain: str = "general",
evolve: bool = False,
sync: bool = False,
tlp: str | None = None,
actor: str | None = None,
) -> tuple[MemoryNote, str]
Create one memory note from content. Uses a dual-stream write path: the fast path
(embedding, storage, vector index, entity index, heuristic graph edges) returns
quickly, and LLM causal-triple extraction is deferred to the background worker.
Set evolve=True to run the two-phase fact pipeline (extract facts, then decide
ADD / UPDATE / DELETE / NOOP against existing notes). Set sync=True to run causal
extraction inline instead of in the background.
Returns (note, status) where status is one of "created", "updated",
"corrected", or "noop".
note, status = mm.remember(
"APT28 used X-Agent against the target network.",
source_type="threat_report",
domain="cti",
tlp="amber",
)
remember_chunked¶
remember_chunked(
content: str,
source_type: str = "conversation",
source_ref: str = "",
domain: str = "general",
chunk_size: int = 800,
sync: bool = False,
tlp: str | None = None,
) -> list[MemoryNote]
Split long content on sentence boundaries and store each chunk as its own note.
Content at or under chunk_size is stored as a single note. Chunked notes carry an
ordinal "{source_ref}#c{i}" suffix so provenance survives the split.
remember_with_extraction¶
remember_with_extraction(
content: str,
source_type: str = "conversation",
source_ref: str = "",
domain: str = "general",
context: str = "",
min_importance: int = 3,
max_facts: int = 5,
tlp: str | None = None,
) -> list[tuple[MemoryNote | None, str]]
Two-phase pipeline: an LLM distills content into scored candidate facts, then each
fact is compared to existing notes and an operation is chosen. Facts below
min_importance are skipped; at most max_facts are extracted. Returns a list of
(MemoryNote | None, status) tuples, where status is one of "added",
"updated", "corrected", "noop".
remember_report¶
remember_report(
content: str,
source_url: str = "",
published_date: str = "",
domain: str = "cti",
min_importance: int = 3,
max_facts: int = 10,
chunk_size: int = 3000,
) -> list[tuple[MemoryNote | None, str]]
Ingest a news or threat report. Chunks long content, runs two-phase extraction on
each chunk, and stores published_date (ISO 8601) as temporal metadata. Returns the
combined list of (MemoryNote | None, status) tuples across all chunks.
LLM prerequisite
remember_report and remember_with_extraction depend on a configured LLM
provider for their extraction phase. With no LLM available they return zero
results. Configure llm in config.yaml (see the
configuration reference) before using report ingestion.
Recall API¶
recall¶
recall(
query: str,
domain: str | None = None,
k: int = 10,
include_links: bool = True,
exclude_superseded: bool = True,
include_expired: bool = False,
caller: str | None = None,
actor: str | None = None,
max_tlp: str | None = None,
tlp_override_reason: str | None = None,
) -> list[MemoryNote]
Retrieve memories relevant to query using blended vector and graph retrieval with
cross-encoder reranking. An intent classifier sets the strategy weights per query.
If governance.limits.recall_timeout_seconds is greater than zero, the retrieval
pipeline is bounded by that wall-clock timeout; exceeding it logs a warning and
returns an empty list. This is a defense-in-depth control against deep graph
traversal denial of service.
Entity lookups¶
These bypass the scoring pipeline for a direct index lookup.
recall_entity(entity_type: str, entity_value: str, k: int = 5) -> list[MemoryNote]
recall_cve(cve_id: str, k: int = 5) -> list[MemoryNote]
recall_actor(actor_name: str, k: int = 5) -> list[MemoryNote]
recall_technique(technique_id: str, k: int = 25) -> list[MemoryNote]
recall_tool(tool_name: str, k: int = 5) -> list[MemoryNote]
recall_entity accepts these entity_type values: cve, actor, threat_actor,
intrusion_set, tool, campaign, person, location, organization, event,
activity, temporal.
recall_cvenormalizes the id to upper case.recall_actorsearchesactor,threat_actor, andintrusion_setand deduplicates. APT/UNC/FIN-style designations are usually stored asintrusion_set; older stores may still useactor.recall_techniquelooks up MITRE ATT&CK technique ids (for exampleT1059) under theattack_patternentity type.
Alias resolution needs the LLM enrichment path
Direct entity lookups match canonical values already in the index. Resolving an
alias to its canonical actor (for example "Fancy Bear" to APT28) depends on
the LLM enrichment path; without it, only exact stored values return notes.
get_context¶
get_context(query: str, domain: str | None = None, k: int = 10, token_budget: int = 4000) -> str
Return a formatted memory context string suitable for injecting into an agent
prompt. token_budget caps the size of the returned context.
Statistics and lifecycle¶
get_stats¶
get_stats() -> dict
Return memory-system statistics. The dict includes the running counters
notes_created, retrievals, entity_index_hits, consolidations_triggered, the
llm_ner_* observability counters, enrichment_failures, and evolution_failures,
plus total_notes, entity_index (the entity-index sub-stats), and
enrichment_degraded.
enrichment_degraded flips to True after the first background enrichment failure.
Watch it: an unset LLM API key makes every background enrichment call fail silently,
which leaves causal extraction, NER, and evolution inactive while remember() still
reports success.
flush¶
flush(timeout: float | None = None) -> bool
Block until queued and in-flight enrichment jobs complete. Returns True when all
jobs are done, or False if timeout is supplied and expires first. Call this in
short-lived scripts that need background enrichment to finish before exit.
evolve_note¶
evolve_note(note_id: str, sync: bool = False) -> dict | None
Trigger neighbor evolution around an existing note (manual or MCP invocation).
Returns an evolution report dict when sync=True; returns None when the job is
queued to the background worker or when note_id is not found.
mark_note_superseded¶
mark_note_superseded(note_id: str, superseded_by_id: str) -> bool
Mark one note as superseded by another and link them. Returns False if either id
does not resolve to a stored note.
snapshot¶
snapshot(actor: str | None = None, tlp_override_reason: str | None = None) -> str
Export a JSONL snapshot of memory and return its path. If the snapshot contains
AMBER or RED notes, the export raises TlpSharingError unless you supply an audited
tlp_override_reason, which is recorded in the audit log.
Knowledge graph API¶
ingest_relationship¶
ingest_relationship(
from_type: str,
from_value: str,
to_type: str,
to_value: str,
relationship: str,
properties: dict | None = None,
) -> None
Write a STIX relationship edge directly into the knowledge graph without creating a
memory note. Intended for sync clients. The edge is deduplicated by
(from_type, from_value, to_type, to_value, relationship); re-ingesting the same
triple is idempotent.
provenance_chain¶
provenance_chain(
entity_type: str,
entity_value: str,
max_depth: int = 3,
direction: str = "forward",
) -> list[dict]
Trace a causal provenance chain from an entity. direction="forward" follows
outgoing causal edges (what does X cause or enable?); direction="backward" follows
incoming causal edges (why did X happen?). Any other value raises ValueError.
Returns a list of steps, each shaped:
{"from_entity": ..., "relationship": ..., "to_entity": ..., "edge_type": ..., "note_id": ...}
get_entity_relationships¶
get_entity_relationships(entity_type: str, entity_value: str) -> list[dict]
Return the direct knowledge-graph neighbors of an entity. The entity value is alias-resolved to its canonical form before lookup.
traverse_graph¶
traverse_graph(start_type: str, start_value: str, max_depth: int = 2) -> list[dict]
Traverse relationships outward from a starting entity. In the open-source
distribution, max_depth is capped at 2; requesting a larger depth is clamped to 2
and logged. Deeper multi-hop graph traversal is available in
ThreatRecall.ai, the hosted SaaS.
Synthesis API¶
synthesize¶
synthesize(
query: str,
format: str = "direct_answer",
k: int = 10,
tier_filter: list[str] | None = None,
caller: str | None = None,
actor: str | None = None,
max_tlp: str | None = None,
tlp_override_reason: str | None = None,
) -> dict[str, Any]
Synthesize an answer from retrieved memories (retrieval-augmented answer). k sets
how many notes are pulled for context; tier_filter restricts by epistemic tier (for
example ["A", "B"]).
format="direct_answer" is the open-source format. The extended formats
"synthesized_brief", "timeline_analysis", and "relationship_map" are
ThreatRecall.ai SaaS capabilities; when requested on the open-source
distribution they fall back to "direct_answer" and the fallback is logged.
The return dict carries the synthesis result, its metadata, and the source notes:
{
"synthesis": {"answer": ..., "confidence": ..., "sources": [...]},
"metadata": {
"query_id": ..., "model_used": ..., "tokens_used": ..., "latency_ms": ...,
"confidence_threshold": ..., "sources_count": ..., "tier_filter": ...,
"max_tlp": ..., "tlp_filter": ..., "tlp_override": ...,
},
}
LLM prerequisite
synthesize needs a configured LLM provider. With none available it returns a
well-formed dict whose answer is "No specific answer found" and whose
confidence is 0.0.
validate_synthesis¶
validate_synthesis(response: dict) -> tuple[bool, list[str]]
Validate a synthesis response for quality. Returns (is_valid, errors).
check_synthesis_quality¶
check_synthesis_quality(response: dict) -> dict
Compute a quality score for a synthesis response. Returns quality metrics including a
score from 0 to 1 and a letter grade.
Feature state¶
This page is marked in_progress. Every method signature and return shape here is
verified against ZettelForge 2.7.0 source. The runnable examples that depend on a
live LLM (report ingestion, alias resolution, synthesize) are documented from
source behavior and their offline fallbacks; exact representative output captured
from a running instance with an LLM provider configured is pending a follow-up run.