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)
¶
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". |