Durable notes that outlive a conversation: the store behind SuperPi.Plugins.Memory.
Notes live in the memories table, not in a durable document and not in artifacts. A document
rewrites the whole corpus as one base row at every checkpoint and pins it in ETS, and artifacts
have no search and die with their conversation. A note belongs to a scope {scope_kind, scope_id}:
{"owner", principal} by default or {"global", ""}. It is not forked and not rewound, so a note
that a branch writes is visible to its parent. Read-only subagents cannot write one, because
memory_write carries the state_write effect and plan mode blocks it.
Writing
write/3 validates, then inserts or updates in one Storage.commit/1 (the index triggers run in
the same transaction). Limits and rules:
- Title and body are stored byte for byte. They must be valid UTF-8 without NUL (PostgreSQL
cannot store NUL) and the body is capped at
:max_body_bytes(32 000 by default). - An explicit
slugupserts. A slug derived from the title never overwrites: a taken one gets-2,-3. pinnedis refused for compaction provenance and for a note withsource.url, unless the caller passesallow_pin_untrusted: true. A pinned body goes into the system prompt, so anything from the open internet needs an explicit opt-in. An update takespinnedfrom the call (defaultfalse), so rewriting a pinned note unpins it.- Provenance is not model-settable:
webwhensource.urlis set, elseagent. Code passes:provenance(user,compaction,import). - The result lists similar live notes with a score, which keeps the store from becoming a junk drawer. The score is the Jaccard similarity of title and topic words, which does not depend on the search backend.
Reading and linking
search/3 is lexical (SuperPi.Memory.Search). read/2 returns a note with its links.
link/5 adds a related, derived_from or supersedes edge. supersedes marks the older note
superseded, refuses a cycle and refuses a note that already has a superseder.
Configuration (config :super_pi, :memory, [...]): :max_body_bytes, :index_token_budget,
:pinned_body_bytes, :half_life_days, :recency_weight, :pinned_bonus, :duplicate_threshold,
:search (backend module), :harvest_model, :harvest_max_facts, :harvest_llm.
Summary
Functions
A configuration value, with the module default.
{count, max updated_seq} of the scope: cheap proof that the index text is still current.
The note with slug in the scope, or nil.
Adds a rel edge from the note from_slug to to_slug. supersedes marks to as replaced by
from. Idempotent for an existing edge.
Normalizes topics to a space-separated string of lowercase words. Anything outside letters,
digits, ., _ and - becomes -. At most 8 topics of 40 characters.
Reads a note with its outgoing links, its backlinks and the slug of its superseder.
Returns {:ok, %{memory, links: [{rel, note}], backlinks: [{rel, note}], superseded_by: slug | nil}}
or {:error, :not_found}.
The scope of a conversation: its owner's notes by default, or the shared global scope.
The query of every note in a scope.
Searches the scope. See SuperPi.Memory.Search.run/3.
Derives a slug from a title: ASCII words joined by -, or a short hash when no ASCII word is left.
A short window of body around the first query word, or the start of the body. Whitespace is collapsed.
The topics of a note as a list.
Writes a note. attrs has string keys: "title", "body", and optionally "slug", "topics"
(list of strings), "pinned", "confidence" (0..1) and "source" (map with url, artifact, note).
Types
Functions
A configuration value, with the module default.
{count, max updated_seq} of the scope: cheap proof that the index text is still current.
@spec get(scope(), String.t()) :: SuperPi.Durable.Schemas.Memory.t() | nil
The note with slug in the scope, or nil.
@spec link(scope(), String.t(), String.t(), String.t(), keyword()) :: {:ok, %{ from: SuperPi.Durable.Schemas.Memory.t(), to: SuperPi.Durable.Schemas.Memory.t(), rel: String.t() }} | {:error, term()}
Adds a rel edge from the note from_slug to to_slug. supersedes marks to as replaced by
from. Idempotent for an existing edge.
Normalizes topics to a space-separated string of lowercase words. Anything outside letters,
digits, ., _ and - becomes -. At most 8 topics of 40 characters.
Reads a note with its outgoing links, its backlinks and the slug of its superseder.
Returns {:ok, %{memory, links: [{rel, note}], backlinks: [{rel, note}], superseded_by: slug | nil}}
or {:error, :not_found}.
The scope of a conversation: its owner's notes by default, or the shared global scope.
The query of every note in a scope.
Searches the scope. See SuperPi.Memory.Search.run/3.
Derives a slug from a title: ASCII words joined by -, or a short hash when no ASCII word is left.
@spec snippet(String.t(), [String.t()], pos_integer()) :: String.t()
A short window of body around the first query word, or the start of the body. Whitespace is collapsed.
The topics of a note as a list.
Writes a note. attrs has string keys: "title", "body", and optionally "slug", "topics"
(list of strings), "pinned", "confidence" (0..1) and "source" (map with url, artifact, note).
Options: :conversation_id (origin), :provenance, :allow_pin_untrusted.
Returns {:ok, %{memory: note, created: boolean, similar: [%{slug, title, score}]}} or {:error, reason}
(see format_error/1).