Skip to main content
Memory persists state across runs. Task outputs are per-run; memory survives every workflow execution and can seed a new agent task with relevant context.
API reference: Memory lists every memory export and type, its options, and links to source and tests.

Four layers

Local SQLite or Hindsight

The <Memory> component has two operating modes. Without HINDSIGHT_URL, it uses the existing local SQLite facts store and keyword recall. With HINDSIGHT_URL, it uses Hindsight for semantic recall, mental-model primers, and retention. Hindsight stores its own data in Postgres 15 or later with pgvector, so it is documented and deployed as a Postgres-family feature.
Use one HindsightMemoryStore writer instance per transactional contract store. The store serializes same-document projections only within one instance. Separate instances can project competing mutations out of order because they do not share a queue or a durable version fence.
The fallback is deliberate. A workflow that does not configure Hindsight keeps the existing SQLite facts behavior and does not acquire a Postgres dependency. For PGlite and Postgres workflow-state backends, those fallback facts use the local .smithers/smithers.db sidecar; workflow state stays on the selected backend. HindsightMemoryStore preserves the MemoryStore identity and concurrency contract in its authoritative transactional rows: fact identity is namespace plus key, while message and note ids remain global. Hindsight is a best-effort semantic projection, not the authoritative copy. A projection failure is logged and queued for another attempt while the process remains alive; it does not reject a contract-store mutation that already committed. The retry queue is not a durable outbox, so an interrupted process can leave Hindsight behind the contract store until the record is written or deleted again. Within one store instance, projections to the same remote document run in mutation order. That ordering does not extend across instances sharing one contract store. Exact facts use a stable document id derived from namespace plus key and updateMode: "replace"; threads, messages, and notes use typed retained documents. searchNotes and task recall call Hindsight recall, while exact reads and supersession filtering use the contract rows. Compound filters use tag_groups only. Stable branch, stream, source, and scope dimensions are tags. Volatile session and run identity stays in metadata and stable append document ids. For a user bank plus a project bank, Smithers recalls the user bank without project filters and recalls the project bank through (scope:main OR branch:current), with stream tags as additional constraints. Primer ids are searched across the configured banks; a missing bank/id pair does not discard primers found elsewhere. The local SQLite runtime applies the same strict tag scope before keyword ranking. Every method has an Effect-returning twin (store.listThreadsEffect, store.deleteMessagesEffect, etc.) for use inside an Effect pipeline.

Namespaces

Pick the kind to match the lifetime: workflow is scoped to a workflow definition; agent to an agent identity; user to an end user; global is shared across everything.

Declarative task memory

The provider configuration and the existing per-task memory={...} prop converge on TaskDescriptor.memoryConfig. A task prop replaces its inherited provider configuration. Before the task runs, the engine fetches primers and recall results, applies a conservative token cap to the complete fenced block, and prepends it to the prompt. The cap includes primers, recall rows, labels, and framing; it also applies to serialized recall tool results. Recall failures degrade to the original prompt. After a successful task, retain="on-complete" starts a non-blocking retain. Tasks with an active bank or banks configuration bypass task-output caching. Recall is mutable input, and a cache hit would otherwise skip recall and return output produced from an older memory snapshot. Legacy-only memory metadata is inert and keeps the task’s existing cache semantics. Smithers still freezes one fetched snapshot across retries of the same task execution. Use stable tags for branches, streams, source, and scope. scope:main belongs only on branch:main; branch-local writes use scope:branch. When a project-bank configuration has one branch tag and no scope tag, Smithers derives the correct scope. A tagless project-bank write defaults to scope:main, so a later canonical recall can see it. Conflicting scope and branch tags are rejected. The 16-tag limit is checked after configured tags, tool tags, and automatic source/scope tags are merged. Run and session identities belong in retention metadata and document ids, where they preserve provenance without fragmenting consolidation. Tags supplied to the mid-task recall tool only narrow the configured recall scope. They cannot replace the base project branch or stream filters. See <Memory> for the complete prop table, multi-bank behavior, tool mode, and deployment configuration. The legacy task shape remains valid:
Legacy namespace, object-form recall, remember, and threadId fields remain accepted and preserved on TaskDescriptor.memoryConfig, but the engine does not interpret them. They do not recall facts, retain task output, or append message history. Use the bank-based fields above for runtime memory, or call createMemoryStore directly for exact local facts, threads, and messages.

Imperative get/set/delete inside a workflow

The memory={{ recall, remember }} legacy block is inert compatibility metadata. To get, set, or delete an exact fact from a compute <Task>, build a store with createMemoryStore(db) and call it directly. The compute callback receives deps only; there is no injected store, so you create one:
Create the store once at module scope (outside the workflow) and reuse it across tasks rather than re-opening the SQLite handle in every task body. Under Effect, the same operations are available as 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.
On SQLite, full-text search is lazy and opt-in per namespace kind: nothing is indexed (and note writes pay nothing) until enableNoteSearch(kind), which creates the FTS index and backfills. On Hindsight, the transactional note row is projected and indexed on write, so enableNoteSearch is a compatibility no-op and searchNotes uses semantic recall before applying the contract store’s status and supersession rules. Search spans every namespace of the kind; pass { namespace } in the filter to stay namespace-local on a shared backend. 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

The CLI currently exposes fact listing. Use the store API for writes, deletes, threads, messages, and TTL cleanup.

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.