Skip to main content
Memory persists state across runs. Task outputs are per-run; memory is per-namespace and survives every workflow execution. The store keeps two kinds of state: namespaced facts (JSON values with optional TTL) and ordered message threads. All memory values and non-generic types are re-exported from the smithers-orchestrator facade, which is canonical. The smithers-orchestrator/memory subpath exports the same surface.
import {
  createMemoryStore,
  createMemoryLayer,
  MemoryService,
  Summarizer,
  TokenLimiter,
  TtlGarbageCollector,
  namespaceToString,
  parseNamespace,
} from "smithers-orchestrator";
import type {
  MemoryNamespace,
  MemoryNamespaceKind,
  MemoryFact,
  MemoryThread,
  MemoryMessage,
  MemoryStore,
  MemoryServiceApi,
  MemoryProcessor,
  MemoryProcessorConfig,
  MemoryLayerConfig,
  TaskMemoryConfig,
  SemanticRecallConfig,
  MessageHistoryConfig,
} from "smithers-orchestrator";
WorkingMemoryConfig<T> is generic over a Zod schema and is documented inline below rather than imported. The memory prop on Task is returned by the factory, not imported; see Memory for the declarative <Task memory={...}> metadata.

Concepts

A MemoryNamespace 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.
type MemoryNamespace = { kind: MemoryNamespaceKind; id: string };
type MemoryNamespaceKind = "workflow" | "agent" | "user" | "global";
kind
"workflow" | "agent" | "user" | "global"
required
Lifetime scope. workflow is per workflow definition, agent per agent identity, user per end user, global shared across everything.
id
string
required
Identifier within the kind.
A fact is a namespaced JSON value, last-write-wins, with an optional TTL.
MemoryFact
object
A thread groups ordered messages.
MemoryThread
object
MemoryMessage
object

createMemoryStore

Build a MemoryStore 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.
function createMemoryStore(db: BunSQLiteDatabase<any>): MemoryStore;
db
BunSQLiteDatabase
required
A Drizzle bun-sqlite database handle. The memory tables are created on the same database your workflow uses.
import { createMemoryStore } from "smithers-orchestrator";
import { Database } from "bun:sqlite";
import { drizzle } from "drizzle-orm/bun-sqlite";

const store = createMemoryStore(drizzle(new Database("smithers.db")));
const ns = { kind: "workflow" as const, id: "code-review" };

await store.setFact(ns, "model", "gpt-4");
const fact = await store.getFact(ns, "model"); // MemoryFact | undefined
const all = await store.listFacts(ns);

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.
Facts
methods
Threads & messages
methods

MemoryService

An Effect Context.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.
MemoryServiceApi
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.
function createMemoryLayer(
  config: MemoryLayerConfig,
): Layer.Layer<MemoryService, never, never>;
config
MemoryLayerConfig
required
import { Effect } from "effect";
import { createMemoryLayer, MemoryService } from "smithers-orchestrator";

const program = Effect.gen(function* () {
  const memory = yield* MemoryService;
  yield* memory.setFact({ kind: "user", id: "u1" }, "tier", "pro");
});

Effect.runPromise(program.pipe(Effect.provide(createMemoryLayer({ db }))));

Processors

A MemoryProcessor is a named maintenance pass over a MemoryStore. Each exposes a Promise process(store) and an Effect processEffect(store).
type MemoryProcessor = {
  name: string;
  process: (store: MemoryStore) => Promise<void>;
  processEffect: (store: MemoryStore) => Effect.Effect<void, SmithersError>;
};
Summarizer
(agent) => MemoryProcessor
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.
TokenLimiter
(maxTokens) => MemoryProcessor
Trims oldest messages per thread until each thread fits a rough token budget (approximated as maxTokens * 4 characters).
TtlGarbageCollector
() => MemoryProcessor
Deletes expired facts across all namespaces by calling deleteExpiredFacts.
MemoryProcessorConfig selects which named processors to run.
MemoryProcessorConfig
object
import { TtlGarbageCollector, Summarizer } from "smithers-orchestrator";

await TtlGarbageCollector().process(store);
await Summarizer(myAgent).process(store);

Task-level config

TaskMemoryConfig is the shape of the memory prop on Task. It is preserved as task metadata for runtimes and integrations that layer memory behavior onto task execution; the store APIs above are the current public read/write surface.
memory
TaskMemoryConfig
SemanticRecallConfig and MessageHistoryConfig describe the two recall strategies a memory-aware runtime can apply.
SemanticRecallConfig
object
MessageHistoryConfig
object
Working memory is a typed, single-document fact validated against a Zod schema. Its config is generic over the schema type:
type WorkingMemoryConfig<
  T extends import("zod").ZodObject<import("zod").ZodRawShape> = import("zod").ZodObject<import("zod").ZodRawShape>,
> = {
  schema?: T;
  namespace: MemoryNamespace;
  ttlMs?: number;
};

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.
namespaceToString
(ns: MemoryNamespace) => string
Serializes a namespace, e.g. { kind: "user", id: "u1" } becomes "user:u1".
parseNamespace
(str: string) => MemoryNamespace
Parses a serialized namespace back. Strings without a known kind prefix parse as { kind: "global", id: str }.
import { namespaceToString, parseNamespace } from "smithers-orchestrator";

namespaceToString({ kind: "user", id: "u1" }); // "user:u1"
parseNamespace("workflow:code-review"); // { kind: "workflow", id: "code-review" }
Cross-run facts are also viewable from the CLI:
bunx smithers-orchestrator memory
Source store/MemoryStore.ts · MemoryServiceApi.ts · createMemoryLayer.js · processors.js · Tests store.test.js · service.test.js · processors.test.js · See also Memory, Types reference