Skip to content

Ingest a news report

Use MemoryManager.remember_report() to turn a report into individual facts that ZettelForge can retrieve by entity or meaning. ZettelForge 2.8.0 splits long input, asks your configured LLM to extract facts, stores each accepted fact as a note, and records a source reference for every stored note.

Provider failure in ZettelForge 2.8.0

An unreachable LLM provider can make remember_report() return an empty list without storing the original report. Always check the returned list before you treat an ingest as successful.

Prerequisites

  • You use ZettelForge 2.8.0.
  • You completed the Quickstart and verified that your LLM provider responds.
  • You have a report that you are authorized to store.

The example below uses synthetic training data. It makes no real-world attribution or current threat-intelligence claim.

1. Prepare the report

report_content = """Synthetic training report — 20 March 2026

Volt Typhoon used Cobalt Strike during a controlled exercise involving
CVE-2024-3094. This report contains fictional training data, not a real-world
attribution or current threat-intelligence assessment.
"""

Keep the full report in one string. remember_report() handles the chunking.

2. Ingest it

from zettelforge.memory_manager import MemoryManager

mm = MemoryManager()

results = mm.remember_report(
    content=report_content,
    source_url="https://example.com/synthetic-training-report",
    published_date="2026-03-20",
    domain="cti",
)

published_date becomes extraction context; it is not a separate field on the stored note. source_url becomes the base of each note's provenance string.

3. Confirm that facts were stored

if not results:
    raise RuntimeError("No facts were stored; check the LLM provider logs")

print(f"Facts processed: {len(results)}")
for note, status in results:
    if note is not None:
        print(f"{status} | {note.content.source_ref} | {note.content.raw}")

The controlled ZettelForge 2.8.0 verification run produced:

Facts processed: 1
added | https://example.com/synthetic-training-report:chunk:0:extraction:0 | Volt Typhoon used Cobalt Strike during a controlled exercise involving CVE-2024-3094.

Your fact count and wording depend on the configured model. The status tells you what ZettelForge did with each extracted fact:

Status Result
added Stored a new note.
updated Stored the new fact and superseded a similar note.
corrected Stored a correction and superseded a conflicting note.
noop Stored nothing because the fact was already represented.

The empty-result check matters because an unreachable provider produced zero facts and zero stored notes in the same v2.8.0 verification run.

4. Check the entity relationships

relationships = mm.get_entity_relationships("actor", "volt-typhoon")

for relationship in relationships:
    node = relationship["node"]
    if node["entity_type"] != "note":
        print(
            f"{relationship['relationship']} -> "
            f"{node['entity_type']}:{node['entity_value']}"
        )

The verification run produced these heuristic relationships:

USES_TOOL -> tool:cobalt-strike
EXPLOITS_CVE -> cve:cve-2024-3094

These edges show that the same extracted sentence mentions the actor, tool, and CVE. They prove co-occurrence in the stored fact; they do not independently prove the real-world attribution.

What remember_report() does

For input longer than chunk_size, ZettelForge replaces newlines with spaces and packs text at period-space boundaries. It runs the extraction and update pipeline once per chunk. Each accepted fact receives a provenance suffix in this form:

<source_url>:chunk:<chunk-number>:extraction:<fact-number>

The update phase compares each extracted fact with similar notes. It adds a new note when no safe similar note exists; otherwise, the LLM chooses added, updated, corrected, or noop behavior.

Parameters in ZettelForge 2.8.0

Parameter Default Purpose
content required Full report text.
source_url "" Provenance base; an empty value becomes report.
published_date "" Extraction context in the form Published: <value>.
domain "cti" Retrieval scope for the stored notes.
min_importance 3 Drops extracted facts below this score.
max_facts 10 Caps extracted facts per chunk.
chunk_size 3000 Sets the character target before splitting into chunks.

Start with the defaults. Lowering min_importance stores more model-selected facts, while reducing chunk_size increases the number of extraction calls.