Skip to content

Memory

Auto-generated from source docstrings (mkdocstrings). The complete public contract is agentmaker.__all__ plus each subpackage __all__.

agentmaker.memory: memory subsystem (built on top of the agentmaker.retrieval base).

Provided
  • MemoryItem: the data type for a single memory (type is a free-form label).
  • MemoryStore: the memory source-of-truth store (full memories persisted by id in SQLite).
  • Memory: semantic memory combining the source-of-truth store with the retrieval base; add / search / update / delete + forget / stats / summary / consolidate (the source-to-derived-index sync seam IndexSync / SyncIndexSync is shared with RAG and lives in agentmaker.retrieval).
  • SmartWriter: Mem0-style smart writing (extract facts -> ADD/UPDATE/DELETE/NOOP).
  • MemoryTool: wraps memory as a tool so an agent can actively record / recall (agentic memory).
  • KVStore / KVMemory: key-value memory, structured facts stored and read by exact key (complements semantic memory).

KVMemory

Key-value memory facade: adds JSON encode/decode on top of KVStore, supporting str / number / list / dict values; carries a fixed scope.

__init__(kv, *, scope=None)

Initialize the facade.

Parameters:

Name Type Description Default
kv KVStore

the underlying key-value store.

required
scope Optional[Scope]

the ownership of this facade (e.g. a given user); defaults to Scope(base="kv").

None

set(key, value)

Write a key; value may be str / number / list / dict, JSON-encoded internally before storing.

get(key, default=None)

Read a key and JSON-decode it; returns default if not found.

delete(key)

Delete a key.

close()

Close the underlying KVStore's database connection.

as_dict()

Return the entire key-value set (values already JSON-decoded).

KVStore

Key-value store: (scope, key) is unique, set writes by overwrite, get reads exactly. Values are strings.

__init__(db_path=':memory:', *, lock=None)

Open the connection and create the table if needed. (each scope column + key) is the unique key, guaranteeing "one value per key under a given ownership".

Parameters:

Name Type Description Default
db_path str

SQLite file path; defaults to ":memory:" for self-tests only.

':memory:'
lock Optional[RLock]

a reentrant lock serializing the connection; pass the same one when sharing a connection with another store, otherwise a new one is created.

None

set(key, value, *, scope=None, all_scopes=False)

Overwrite-write: update value if (scope, key) already exists, otherwise insert.

Rejects an empty scope (unless all_scopes=True is explicit): an empty scope would write into an "ownerless" row mixed in with all users, which is a slip.

get(key, *, scope=None, all_scopes=False)

Read exactly by key; returns None if not found.

Rejects an empty scope (unless all_scopes=True is explicit): an empty scope adds no ownership filter and would read another user's value. When the scope spans multiple sub-ownerships (a coarse scope hits several rows for the same key), fail loud: KV semantics are "one value per key under a scope". Previously fetchone silently returned an arbitrary row, inconsistent with all's "last-wins" behavior; now it errors and requires an exact scope.

delete(key, *, scope=None, all_scopes=False)

Delete a key.

Rejects an empty scope (unless all_scopes=True is explicit): an empty scope would delete this key across all users, which is a dangerous operation.

all(*, scope=None, all_scopes=False)

Return all key-values under this scope (as a dict).

Rejects an empty scope (unless all_scopes=True is explicit): an empty scope would read across users. When the same key appears in multiple matched sub-ownerships (a coarse scope / all_scopes spanning users), fail loud: a dict cannot represent duplicate keys. Previously dict(rows) silently kept the last row and dropped data, inconsistent with get; now it errors and requires an exact scope or per-scope reads.

close()

Close the database connection.

Memory

Memory manager: coordinates the source-of-truth store (MemoryStore) with the retrieval index (HybridRetriever).

__init__(retriever, store, *, llm=None, scope=None, config=None, index_sync=None, tracer=None)

Initialize the memory manager.

Parameters:

Name Type Description Default
retriever HybridRetriever

Retrieval backend handling the read path (vector + keyword + RRF + optional rerank).

required
store MemoryStore

The memory source-of-truth store, holding complete MemoryItem records.

