Skip to main content
Memory persists state across runs: task outputs are per-run, memory is per-namespace. The store keeps three kinds of state, detailed below: namespaced facts, ordered message threads, and append-only notes. All memory values and non-generic types are re-exported from the canonical smthrs facade; the smthrs/memory subpath exports the same surface.
createMemoryStore(db) preserves the local SQLite implementation; pass it as contractStore when constructing the Hindsight adapter:
HINDSIGHT_URL is the base URL of a reachable Hindsight deployment: http://127.0.0.1:8888 for the default self-hosted Docker image, or a Hindsight Cloud workspace URL. Obtaining one (cloud signup or self-host deploy) is a human step, covered in the Product API at Set Up Semantic Memory; HINDSIGHT_API_KEY is the bearer token for endpoints that require one. The contract store stays authoritative for atomic exact records and global message/note ids; Hindsight projection is best-effort, logging and queueing a remote failure for in-process retry (not durable across restarts) without rejecting an already-committed contract mutation. HindsightMemoryStore is exported for its recallMemory, getPrimers, and retainMemory engine-facing methods.
Note types (MemoryNote, SaveNoteInput, NoteReadFilter, MemoryProvenance) are exported from smthrs/memory. WorkingMemoryConfig<T> is generic over a Zod schema (documented below). Task, imported directly or from createSmithers, exposes the same memory prop; see Memory for the declarative <Task memory={...}> metadata.

Concepts

A MemoryNamespace scopes everything you store: kind matches the data’s lifetime, id identifies the specific workflow, agent, or user.
"workflow" | "agent" | "user" | "global"
required
Lifetime scope: workflow per workflow definition, agent per agent identity, user per end user, global shared across everything.
string
required
Identifier within the kind.
A fact is a namespaced JSON value, last-write-wins, with an optional TTL.
object
A thread groups ordered messages.
object
object
A note is append-only knowledge: facts are mutable KV, notes are immutable rows, body/labels/provenance fixed after insert. status is the one deliberate exception: setNoteStatus lets a human or workflow gate write an answer about an existing note without churning its id. Notes carry no TTL: knowledge dies by supersession or rejection, not by clock.
object
MemoryProvenance is the run coordinate a memory write was made from, passed explicitly by the caller on setFact and saveNote. It’s never inferred from ambient context, which doesn’t survive agent/tool boundaries.
SaveNoteInput is the saveNote argument: the namespace as the structured object, tags as an array, plus optional kind, author, status, provenance, supersedes (note ids this note replaces; junction rows are written atomically with the note), and id (idempotency key, see saveNote). NoteReadFilter shapes listNotes/searchNotes reads. The default read contract (no filter): returns notes that are (a) not superseded by an accepted note and (b) status = "accepted"; a pending or rejected superseder hides nothing. Filters widen or narrow:

createMemoryStore

Build a MemoryStore over a Drizzle SQLite handle. Synchronous; create one at module scope and reuse it across tasks instead of reopening the database per task.
BunSQLiteDatabase
required
A Drizzle bun-sqlite database handle; the memory tables live on the same database your workflow uses.

MemoryStore

The Promise-based read/write surface. Every method has an Effect-returning twin (getFactEffect, setFactEffect, listThreadsEffect, deleteMessagesEffect, etc.) with the same arguments, for use inside an Effect pipeline.
methods
methods
Note writes and search require bun:sqlite: transactions give note+edge atomicity, FTS5 gives search. Migration 0023 creates note tables on Postgres/PGlite, but the runtime still rejects saveNote/enableNoteSearch there with DB_WRITE_FAILED, and searchNotes with DB_QUERY_FAILED.
methods
Thread and message reads/writes use the portable Drizzle tables, except the transactional deleteThread, which requires bun:sqlite and raises DB_WRITE_FAILED on Postgres/PGlite.

MemoryService

An Effect Context.Tag whose service value is a MemoryServiceApi: the same operations as the store, but every method returns Effect.Effect<T, SmithersError> instead of a Promise. The underlying store is reachable via .store.
object

createMemoryLayer

Build the Effect Layer that provides MemoryService. Pass it a MemoryLayerConfig carrying the Drizzle database; provide the resulting layer to any Effect that depends on MemoryService.
MemoryLayerConfig
required

Processors

A MemoryProcessor is a named maintenance pass over a MemoryStore. Each exposes a Promise process(store) and an Effect processEffect(store).
(agent) => MemoryProcessor
Compresses older messages in each thread into one system summary message, keeping the two most recent. agent is any { run: (prompt: string) => Promise<unknown> } whose output text becomes the summary.
(maxTokens) => MemoryProcessor
Trims oldest messages per thread until each thread fits a rough token budget (approximated as maxTokens * 4 characters).
() => MemoryProcessor
Deletes expired facts across all namespaces by calling deleteExpiredFacts.
MemoryProcessorConfig selects which named processors to run.
object

Task-level config

TaskMemoryConfig is the shape of the memory prop on Task. A bank or banks selection activates engine recall, primers, retention, and tools.
TaskMemoryConfig
Object-form recall, remember, namespace, and threadId inject no facts, retain no output, and append no message history. Use the bank-based fields for runtime memory, or the store APIs directly for exact local records. SemanticRecallConfig and MessageHistoryConfig describe the two recall strategies a memory-aware runtime can apply.
object
object
Working memory is a typed, single-document fact validated against a Zod schema. Its config is generic over the schema type:

Helpers

Namespaces are stored as strings. These two helpers round-trip a MemoryNamespace to and from its canonical kind:id form, percent-encoding : and % in the id.
(ns: MemoryNamespace) => string
Serializes a namespace, e.g. { kind: "user", id: "u1" } becomes "user:u1".
(str: string) => MemoryNamespace
Parses a serialized namespace back. Strings without a known kind prefix parse as { kind: "global", id: str }.
Cross-run facts are also viewable from the CLI:
Source store/MemoryStore.ts · MemoryServiceApi.ts · MemoryNote.ts · createMemoryLayer.js · processors.js · Tests store.test.js · notes.test.js · service.test.js · processors.test.js · See also Memory, Types reference