Knowledge · Memory · v0.3.0

AbstractMemory

AbstractMemory is a Python library for durable, append-only agent memory. Layer 1 stores facts as temporal triples (subject, predicate, object), each with its time and provenance: where it came from. Layer 2, the MemorySystem, combines those triples with an append-only journal into a usage-weighted memory graph: it forms records, reconstructs working memory from a cue, and strengthens only what you actually use. Storage never decays and nothing is deleted. It is the memory engine of the framework's entities.

python -m pip install abstractmemory
python -m pip install "abstractmemory[lancedb]"   # LanceDB backend

Two layers

Reads are pure: rendering a memory does not strengthen it; only committed use does. AbstractMemory has no dependency on AbstractCore or AbstractRuntime, and SQLite persistence and vector scoring use only the standard library.

Layer 1: triple truth

Append-only, temporal, provenance-aware triple assertions with deterministic structured queries and optional vector retrieval, over in-memory, SQLite or LanceDB stores.

TripleAssertionTripleQueryTripleStore

Layer 2: the memory system

A MemorySystem over a store and a journal: typed record formation, stimulus-driven reconstruction, attention and decay, identity, valence, diary conventions, sleep and consolidation, and a replay stream.

MemorySystemSQLiteJournalStimulus

Embeddings you choose

OpenAICompatTextEmbedder for any OpenAI-compatible /embeddings endpoint, AbstractGatewayTextEmbedder for an AbstractGateway deployment, or your own TextEmbedder. Keep one embedding model per store.

optionalvector search
AbstractMemory comes with the one-line AbstractFramework installer: AbstractGateway installs it with the LanceDB backend. It is the memory engine of the framework's long-lived entities, and AbstractRuntime's MEMORY_KG_* effects (the assert, query and resolve requests a workflow makes against its knowledge graph) run on its triple stores.

How an entity remembers: reconstructed, not retrieved

For an entity, each turn is a cue that rebuilds a working memory; its memory tools then read more. The calls behind it are on this page.

Dynamic memory reconstruction A cue made of the request, the people present and the date and time of day enters reconstruct. It returns a working memory made of three parts: the core identity, what was just in use, and what the cue calls up. The entity can then read more with its memory tools, which feed the working memory directly. The reconstruction is recorded as a step in the run's ledger and as a trace in the temporal memory graph. The moment (cue) the request, its words who is present date and time of day the machine Stimulus(cue_text, participants, …) reconstruct pure read · RECALL step Core identity values, purposes, traits (self) Short-term memory what was just in use (stm) What the request needs what the cue calls up (stimulus) Working memory for this answer Memory tools active: the entity reads more (search_memory, read_memory, recent_memories, feelings_about) Durable twice: a MEMORY_RECALL step in the run's ledger, and a journaled trace in the temporal memory graph (replayable as of that moment)
CallWhat it does
reconstruct(Stimulus(...), scopes=...)Pure read that returns the working set: self (identity), stm (just in use) and stimulus / both (what the cue matched), with an as_of_seq replay anchor
commit_selection(trace_id, used_ids)The only strengthening path: marks what actually entered the context
probe(...), probe_expand(...), familiarity(...)A deliberate reach with a stated reason; tells "I hold nothing here" from "I hold a lot"
situate(...)Rebuilds the context of one past moment; pure read, results labeled historical
recall_history(...), explain_recall(...)A record's part in recent reconstructions, and why it did or did not surface in one of them
Related work. Letta (formerly MemGPT) also builds stateful agents: an agent keeps its own memory and message history across sessions, and background subagents review recent conversations to consolidate lessons into memory. AbstractEntity and AbstractMemory take these choices: an entity's identity is planted once from its starting document and never rewritten; night consolidation is deterministic, calls no model and only proposes; facts live in a temporal graph with validity windows and provenance; beyond the identity core, working memory is reconstructed for each moment from the cue; and every reconstruction is recorded as a step in the run's ledger.

Every fact knows when, and where from

A fact is a TripleAssertion: subject, predicate and object, with the time it was observed, the window in which it holds, a confidence and its provenance. Beliefs change by adding records, never by editing or deleting old ones.

Validity windows

observed_at says when the fact was seen; valid_from and valid_until bound when it holds. A query with active_at returns what was true at that moment, and since / until filter by observation time.

observed_atvalid_fromvalid_untilactive_at

Provenance

Each assertion carries a free-form provenance record (for example the span or turn it came from) and an optional confidence; journal events record who acted: the runtime, an operator or the system.

provenanceconfidenceactor

Revision without deletion

A belief is revised by an append-only closure, retract or supersede. The journal gives every record a monotonic seq, so a recall can be re-run as of an earlier moment and returns the same result while the stored facts are unchanged.

retractsupersedeseqas_of
How an entity lives on it. Every turn of a visit is formed into an episode, and every answer starts with a recall over the entity's own scopes. At the end of a visit the entity keeps the lessons, feelings and interests it chooses; at night, sleep consolidates and proposes without calling a model. Its founding identity (values, purposes, traits) stays fixed. See AbstractEntity.