required
llm Optional[LLMClient]

Optional; used by summary / consolidate. Calling those two without it raises.

None
scope Optional[Scope]

Default ownership label that isolates memories from rag documents and from different users / agents; defaults to Scope(base="memory").

None
config Optional[MemoryConfig]

Optional memory knobs (scoring weights / halflife / search_top_k / summary_top_k / batch_size / default_importance / forget_threshold); defaults to MemoryConfig(). Per-call kwargs on each method override these (three-level resolution).

None
index_sync Optional[IndexSync]

Optional derived-index sync seam (IndexSync, see index_sync.py); write paths (add/update/delete/rebuild) propagate changes to the retrieval index and reconcile through it. Defaults to SyncIndexSync(retriever) (synchronous write-through + in-process tracking); for async / distributed delivery (outbox + worker), implement one and inject it, and the Memory write paths stay unchanged.

None
tracer

Optional tracer (duck-typed emit, same shape as Harness); once attached, search emits memory_search events and the summary / consolidate LLM calls flow into trace and RunPolicy governance. Zero overhead when not attached.

None

from_config(config, *, embedder=None, retriever=None, store=None, llm=None, db_path=':memory:', reranker=None, scope=None, retrieval=None, index_sync=None, tracer=None) classmethod

Assemble a Memory in one line from an AgentmakerConfig: defaults to the sqlite backend; pass retriever / store to inject a custom backend (without touching framework source).

