Concepts — the memory model
SyntheticMem deliberately differs from naive RAG-memory (“store text → embed → top-k → trust”). Every memory is a row that can be appealed and falsified. This is the differentiator — read it carefully.
Provenance
Every memory travels with the facts about where it came from, so recall can tell the agent how far to trust it:
| Field | Meaning |
|---|---|
source | Where the memory was attested: a file path, a URL, “user”, a command output. Its presence is what makes a memory a fact. |
learned_at | When the memory was first created. |
last_observed_at | When it was last directly observed to hold true. null means never freshly observed. |
hop_count | Compression depth. A memory derived from another (e.g. by consolidation) sits one hop below its parent. |
parent_id | The memory this one was derived from or supersedes. |
namespace | The sub-scope (e.g. a project) the fact held against — a fact about the world, not about where the agent was standing. |
hop_count > 0 and last_observed_at = null was compressed but never re-observed — recall flags it (unattributable: true) and the agent should re-verify, not trust.
Falsifiability
Every lesson can carry an invalidation tag (the kind of shift that would falsify it —
temporal, paradigm, architectural, observational) and a concrete
invalidation condition — the test that retires it.
{
"content": "Staging deploy needs PG_SSLMODE=require; the pooler refuses plain TCP.",
"source": "ops/deploy.md",
"invalidation_tag": "architectural",
"invalidation_condition": "retire if the pooler adds TLS passthrough"
}
“A rule that can’t be falsified is a religion.”
The status machine
Each memory sits in one of three states:
- fact — sourced and observed. Trustworthy, but still falsifiable (check its
invalidation_condition). - hypothesis — unsourced. A lead to verify, “do not execute from it.” Down-ranked in recall and labelled.
- retired — a tombstone: superseded or forgotten. Hidden from default recall, kept for history, reachable with
include_retired.
A sourced memory is stored as a fact; an unsourced one is demoted to hypothesis.
A hypothesis is promoted back to a fact only by a fresh observation
(memory_observe) — never because it was used N times.
Supersede, not delete
Correcting a memory never overwrites history. memory_supersede
writes a new row and retires the old one, linked both ways (superseded_by ↔ parent_id).
memory_forget tombstones with a reason. Nothing is hard-deleted
(except expired ephemeral rows — see below), so the record of what was once believed, and why it was dropped survives.
Ephemeral vs permanent
Durability depends on whether the agent is linked to a registered user (the anonymous-24h rule):
| Agent | Default | With ttl_hours |
|---|---|---|
| User-linked | Permanent (expires_at = null) | Honoured exactly — any duration. |
| Anonymous | Ephemeral — auto-expires 24h after creation | Honoured if ≤ 24h; a longer value (or no TTL) is capped to 24h with an advisory. |
Pass ttl_hours on memory_remember to opt a
scratch/working note into a shorter lifetime. Every stored and recalled row reports its durability:
expires_at, an ephemeral flag, and a human expiry (“permanent” / “expires in 2h” / “expired”).
Expired rows are hidden from recall and list unless you pass include_expired, and are hard-deleted hourly by the
prune scheduler. Register / link a user to keep memories permanently.
Trust, but verify
Recall returns content plus provenance and status, and a trust hint on every row —
facts rank before hypothesis rows, retired rows are excluded by default. The embedding is the
index, not the truth: it finds candidates by meaning, but the agent decides what to trust from the provenance.
The discipline is enforced at the API, not left to a prompt.