Facts: what is held true
Claims tell you what each source said. An agent usually needs something else: what is true now, how sure the memory is, and what changed. Five meeting notes that each say Ravi works on the billing migration should be one piece of knowledge with five sources behind it, not five search hits. A retro that says he moved to the search team in June should close that knowledge, not sit next to it as an equal.
A fact is that piece of knowledge. RememberStack builds facts from claims, attaches every supporting claim as evidence, and revises a fact when new evidence arrives. Claims never change; facts do.
Two kinds of fact
Relations
A relation connects two entities with a predicate:
Ravi works_on billing migration
Dana reports_to Head of Product
Relations are the edges of the knowledge graph. You can traverse them
(graph_neighborhood, graph_path), and facts_context expands through
them from the entities you ask about. See Retrieval.
Observations
An observation is a statement about one entity:
Billing migration: the cutover is scheduled for 2026-06-08.
Dana: Dana believes the June date is too tight.
Observations carry values, properties and anything that does not fit a two-entity predicate: numbers, descriptions, states, stances. They are anchored on their entity and searchable, but they are not graph edges.
An attributed claim ("Dana believes…", "Ravi said…") always becomes an observation on the person who holds the view, never a fact about the thing they talked about.
Both kinds share one shape in results: a FactResult with kind set to
relation or observation, a readable label, a validity window and an
evidence_count. See Reading a result.
Predicates
A relation's predicate comes from a governed vocabulary, not free text. Free-text predicates grow into hundreds of near-synonyms ("works at", "employed by", "is on the team of") that no query can gather. A fixed vocabulary keeps the graph queryable.
The core vocabulary has 16 predicates:
related_to, works_for, member_of, affiliated_with, founded,
located_in, part_of, authored, created, about, knows_about,
knows, participated_in, works_on, uses, reports_to.
The normalizer maps synonyms onto these ("employed by" becomes works_for).
A relational fact that fits none of them may use an escape value,
other:<short_snake_case>, for example other:sponsors. The value must
match other: followed by a lower-case letter and 1 to 40 lower-case
letters, digits or underscores. Any other unknown predicate is dropped; the
claim still holds the statement.
Extension packs add predicates for a domain. The engine ships one, work,
with blocks, depends_on, concerns, decided_by, assigned_to and
pursues. Packs are installed at the engine level; there is no HTTP route or
CLI command to enable one yet.
How a claim changes the facts
After claims are extracted, each claim is normalized into zero or more assertions (relations and observations), its entities are resolved (see Entities), and each assertion is then adjudicated against the existing facts about the same entity. The adjudicator sees the incoming assertion, the candidate facts, their evidence and their dates, and makes one decision:
| Decision | What happens | Recorded outcome |
|---|---|---|
| Add | The assertion is a new proposition. A new fact is created with it as evidence. | add |
| Confirm | The assertion repeats an existing fact. The claim is attached as supporting evidence; nothing else changes. | noop |
| Adjust | The assertion is the same fact with better dates. The fact's world-time window is replaced, and the decision names the claims that justify the new dates. | update |
| Supersede | The assertion is a successor to an existing fact ("moved to the search team"). The earlier fact's window is closed at the successor's start in the world, never at the time the source spoke. | update on the earlier fact |
| Contradict | The assertion conflicts with an existing fact. Either the claim attaches to that fact as evidence with stance contradicts, or the assertion becomes its own fact and the two are linked in a contradiction group. | contradict for facts placed in a group |
Every decision is written to the fact's decision transcript with its
confidence, method and rationale. You can read it with
transcript_relation (see Evidence).
Rules that keep this safe:
- Low confidence never merges. If the adjudicator's confidence is below the floor (0.75 by default), the assertion becomes a separate new fact instead of being merged into an existing one. A wrong split is visible and cheap to live with; a wrong merge silently destroys a fact.
- No candidates, no model call. When the entity has no facts yet, the assertion becomes a new fact directly.
- Distinct events stay distinct. Two wins of the same tournament in different years are two facts. Equal wording or equal dates do not make two assertions the same fact, and different dates do not make them different.
- Corrections move dates, not statements. A correction from 5 November to 6 November keeps the fact and changes its window.
Two adjudication engines
Self-hosted deployments choose how this decision is made with
REMEMBERSTACK_FACT_ADJUDICATION_ENGINE:
prompt(the default) asks a chat model for the decision as a structured answer.jevanswers the same questions with a decision model (TypeSafe AI's System One) that picks from fixed choices (match, stance, window action) instead of writing free text. It needs a TypeSafe API key.
Both engines go through the same validation, the same confidence floor and the same writer. See Models and providers.
Evidence count
evidence_count is the number of distinct documents whose current
testimony supports the fact.
It does not count claims, versions or repetitions. Five claims from one transcript count once. Ten versions of one spec count once. A new extractor re-reading the same file does not add support. Two different documents saying the same thing count twice.
Claims attached with stance contradicts are counted separately and
reported per stance in evidence_totals (see
Reading a result).
Support withdrawn
A fact's support is normally current. It becomes withdrawn when every
source that asserted it has stopped doing so for a processing reason: a new
extractor version failed to derive the claim again from a file that did not
change. RememberStack cannot tell by itself whether the old reading was
wrong or the new extractor regressed, so it does not delete the fact. It
flags it.
A withdrawn fact is still returned, with support: "withdrawn", so an agent
sees that the ground moved before it relies on it.
This is different from a source removing content. When a living source
drops the text that was a fact's only support, or a document is deleted, the
fact is closed and the closure is recorded. See
Updating a source.
Labels
Every fact has a readable label. A relation's label is built without a
model, from its entity names and predicate ("Ravi works on billing
migration"). An observation's label is its statement. Labels hold only the
statement; the dates live in the validity fields, never in the text.
Where to go next
- Time: the window every fact carries and how to query it.
- Contradictions: what happens when facts disagree.
- Entities: what a fact is about.
- Assured operations:
facts_context.