API reference: Memory lists every memory export and type, its options, and links to source and tests.
Four layers
| Layer | API | Use for |
|---|---|---|
| Facts | store.setFact(ns, key, value, ttlMs?) / store.getFact(ns, key) / store.listFacts(ns) / store.listAllFacts() | Namespaced JSON facts. Optional TTL. Last-write-wins. listAllFacts() returns every fact across all namespaces (what memory list prints with no namespace). |
| Message history | store.createThread(ns, title?), store.listThreads(), store.saveMessage(msg), store.listMessages(threadId, limit), store.deleteMessages(threadId, messageIds) | Ordered chat threads per agent or user; list/delete to compact history. |
| Notes | store.saveNote(input) / store.getNote(id) / store.listNotes(ns, filter?) / store.setNoteStatus(id, status) / store.enableNoteSearch(kind) / store.searchNotes(kind, query, limit?, filter?) | Append-only knowledge (lessons, decisions, runbook rules). Notes never mutate; they die by supersession or rejection, not TTL. SQLite backend only. |
| Maintenance | store.deleteExpiredFacts() plus processors | TTL cleanup and history compaction. |
store.listThreadsEffect, store.deleteMessagesEffect, etc.) for use inside an Effect pipeline.
Namespaces
workflow is scoped to a workflow definition; agent to an agent identity; user to an end user; global is shared across everything.
Task Memory Metadata
memory is preserved on the task descriptor as metadata for runtimes and integrations that layer memory behavior onto task execution. The direct store APIs above are the current public memory read/write surface.
Imperative get/set/delete inside a workflow
Thememory={{ recall, remember }} block above is declarative metadata; it is preserved on the task descriptor for runtimes that layer memory onto task execution, not an imperative call. To actually get, set, or delete a fact while a run is executing, build a store with createMemoryStore(db) and call it from inside a compute <Task> (a function task with no agent). The compute callback receives deps only; there is no injected store, so you create one:
store.setFactEffect(ns, key, value, ttlMs?), store.getFactEffect(ns, key), etc., or via MemoryService (MemoryServiceApi), which also exposes the underlying .store.
Durable notes
Facts are a mutable scratch lane; notes are the durable knowledge lane. A note’s body, labels, and provenance never change after insert. Knowledge evolves by supersession: a new note lists the ids it replaces, and the superseded notes drop out of default reads only once the superseder is accepted.status is the one mutable field: a human or workflow gate
flips it with setNoteStatus, so propose-then-reject leaves the original
knowledge untouched.
The default read contract (no filter) is a stability contract: reads
return notes that are accepted and not superseded by an accepted note.
Filters (status, includeSuperseded, kind, namespace) widen or narrow.
enableNoteSearch(kind), which
creates the FTS index and backfills. searchNotes(kind, query) spans every
namespace of the kind; pass { namespace } in the filter to stay
namespace-local on a shared database. Notes require the SQLite backend and
fail loud on Postgres/PGlite. See
examples/incident-runbook-memory.jsx
for the full recall → triage → bank → distill → ratify loop.
Processors
Maintenance jobs you run periodically:Inspect from the CLI
Notes
- Memory and task outputs are distinct stores. Don’t use memory for run-scoped state; it’s not transactional with the workflow’s frame commits.
- Working-memory writes are unordered. Use message history when sequence matters.