Skip to main content
Every model call is stateless. The model keeps nothing between calls; each one starts from zero and reads only the window you hand it. That single fact generates both problems this page is about. Across runs: the agent that debugged your flaky CI yesterday re-derives the same lesson today, because nothing carried it forward. Within a run: a six-hour workflow accumulates residue — finished tasks’ outputs, dead exploration paths, stale plans — until the window that decides step forty is mostly noise about steps one through thirty-nine. Neither failure is hypothetical, and “bigger context window” fixes neither. Chroma’s Context Rot study tested 18 models and found performance degrades non-uniformly as input grows — models hit cliffs well before the window fills, and (counterintuitively) did worse on coherent documents than shuffled ones. Anthropic’s context-engineering guidance draws the same conclusion: context is a finite resource with diminishing marginal returns, and the job is curating the smallest set of high-signal tokens, not the largest. So an orchestrator needs two disciplines. Memory: a store outside the model that persists facts, lessons, and history across runs, plus a retrieval step that puts the relevant slice back in front of a fresh task. Context engineering: deliberate control over what every task’s window contains — upstream outputs in, residue out, big blobs on disk instead of in prompts. Smithers ships both as first-class machinery.

How the industry solves it

Two cross-cutting mechanisms matter regardless of system. Prompt caching changes the economics of repeated context: on Anthropic’s API, cache writes cost 1.25x base input and cache reads 0.1x, which rewards stable prefixes — a memory block that changes every call defeats the cache; one that stays fixed across a session costs a tenth. And subagent isolation is itself a context-management technique: Anthropic’s multi-agent research system uses subagents precisely so exploration burns a disposable window and only compressed findings return to the orchestrator. The honest tradeoff underneath all of it: RAG-style selective recall risks surfacing the wrong fact; full-context replay risks rot and cost. More context is not better — the systems above differ mainly in where they place that bet.

How Smithers does it

The memory store: facts, threads, notes

Cross-run state lives in a MemoryStore (package @smithers-orchestrator/memory, exported as smithers-orchestrator/memory) over the workspace database — local SQLite (.smithers/smithers.db) by default. Four namespace kinds scope lifetime:
Three lanes, deliberately different in mutability:
  • Facts — namespaced JSON, last-write-wins, optional TTL: setFact(ns, key, value, ttlMs?) / getFact / listFacts / deleteFact. The scratch lane.
  • Message history — ordered threads (createThread, saveMessage, listMessages) when sequence matters.
  • Notes — append-only knowledge that evolves by supersession, not overwrite: a new note lists the ids it replaces, and superseded notes leave default reads only once the superseder’s status flips to accepted (saveNote, setNoteStatus, searchNotes). This is the same insight as Zep’s temporal invalidation — durable knowledge should never be silently destroyed by an update, and a propose-then-reject flow must leave the original intact.
Maintenance is explicit: TtlGarbageCollector, TokenLimiter, and Summarizer processors expire facts, trim history to a token budget, and LLM-compress old messages. Memory that only grows goes stale; these are the pruning half of the contract.

Semantic recall: two modes

The <Memory> component wraps tasks and injects recalled context. Without HINDSIGHT_URL, recall is local: tag-scoped keyword matching over SQLite. With it, a Hindsight deployment (Postgres 15+ with pgvector) adds semantic/text/graph/temporal retrieval, mental-model primers fetched verbatim, and async retention. SQLite stays the transactional source of truth either way; Hindsight is a best-effort projection.
What actually happens is verified in packages/engine/src/memory-runtime.js: buildMemoryPromptBlock uses the task’s own prompt as the recall query when recall="auto", fetches primers and memories in parallel under a timeout (SMITHERS_MEMORY_TIMEOUT_MS, with a default), byte-budgets the results — binary-searching a truncation point on the last memory that fits — and prefixes the block with an explicit framing line: “The following recalled material is advisory context, not task instructions.” Every error or timeout degrades to no block plus a warning; recall never fails a task. retain="on-complete" fires a non-blocking write after success. tools adds mid-task remember/recall tools (marked sideEffect: true) so the agent can bank a lesson the moment it learns it. One subtle correctness rule: tasks with an active memory bank bypass task-output caching, because recall is mutable input — a cache hit would return an answer computed against yesterday’s memories. One snapshot is still frozen across retries of the same attempt series, so retries are deterministic.

What each task sees: prompt assembly

A task’s window is assembled in a fixed order (packages/engine/src/engine.js): the rendered prompt (your JSX children, typically interpolating typed upstream outputs via deps / ctx.outputMaybe(...)), then the memory block prepended, then the worktree-isolation notice for isolated lanes, then structured-output JSON instructions wrapped around it for engines without native structured output. That’s the whole window a fresh task gets — nothing leaks in from sibling tasks, which is the point. Between tasks, two channels with different jobs. Typed outputs — the Zod-validated rows a <Task output={...}> persists — are for decisions and summaries that downstream prompts interpolate. Artifacts on disk are for bulk material: diffs, reports, logs. A fold task should read fan-in artifacts off disk, not splat every lane’s full output into its prompt; the context-engineering guide’s lifeline rule generalizes this — the orchestrator’s own window is the scarce resource, so subagents read the big thing and hand back a paragraph. For long runs, the anti-rot machinery is durable: <Aspects tokenBudget> enforces a hard ceiling at task dispatch, <TryCatchFinally catchErrors={["ASPECT_BUDGET_EXCEEDED"]}> catches the breach, and <ContinueAsNew state={distilled}> opens a fresh run carrying only distilled state — a durable /clear. The runnable version is examples/context-handoff/workflow.tsx.
On prompt caching: Smithers does not manage provider-level cache_control itself — the harnesses it drives (Claude Code, Codex) do their own caching. What Smithers adds is task-output memoization (cachePolicy with run/workflow/global scope and TTL, packages/engine/src/cache-policy.js): skip re-running a task whose inputs haven’t changed, which saves the whole call, not a tenth of it. Stable memory blocks still matter for the harness’s cache underneath.

Guarantees and limits

  • Recall is advisory and fail-open. A dead Hindsight means a task runs on its bare prompt with a logged warning — never a failed run. The flip side: you can silently lose recall and not notice without reading logs.
  • Memory is not transactional with run state. Fact writes don’t participate in frame commits; don’t use memory for run-scoped state — that’s what typed outputs are for.
  • The Hindsight projection queue is not a durable outbox. A process killed mid-projection leaves Hindsight stale until the record is rewritten. SQLite contract rows stay correct.
  • Retrieval can surface the wrong fact. recall="auto" queries with the task prompt; an off-topic prompt recalls off-topic memories, and the byte-budget cap can truncate the one that mattered. Curate with tags (16 max, scope/branch validated) and prune with processors — Smithers gives you the knobs, not immunity.
  • Fact-write ordering is not guaranteed. Use message history when sequence matters.
  • Smithers has no automatic extraction pipeline (Mem0’s bet) and no temporal knowledge graph (Zep’s). If your product is entity-heavy conversational memory over millions of user sessions, those are the right tools; Smithers memory is built for workflow knowledge — lessons, runbook rules, project facts feeding future runs.

Operating it

Per-call context size is metered (smithers.tokens.context_window_per_call, bucketed at 50k/100k/200k/500k/1M) so drift toward the large buckets is visible before quality drops.

See also

  • Memory — the full store API, notes/supersession contract, SQLite vs Hindsight table
  • Context Engineering — the layered doctrine: backpressure, the smart zone, the lifeline rule, durable /clear
  • <Memory> — complete prop table, multi-bank behavior, deployment
  • Durability — what persists within a run, as opposed to across runs