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 modes: without HINDSIGHT_URL it uses local SQLite facts and keyword recall; with it, Hindsight adds semantic recall, mental-model primers, and retention. Hindsight itself runs on Postgres 15+ with pgvector, so it’s documented and deployed as a Postgres-family feature. HINDSIGHT_URL points at a Hindsight deployment the human has set up (Hindsight Cloud signup or a self-hosted Docker/Helm install); that human step is covered in Set Up Semantic Memory.
The fallback is deliberate: skipping Hindsight keeps the existing SQLite facts behavior, with no Postgres dependency.
For PGlite/Postgres workflow-state backends, fallback facts still use the local
.smithers/smithers.db sidecar while workflow state stays on the selected backend.
HindsightMemoryStore keeps MemoryStore’s identity and concurrency contract in its authoritative rows: fact identity is namespace plus key; message/note ids stay global. A failed projection logs and requeues while the process lives, never rejecting an already-committed mutation. The retry queue isn’t a durable outbox: an interrupted process can leave Hindsight stale until the record is rewritten or deleted. Same-instance projections to one remote document run in mutation order; that ordering doesn’t cross instances sharing a contract store. Exact facts get a stable document id from namespace+key and updateMode: "replace"; threads, messages, and notes use typed retained documents. searchNotes and task recall call Hindsight recall; exact reads and supersession filtering use the contract rows. tag_groups compound filters cover only stable tags (branch, stream, source, scope); session/run identity stays volatile, in metadata and append document ids.
With a user bank plus a project bank, Smithers recalls the user bank without project filters and the project bank through (scope:main OR branch:current) plus stream tags as constraints. Primer ids are searched across configured banks; a missing bank/id pair doesn’t 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
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
memory={...} prop both converge on TaskDescriptor.memoryConfig; a task prop replaces the inherited one. Before the task runs, the engine fetches primers and recall results, caps them conservatively (covering primers, recall rows, labels, framing, and serialized recall tool results), and prepends the block to the prompt. Recall failures degrade to the original prompt; a successful task with retain="on-complete" starts a non-blocking retain.
Tasks with an active bank/banks config bypass task-output caching: recall is mutable input, and a cache hit would otherwise skip it and return an older snapshot’s output. 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 execution.
Use stable tags for branches, streams, source, and scope: scope:main belongs only on branch:main; branch-local writes use scope:branch. Given one branch tag and no scope tag, Smithers derives the correct scope; a tagless project-bank write defaults to scope:main so later canonical recall can see it. Conflicting scope/branch tags are rejected, and the 16-tag limit is checked after merging configured, tool, and automatic source/scope tags. Run and session identities instead belong in retention metadata and document ids, preserving 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:
namespace, object-form recall, remember, and threadId fields remain accepted and preserved on TaskDescriptor.memoryConfig, but the engine ignores them: no fact recall, output retention, or message-history appends. 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
Thememory={{ recall, remember }} legacy block is inert compatibility metadata. Get, set, or delete an exact fact from a compute <Task> by building a store with createMemoryStore(db) and calling it directly: the compute callback receives deps only, so there is no injected store.
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 instead: 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)
creates the FTS index and backfills. On Hindsight, the transactional note row
is projected and indexed on write, so enableNoteSearch is a 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 } to stay namespace-local on a shared backend. See
examples/incident-runbook-memory.jsx
for the full recall → triage → bank → distill → ratify loop.
Notes are a SQLite-only capability: the note + supersession-edge write needs
one synchronous transaction and search needs FTS5, neither of which the Postgres
path provides. saveNote, enableNoteSearch, searchNotes, and the
transactional deleteThread fail loud against a Postgres/PGlite handle rather
than degrading. The Postgres schema still carries _smithers_memory_notes and
_smithers_memory_note_supersessions for dialect parity, labelled staged, not
served by migration 0043_memory_notes_postgres_staged. Under Bun, a Postgres
or PGlite workspace still gets notes: openSmithersBackend keeps memory in the
.smithers/smithers.db sqlite sidecar it opens alongside the main database. Under
Node there is no sqlite at all. See Node runtime.
Processors
Maintenance jobs you run periodically:Inspect from the CLI
Notes
- Memory is not transactional with the workflow’s frame commits: don’t use it for run-scoped state.
- Working-memory writes are unordered. Use message history when sequence matters.