The memory system

Working memory is not stored; it emerges from use. Every journal record gets a monotonic seq, so a recall can be reproduced as_of an earlier point (exactly, while the stored facts are unchanged). The memory system guide explains the model in depth.

Formation

Typed records

remember and remember_many form typed records (episodes, diary entries and more), idempotent by key. Forming a memory is not using it: formation leaves attention untouched.

Recall

Reconstruction

reconstruct(Stimulus(...)) is a pure read: exact, keyword/full-text and vector channels find matches, activation spreads to related memories over recorded links, and the result is a bounded shelf (by default 12 memories within a token budget). commit_selection is the only path that strengthens a memory.

Attention

Attention and decay

Activation over rank distance, with strengthen, weaken and refocus. Only retrieval strength changes; the stored memory never decays.

Identity

Identity and valence

Identity cores for long-lived entities (value, purpose, trait, interest) that order ahead of other records, and signed appraisals on two channels, so mixed feelings are kept rather than summed away.

Diary

Diary conventions

Notes, ideas, commitments, reflections, questions and problems. Open questions and problems stand until a later entry answers or resolves them, and entries can carry a verifiable content-hash chain.

Sleep

Sleep and consolidation

One night in a fixed order: resolution, maintenance, world models, mining, identity and dream. It is deterministic and makes no LLM calls; it only proposes, and waking evidence decides.

Reach

Deliberate reach

probe, probe_expand and familiarity tell "I hold nothing here" from "I hold a lot" before answering; situate rebuilds the context of one past moment.

Revision

Belief revision without deletion

Updates are new assertions. Revision uses closure records (retract, supersede), and forgetting is decay of retrieval strength plus closures and silencing: the substrate stays lossless.

Operations

Replay and operator tools

export_replay serves the journal in seq order for history scrub and live tail. Operator tools cover health checks, re-embedding and redigestion. SQLiteJournal can share one file with the store.

from abstractmemory import (
    MemorySystem, MemoryRecordInput, SQLiteTripleStore, SQLiteJournal, Stimulus,
)

store = SQLiteTripleStore("memory.sqlite3")
journal = SQLiteJournal("memory.sqlite3")   # sidecar tables in the same file
system = MemorySystem(store=store, journal=journal)

# Form a typed record (idempotent by key; forming is not using).
[record_id] = system.remember_many(
    [MemoryRecordInput(kind="episode", title="Pool outage",
                       digest="The connection pool saturated at noon.",
                       keywords=("pool", "outage"))],
    scope="session", owner_id="s1", idempotency_key="turn-1",
)

# Reconstruct working memory for a cue (pure read), then commit what you used.
result = system.reconstruct(Stimulus(cue_text="pool outage"), scopes=[("session", "s1")])
system.commit_selection(result.trace_id, [h.record_id for h in result.handles[:2]])
print(record_id, [h.record_id for h in result.handles])

Temporal triples

Knowledge as TripleAssertion records: subject, predicate and object with scope, time, confidence and provenance. Assertions are immutable, and all three stores implement the same TripleStore protocol: add(), query(), close().

Store, query, reason over time

  • ✓ Canonical terms: subject, predicate and object are trimmed and lowercased (set attributes={"literal": True} to keep the object's case)
  • ✓ Scopes: a free-form partition label, such as run, session, global, or the entity-home scopes self, diary and life, plus an optional owner_id
  • ✓ Time: observed_at, an optional valid_from / valid_until window, and since, until and active_at filters
  • ✓ Semantic search: query_text needs a store embedder (there is no keyword fallback); query_vector takes your own vector; min_score sets a cosine threshold
  • ✓ Stores: InMemoryTripleStore (volatile), SQLiteTripleStore (one file, standard library, native vector search), LanceDBTripleStore (optional lancedb extra)
from abstractmemory import InMemoryTripleStore, TripleAssertion, TripleQuery

store = InMemoryTripleStore()
store.add([
    TripleAssertion(
        subject="Scrooge",
        predicate="related_to",
        object="Christmas",
        scope="session",
        owner_id="sess-1",
        provenance={"span_id": "span_123"},
    )
])

hits = store.query(TripleQuery(subject="scrooge", scope="session", owner_id="sess-1"))
assert hits[0].object == "christmas"      # terms are canonicalized
assert hits[0].assertion_id is not None   # read-side identity
print(hits[0].subject, hits[0].predicate, hits[0].object)   # scrooge related_to christmas

Semantic search with an embedder

Give a store an embedder and TripleQuery(query_text=...) ranks by vector similarity: OpenAICompatTextEmbedder(base_url, model) works with any OpenAI-compatible /embeddings endpoint (the base_url includes the version prefix, for example http://127.0.0.1:1234/v1), passed as SQLiteTripleStore(path, embedder=embedder). Results carry attributes["_retrieval"] with the score and metric. See getting started and the API reference.

For an AbstractGateway deployment, use AbstractGatewayTextEmbedder(base_url=..., auth_token=...); it posts to /api/gateway/embeddings by default.