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
AMemoryNamespace 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.
object
object
object
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 aMemoryStore 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 EffectContext.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 EffectLayer 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
AMemoryProcessor 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
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
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.
(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 }.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