Pluggable backends follow the "assembly root lives in the app" principle (the library does not hardwire the wiring, it only ships default batteries; wiring is the app's job): to swap in pgvector or similar, implement the retrieval VectorStore / KeywordIndex interfaces and pass the retriever (plus store if needed); otherwise build_sqlite_hybrid is used.

Parameters:

Name Type Description Default
config

AgentmakerConfig (reads config.memory; the default backend's retrieval knobs come from retrieval or config.retrieval).

required
embedder

Text-to-vector encoder; required when using the default sqlite backend, unneeded when a retriever is injected.

None
retriever Optional[HybridRetriever]

Inject a custom retrieval backend (HybridRetriever); defaults to building the sqlite backend.

None
store Optional[MemoryStore]

Inject a custom memory metadata store; defaults to MemoryStore(db_path).

None
retrieval

Optional RetrievalConfig override (for the default backend): pass it when memory and rag should use different retrieval knobs. Note: Memory's return count / candidate pool is set by MemoryConfig.search_top_k plus an internal strategy (x4), which overrides the backend RetrievalConfig's top_k / candidate_pool; for memory the backend config mainly just supplies rrf_k (the fusion constant).

None
Example

mem = Memory.from_config(AgentmakerConfig(memory=MemoryConfig(recency_halflife_hours=24)), embedder=emb, scope=ALICE)

add(content, *, type='semantic', importance=None, metadata=None, scope=None)

Record a memory: build a MemoryItem, persist to the source-of-truth store (authoritative), sync into the retrieval index through the seam, and return the item.

An index write failure does not roll back the store; the entry is marked pending reindex (the store is authoritative and eventually consistent, see index_sync / pending_reindex). (This stores directly; for de-duplicating / updating "smart writes" of the same fact see SmartWriter.)

Parameters:

Name Type Description Default
content str

The memory body text.

required
type str

Memory type (free-form label, defaults to semantic).

'semantic'
importance Optional[float]

Importance 0..1; when None, uses self.cfg.default_importance (an explicit 0 is not swallowed).

None
metadata Optional[dict]

Attached information.

None
scope Optional[Scope]

Ownership label for this item; defaults to the Memory's default scope.

None

Returns:

Name Type Description
MemoryItem MemoryItem

The newly created and persisted memory.

update(id, content, *, scope=None)

Update a memory's body text: the store deletes the old row and inserts the new one in a single transaction (authoritative, no lost rows), and the index converges via an upsert through the seam. Returns None if the item is not in this scope.

Scope defaults to this Memory's scope (by default it only touches its own, preventing cross-scope edits); it mirrors add's scope= to allow explicit targeting when multiple scopes share one Memory instance (e.g. a coarse-ownership instance editing a fine-ownership memory). Deleting the old and writing the new go through store.replace in one transaction, so a mid-way crash does not lose this memory. An index write failure does not roll back the store; the entry is marked pending reindex and converges via rebuild_index or read-time self-heal (store authoritative, eventually consistent).

invalidate(id, *, superseded_by=None, scope=None)

Soft-invalidate a memory: remove it from the index (no longer retrievable) while keeping the record in the store for audit. Returns None if not in this scope.

Difference from physical deletion (delete / forget, for compliance cases): an invalidated memory stays in the source-of-truth store with an invalid_at timestamp and a superseded_by chain, so state changes like "the user moved from Shanghai to Beijing" do not erase history (a lightweight flat version of Zep bi-temporal / Mem0 supersession). Audit entry point: store.all(include_invalid=True). Delete-old + write-back run in a single atomic transaction (no lost rows).

Parameters:

Name Type Description Default
id str

The memory id to invalidate.

required
superseded_by Optional[str]

Optional id of the new memory that supersedes it (SmartWriter's UPDATE decision fills this, forming a fact-evolution chain).

None
scope Optional[Scope]

Ownership; defaults to this Memory's scope (mirrors add's scope=).

None

Returns:

Name Type Description
MemoryItem Optional[MemoryItem]

The item marked invalid; None if not in this scope.

delete(id, *, scope=None)

Delete a memory: remove from the store and from the index (scope defaults to this Memory's scope).

delete_many(ids, *, scope=None)

Delete memories in bulk: the store (authoritative) is deleted first, then the index through the seam (far fewer commits than deleting one at a time).

Deliberately delete the store first, then the index: if the index deletion fails, the leftover is only a stale "in the index, not in the store" entry, which search filters out (the store lookup misses) and lazily cleans up (see search), never leaking deleted content; the reverse order would leave a store orphan still visible to stats / summary, which is worse. So do not casually swap the order. The seam's drop is best-effort internally and does not raise. Scope defaults to this Memory's scope (mirrors add's scope=).

close()

Close the resources held by this Memory: the source-of-truth store, the retrieval backend retriever, and the index-sync seam _sync (including the DB connection of the SqliteBookkeeping installed by from_config; leaving it open would leak a file handle). When a custom backend is injected, the injector and this close release resources jointly.

__enter__()

Support with Memory(...) as m:, auto-closing on exit (including the _sync bookkeeping connection).

rebuild_index(*, scope=None, batch_size=None)

Rebuild the retrieval index from the store: iterate all memories in this scope and re-write them into the retrieval backend (re-embed + reindex).

The store is the authoritative record and the index is its disposable derivative; this method delivers on "the derivative can be rebuilt from the record." Typical uses: after swapping the retrieval backend (e.g. SQLite vector store -> pgvector), import the data into the new index; or reload the whole thing when the index is corrupt or inconsistent with the store.

Notes
  • retriever.add is an upsert (same id overwrites): a fresh build for an empty index, an in-place refresh for an existing one.
  • Only records present in the store are reloaded; orphans (already deleted from the store but still lingering in the index) are not cleaned up here (search removes them lazily; for a perfect match between index and store, rebuild against a fresh empty backend).
  • Rebuilds by this Memory's (or the given) scope; with one Memory instance per user, call rebuild_index for each.

Parameters:

Name Type Description Default
scope Optional[Scope]

Which ownership range to rebuild; defaults to this Memory's scope.

None
batch_size Optional[int]

Rows written per batch; batching avoids embedding too much at once on large stores.

None

Returns:

Name Type Description
int int

The number of memories reloaded.

pending_reindex(*, scope=None)

Return the set of ids pending reindex (recent index writes that failed and have not yet been converged by rebuild_index / reconcile).

Store authoritative, index eventually consistent (see index_sync): an index write failure does not roll back the store, it only marks the entry pending. An app can use this to monitor drift and periodically trigger rebuild_index to converge. The default backend's (SyncIndexSync) pending set is in-process state (cleared on restart, rebuildable via rebuild).

Parameters:

Name Type Description Default
scope Optional[Scope]

Which ownership range to query; defaults to this Memory's scope.

None

search(query, *, top_k=None, scope=None, relevance_weight=None, recency_weight=None, importance_weight=None, recency_halflife_hours=None, filters=None)

Recall the most relevant memories for a query, ranked by a combined relevance x recency x importance score.

Inspired by Generative Agents: the final score = each component normalized to 0..1 then weighted and summed. With all three weights at 0 it degrades to pure relevance ranking (backward compatible). Recency decays via a halflife: more recent approaches 1, older approaches 0. The knobs below default to self.cfg (MemoryConfig) when None (an explicit 0 is not swallowed).

Parameters:

Name Type Description Default
query str

The query text.

required
top_k Optional[int]

Number of results to return (defaults to cfg.search_top_k).

None
relevance_weight Optional[float]

Relevance weight (defaults to cfg.relevance_weight).

None
recency_weight Optional[float]

Recency weight (defaults to cfg.recency_weight).

None
importance_weight Optional[float]

Importance weight (defaults to cfg.importance_weight).

None
recency_halflife_hours Optional[float]

The recency halflife in hours (defaults to cfg.recency_halflife_hours); smaller favors newer memories more strongly.

None
filters

Optional metadata hard filter (list of MetadataFilter, see agentmaker.retrieval.types), passed through to the retrieval backend for pre-filtering; the backend must have declared the corresponding filterable columns. Complements the three-way scoring (soft ranking): filters are hard constraints.

None

Returns:

Type Description
List[RetrievalResult]

List[RetrievalResult]: Ordered by combined score, highest first; metadata carries the relevance / recency / importance / final component scores.

aadd(content, **kwargs) async

Async variant of add (to_thread).

aupdate(id, content, **kwargs) async

Async variant of update (to_thread).

adelete(id, **kwargs) async

Async variant of delete (to_thread).

ainvalidate(id, **kwargs) async

Async variant of invalidate (to_thread).

adelete_many(ids, **kwargs) async

Async variant of delete_many (to_thread).

asearch(query, **kwargs) async

Async variant of search (to_thread).

aforget(**kwargs) async

Async variant of forget (to_thread).

arebuild_index(**kwargs) async

Async variant of rebuild_index (to_thread; full re-embedding is a long network operation, so never run it synchronously on the event loop).

forget(*, strategy='importance', threshold=None, max_age_days=None, capacity=None)

Forget by strategy, returning the list of deleted ids.

Strategies

importance: delete items with importance < threshold (threshold defaults to self.cfg.forget_threshold); age: delete items older than max_age_days days (requires max_age_days); capacity: keep only the "most important + newest" capacity items, delete the rest (requires capacity).

stats()

Stats: {total, by_type} (total count + distribution by type). Pure data, no LLM call.

summary(query=None, *, top_k=None) async

Use the LLM to summarize memories (all, or those matching query) into one paragraph (async: the LLM call awaits chat; the synchronous DB fetch runs in a thread pool).

Requires an llm passed at construction. top_k defaults to self.cfg.summary_top_k when None. Sync callers go through agentmaker.core.aio.run_sync.

consolidate() async

Consolidate (async): hand all memories to the LLM to de-duplicate / merge / keep-latest-on-conflict, then replace them in the store; returns {before, after}. Requires an llm.

The LLM call awaits chat; the synchronous fetch / persist DB operations run in a thread pool. Sync callers go through agentmaker.core.aio.run_sync.

MemoryTool

Bases: Tool

A tool letting an Agent manage long-term memory: remember / recall / forget / summary / stats / consolidate.

__init__(memory, writer=None, *, top_k=5, confirm_writer_edits=True, prompts=None)

Initialize the memory tool.

Parameters:

Name Type Description Default
memory Memory

The memory manager (provides search / forget / summary / stats / consolidate / add).

required
writer Optional[SmartWriter]

Optional smart writer; when provided, remember goes through SmartWriter (auto de-duplicate / rewrite), otherwise it calls add directly.

None
top_k int

Number of results recall returns.

5
confirm_writer_edits bool

When a writer is attached, whether remember also passes through the confirmation gate (default True). SmartWriter may decide, per the LLM, to UPDATE / DELETE existing memories (soft-invalidate, rewrite), which modifies existing data, so by default it goes through the confirmation gate like forget / consolidate (fail-safe). Pure add (no writer) never gates. Pass False to disable in low-friction scenarios.

True

get_parameters()

Declare parameters: one action + content / query depending on the operation.

needs_confirmation(parameters)

Data-deleting / data-modifying actions require human confirmation: forget / consolidate always do; remember also does when a writer is attached and confirm_writer_edits is set (SmartWriter may UPDATE / DELETE existing memories). recall / summary / stats and pure-add remember pass through.

run(parameters)

Dispatch by action to the memory operations, returning a result for the LLM to read (errors as status="error").

arun(parameters) async

Native async variant of run: LLM-calling actions (remember/summary/consolidate) go async via await; non-LLM ones (recall/stats/forget) reuse the sync logic directly (no network wait, no need for async).

SmartWriter

Mem0-style smart writing: extract facts -> compare against existing memories -> decide ADD/UPDATE/DELETE/NOOP -> execute.

__init__(memory, llm, *, similar_k=None, fail_open=True, extract_prompt=None, reconcile_prompt=None, prompts=None, tracer=None)

Initialize the smart writer.

Parameters:

Name Type Description Default
memory Memory

The memory manager (provides search / add / update / delete).

required
llm LLMClient

LLM client, used for fact extraction and reconcile decisions (ideally a cheap model such as deepseek).

required
similar_k Optional[int]

How many similar existing memories to fetch per fact for the comparison; defaults to the injected memory's cfg.similar_k when None.

None
fail_open bool

Strategy when extraction parsing fails. True (default) degrades to storing the whole original text as a single fact, losing no information; False drops the segment and does not store it. Set False for chit-chat / long text / sensitive content you don't want stored wholesale.

True
extract_prompt Optional[str]

System prompt for fact extraction; defaults to the framework default (registry memory.extract). Pass your own to change language or tune the extraction categories.

None
reconcile_prompt Optional[str]

System prompt for the reconcile decision (ADD/UPDATE/DELETE/NOOP); defaults to the framework default (registry memory.reconcile).

None
prompts

Optional prompt registry (PromptRegistry, see agentmaker.prompts); defaults to the framework default DEFAULT_PROMPTS. extract_prompt / reconcile_prompt are its nearby shortcut overrides (equivalent to overriding the memory.extract / memory.reconcile keys).

None
tracer

Optional tracer; defaults to following the injected memory's tracer. The extraction / reconcile LLM calls flow through governance (into trace and RunPolicy limits, see governed_chat in runtime/execution/run_context.py).

None

write(text) async

Smartly write a piece of input into memory (async); returns a processing record per fact (with op, so you can see what was done).

Extraction / reconcile await chat; retrieval uses memory.asearch and CRUD runs in a thread pool. Sync callers go through agentmaker.core.aio.run_sync.

Writing / reconciling / edits+deletes all land within the injected memory's scope: in multi-user scenarios, give each user its own Memory(scope=...) instance each with its own SmartWriter and isolation is natural (consistent with the alice/bob pattern).

MemoryStore

Memory source-of-truth store: full MemoryItems persisted by id in SQLite.

__init__(db_path=':memory:', *, lock=None)

Open the connection and create the table if needed.

Parameters:

Name Type Description Default
db_path str

SQLite file path; defaults to ":memory:" for self-tests only. For real use pass a file path to persist (pass the same file path as the vector store / keyword index when sharing a database).

':memory:'
lock Optional[RLock]

a reentrant lock serializing the connection; pass the same one when sharing a connection with another store, otherwise a new one is created.

None

save(item, *, scope=None)

Store a memory (overwrites if the same id + same scope already exists; same id under different scopes stays independent). metadata is stored as JSON, times as isoformat.

replace(id, item, *, scope=None)

Atomically replace within a single transaction: delete the old row matching (id, scope) first, then insert item.

Used by update / invalidate: it folds the two "delete then write" steps into one transaction (with self._db: commits on success, rolls back on exception), avoiding a crash between deleting the old and writing the new that loses the row. Previously the source-of-truth delete and write were two independent commits, and a crash in between permanently lost the memory.

get(id, *, scope=None)

Fetch the full memory by id (including soft-invalidated ones; whether to filter is up to the caller); returns None if not found.

With a scope, the fetch is limited to that ownership (B semantics: only filter on non-empty dimensions), to avoid reading someone else's memory across scopes. Without a scope (None / empty Scope), fetch by id across the whole database (raw low-level use; the higher-level Memory always passes a scope).

touch(ids, *, scope=None)

Batch-write last_accessed_at = now (a retrieval hit means "got used", Generative Agents semantics); one commit.

delete(id, *, scope=None)

Delete a memory (within the scope-limited range, to avoid cross-ownership deletion).

delete_many(ids, *, scope=None)

Batch-delete memories (within the scope-limited range); one commit.

close()

Close the database connection.

all(*, scope=None, include_invalid=False)

List all valid memories within the scope-limited range (in reverse time order; B semantics: only filter on non-empty dimensions).

With include_invalid=True, soft-invalidated ones are returned too (for auditing / inspecting the fact evolution chain). By default only valid ones are returned: rebuild_index / forget / consolidate / summary all build on this, so invalidated memories won't be re-indexed or revived by cleanup.

MemoryConfig dataclass

All tunable knobs for the memory subsystem: combined scoring + summary + rebuild + write-time dedup + forget/write defaults. Fully frozen.

Attributes:

Name Type Description
relevance_weight / recency_weight / importance_weight

the three scoring weights for search (all 0 degenerates to pure relevance).

recency_halflife_hours float

recency half-life in hours; smaller values favor newer memories more strongly.

recency_anchor str

the time anchor for the recency dimension. "created_at" (default; actually uses updated_at or created_at, so edited content decays by its edit time) or "last_accessed" (decays by the most recent retrieval-hit time, the original Generative Agents semantics).

summary_top_k int

how many memories summary takes by default.

rebuild_batch_size int

how many memories rebuild_index writes per batch.

similar_k int

how many similar memories SmartWriter looks up before writing, for dedup.

forget_threshold float

the importance threshold for Memory.forget.

default_importance float

the default used by Memory.add when importance is not given.

validate()

Range-check: weights non-negative, half-life > 0, count fields >= 1, threshold/importance in [0,1], anchor within the enum.

MemoryItem dataclass

A single memory.

Attributes:

Name Type Description
content str

the memory body.

id str

unique identifier, auto-generated by default (uuid).

type str

memory type (working / episodic / semantic / procedural); not enforced initially, used as a label for now.

importance float

importance in 0~1, for future importance-based eviction ("forgetting").

created_at datetime

the time the memory was recorded, defaults to now.

updated_at Optional[datetime]

the time of the most recent content update; None means never updated (falls back to created_at). Recency scoring uses this, so edited memories decay by their edit time, fixing the case where content changed but the recency score still used the old time.

last_accessed_at Optional[datetime]

the most recent retrieval-hit time; None means never hit. Used as the recency anchor when recency_anchor="last_accessed".

invalid_at Optional[datetime]

soft-invalidation time; None means valid. An invalidated memory is removed from the index and no longer retrievable, but the source-of-truth record is kept for auditing: state changes like "the user moved from Shanghai to Beijing" no longer physically erase the old fact (physical deletion is reserved for forget / compliance cases).

superseded_by Optional[str]

the id of the newer memory that supersedes this one (recorded on soft-invalidation, forming a fact evolution chain); None means none.

metadata Dict[str, Any]

attached information, defaults to an empty dict.

__post_init__()

Normalize all four time fields to aware UTC in one place: regardless of whether the caller or a read-back from an old database passes naive or aware datetimes, internally everything is aware. This avoids a TypeError when subtracting a naive time from an aware now in recency / invalidation scoring (see agentmaker.core.clock).

__str__()

Make print(item) show "[type] body (truncated if long)" for easier debugging.

Returns:

Name Type Description
str str

e.g. "[semantic] I'm allergic to peanuts and can't eat nuts".