smithers-orchestrator facade, which is canonical. The
smithers-orchestrator/memory subpath exports the same surface.
createMemoryStore(db) preserves the local SQLite implementation. Pass that
store as contractStore when constructing the Hindsight adapter:
HindsightMemoryStore class is exported for callers that need
its recallMemory, getPrimers, or retainMemory engine-facing methods.
The note types (
MemoryNote, SaveNoteInput, NoteReadFilter, and
MemoryProvenance) are exported from the smithers-orchestrator/memory
subpath. WorkingMemoryConfig<T> is generic over a Zod schema and is documented
inline below. Task can be imported directly or taken from createSmithers;
both expose the same memory prop. See Memory for the
declarative <Task memory={...}> metadata.Concepts
AMemoryNamespace scopes everything you store. Pick the kind to match the
lifetime of the data, and the id to identify the specific workflow, agent, or
user.
Lifetime scope.
workflow is per workflow definition, agent per agent
identity, user per end user, global shared across everything.Identifier within the kind.
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.
MemoryProvenance is the run coordinate a memory write was made from. It is
passed explicitly by the caller on setFact and saveNote, never
inferred from ambient context, which does not 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 (pass one to make retries
idempotent).
NoteReadFilter shapes listNotes/searchNotes reads. The default read
contract (no filter) is a stability contract: it 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 aMemoryStore over a Drizzle SQLite handle. Synchronous. Create one at
module scope and reuse it across tasks rather than re-opening the database in
every task body.
A Drizzle
bun-sqlite database handle. The memory tables are created 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,
and so on) with the same arguments, for use inside an Effect pipeline.
Note writes and search require the bun:sqlite backend: transactions provide
note+edge atomicity and FTS5 provides search. Migration 0023 creates the note
tables on Postgres/PGlite, but the runtime still rejects
saveNote and
enableNoteSearch there with DB_WRITE_FAILED, and searchNotes with
DB_QUERY_FAILED.Thread and message reads/writes use the portable Drizzle tables. The
transactional
deleteThread operation is the exception: it currently
requires bun:sqlite and raises DB_WRITE_FAILED on Postgres/PGlite.MemoryService
An EffectContext.Tag whose service value is a MemoryServiceApi. It exposes
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.
createMemoryLayer
Build the EffectLayer that provides MemoryService. Pass it a
MemoryLayerConfig carrying the Drizzle database; provide the resulting layer
to any Effect that depends on MemoryService.
Processors
AMemoryProcessor is a named maintenance pass over a MemoryStore. Each
exposes a Promise process(store) and an Effect processEffect(store).
Compresses older messages in each thread into a single
system summary
message, keeping the two most recent. agent is any
{ run: (prompt: string) => Promise<unknown> }; its output text becomes the
summary.Trims oldest messages per thread until each thread fits a rough token budget
(approximated as
maxTokens * 4 characters).Deletes expired facts across all namespaces by calling
deleteExpiredFacts.MemoryProcessorConfig selects which named processors to run.
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.
recall, remember, namespace, and threadId do not inject
facts, retain output, or append message history. Use the bank-based fields for
runtime memory, or use the store APIs directly for exact local records.
SemanticRecallConfig and MessageHistoryConfig describe the two recall
strategies a memory-aware runtime can apply.
Helpers
Namespaces are stored as strings. These two helpers round-trip aMemoryNamespace to and from its canonical kind:id form, percent-encoding
: and % in the id.
Serializes a namespace, e.g.
{ kind: "user", id: "u1" } becomes
"user:u1".Parses a serialized namespace back. Strings without a known kind prefix parse
as
{ kind: "global", id: str }.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