The write pipeline

Calling remember() doesn't just dump text into a vector store. Memoria runs a seven-stage cascade that turns raw text into a structured, bi-temporally-stamped knowledge graph entry — atomically committed to per-brain storage.

Why so many stages?

Single-shot extraction ("ask the LLM for entities and edges in one prompt") works for demos but degrades fast in production. Two failure modes dominate:

  1. Entity drift — the same person appears under three slight variations of their name across episodes. Without resolution, recall fragments.
  2. Silent piling up — "Stefan works at Premex" and "Stefan works at Acme" both end up as facts, both true at retrieval time. The agent has no way to know which is current.

The pipeline solves both by treating extraction as a series of small, focused LLM calls with explicit handoffs between them.

The seven stages

1. entity-extract

Reads the episode text. Returns a list of {name, type} candidates.

[
  { "name": "Stefan", "type": "Person" },
  { "name": "Premex", "type": "Organization" }
]

2. entity-resolve (batched)

For each candidate, looks up existing entities by name embedding via approximate nearest-neighbour search. Then asks an LLM batch: "Are these the same? Provide your reasoning." Resolved candidates become references to existing entities; new candidates become new entities.

3. fact-extract

Given the resolved entities and the original text, extract relationships as triples:

{ "from": "Stefan", "relation": "worksAt", "to": "Premex", "factText": "Stefan works at Premex." }

4. fact-dedup (conditional)

If any existing edge already connects the same entity pair, ask the LLM whether the new fact is a duplicate (skip), a refinement (update), or a contradiction (continue to stage 6).

5. temporal

For each fact, set tValid (when did this become true?) and optionally tInvalid (was an end-date mentioned?). The LLM is given the surrounding text and decides — defaulting to "now" if no event time is stated.

6. contradiction (conditional)

For facts that overlap with existing edges in the entity pair, decide which is the successor. The old edge gets its tInvalid set to the new fact's tValid; the new edge takes over from there. Both stay queryable via time-travel.

7. commit

Everything from stages 1–6 is collected into a single transactional write and committed atomically:

  • New entities and edges
  • Updates to existing entities (renamed, merged, etc.)
  • tInvalid updates for superseded edges
  • The episode backref linking it to the new edges

Either all of this lands or none of it does. Partial state is impossible.

Why episodes persist before extraction

You'll notice that remember() returns immediately even though extraction takes ~1-2 seconds. That's because the episode is persisted first, and the seven-stage cascade runs after. If extraction fails (rare — LLM provider hiccups, mostly), the episode is still queryable as a recall source; only the knowledge graph misses that update for this episode.

A failed extraction can be retried. We don't currently retry automatically — failed extractions are visible in the dashboard's Episodes view, and you can replay them manually.

Observability

Each stage records its input, output, and timing. From the dashboard you can drill into any episode and see:

  • The LLM call for each stage
  • Entities created/resolved
  • Edges created and how tValid was decided
  • Contradictions detected (with the superseded edge linked)

Cost shape

A typical remember() call:

  • 7 LLM calls (one per stage; stages 2 and 4 are sometimes skipped)
  • N name embeddings (for entity resolution; cached aggressively)
  • 1 atomic batch commit
  • Total: ~1–3 s for moderate text (~500 tokens of input)

For long episodes (multi-paragraph), Memoria automatically chunks before stage 1.

Tuning

A few remember() parameters worth knowing:

ParameterDefaultWhat it does
metadata{}Free-form key-value tags attached to the episode. Useful for filtering recall later.
tValidOverridenoneSkip the temporal stage and force a specific tValid on all edges from this episode. Use when you know the date for sure.
skipExtractionfalsePersist the episode but skip the graph pipeline entirely. Used by import jobs that pre-extract elsewhere.

See Recall pipeline for what happens on the read side.