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.
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.
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.
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.
| Call | What 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 |
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.
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.
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.
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.
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.
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 and decay
Activation over rank distance, with strengthen, weaken and refocus. Only retrieval strength changes; the stored memory never decays.
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 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 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.
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.
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.
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 scopesself,diaryandlife, plus an optionalowner_id - ✓ Time:
observed_at, an optionalvalid_from/valid_untilwindow, andsince,untilandactive_atfilters - ✓ Semantic search:
query_textneeds a store embedder (there is no keyword fallback);query_vectortakes your own vector;min_scoresets a cosine threshold - ✓ Stores:
InMemoryTripleStore(volatile),SQLiteTripleStore(one file, standard library, native vector search),LanceDBTripleStore(optionallancedbextra)
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 christmasSemantic 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.