Skip to main content
Smithers includes a built-in caching layer that stores task outputs and replays them when the same task is executed with identical inputs. This eliminates redundant agent calls during development iteration, workflow resumption, and re-runs with unchanged parameters.

Enabling Caching

There are two ways to enable caching: On the <Workflow> element:
<Workflow name="my-workflow" cache>
  <Task id="analyze" output={schema.analyze} agent={agent}>
    {prompt}
  </Task>
</Workflow>
Via the smithers() options:
export default smithers(db, buildWorkflow, { cache: true });
Both approaches enable caching for all tasks in the workflow. There is no per-task cache toggle — it is workflow-wide.

How It Works

When caching is enabled, the engine checks for a cached result before executing each task:
Task ready to execute
  |
  v
Compute cache key (SHA-256 of all input components)
  |
  v
Look up key in _smithers_cache table
  |
  +---> Cache hit: validate payload against current output schema
  |       |
  |       +---> Valid:   use cached payload, skip agent call
  |       +---> Invalid: cache miss (schema changed), execute normally
  |
  +---> Cache miss: execute task normally
          |
          v
        On success: write result to _smithers_cache
A cache hit marks the attempt as cached: true in _smithers_attempts, so you can distinguish cached results from fresh executions in your debugging.

Cache Key Components

The cache key is a SHA-256 hash of a JSON object containing all of these components:
ComponentSourceDescription
workflowName<Workflow name="...">Scopes caches to a specific workflow.
nodeId<Task id="...">Scopes caches to a specific task.
outputTableNameTask’s output table nameEnsures different output schemas are not confused.
schemaSigSHA-256 of table column definitionsInvalidates cache when table structure changes.
agentSigagent.id or "agent"Invalidates cache when the agent/model changes.
toolsSigSorted, comma-joined tool namesInvalidates cache when available tools change.
promptTask’s prompt textInvalidates cache when the prompt changes.
payloadTask’s static payloadInvalidates cache when static data changes.
If any component changes, the cache key changes and a fresh execution occurs.

Schema Signatures

The schemaSig deserves special attention. It is computed by the schemaSignature() function, which:
  1. Gets all columns from the Drizzle table.
  2. Sorts column names alphabetically.
  3. For each column, captures: name, columnType, notNull flag, and primary flag.
  4. Joins everything with | separators.
  5. Computes a SHA-256 hash of the result.
tableName|colA:SQLiteText:1:0|colB:SQLiteInteger:0:0|...
This means adding a column, removing a column, changing a column type, or changing nullability all produce a different signature, invalidating the cache. Renaming a column also invalidates it.

Cache Invalidation

The cache is automatically invalidated when any of these change:
ChangeWhy
Prompt text changesDifferent input = different expected output.
Output table structure changesSchema signature changes.
Agent model changesDifferent model may produce different results.
Tool allowlist changesAvailable tools affect agent behavior.
Static payload changesDifferent data = different expected output.
Workflow name changesCache is scoped to workflow name.
Task node ID changesCache is scoped to node ID.
The cache is not time-based. A cached result is valid indefinitely until one of the above components changes. To manually clear the cache, delete rows from _smithers_cache:
sqlite3 workflow.db "DELETE FROM _smithers_cache"
Or clear cache for a specific workflow:
sqlite3 workflow.db "DELETE FROM _smithers_cache WHERE workflow_name = 'my-workflow'"

Database Storage

Cached results are stored in the _smithers_cache table:
ColumnTypeDescription
cache_keyTEXT (PK)SHA-256 hash of all cache key components.
created_at_msINTEGERWhen the cache entry was created.
workflow_nameTEXTThe workflow that produced this entry.
node_idTEXTThe task that produced this entry.
output_tableTEXTThe output table name.
schema_sigTEXTSHA-256 of the table column structure at cache time.
agent_sigTEXTAgent identifier at cache time.
tools_sigTEXTTool allowlist signature at cache time.
jj_pointerTEXTJJ version control pointer (if available).
payload_jsonTEXTThe cached output payload as JSON.

Cache Validation on Read

When a cache hit is found, Smithers does not blindly use the stored payload. It validates the payload against the current output table schema. If validation fails (e.g., a required column was added since the cache was written), the cache entry is treated as a miss and the task executes normally. This means cache entries are self-healing — you do not need to manually purge stale entries after schema changes.

When Not to Cache

Caching is best for:
  • Development iteration: Re-running a workflow while changing later tasks should not re-execute expensive early tasks.
  • Idempotent agent tasks: Tasks where the same prompt reliably produces equivalent outputs.
  • Resumption: Resuming a run after a crash should not re-execute already-completed agent calls.
Caching may not be appropriate for:
  • Tasks that depend on external state: If the agent reads from APIs, files, or databases that change between runs, cached results may be stale.
  • Non-deterministic tasks: If you want fresh, varied outputs each time (e.g., creative writing with high temperature), caching defeats the purpose.
  • Tasks with side effects via tools: If agent tools make external changes, replaying a cached result skips those side effects.

Inspecting the Cache

# View all cache entries
sqlite3 workflow.db "SELECT cache_key, workflow_name, node_id, created_at_ms FROM _smithers_cache"

# Check if a specific task has a cached result
sqlite3 workflow.db "SELECT cache_key FROM _smithers_cache WHERE workflow_name = 'my-workflow' AND node_id = 'analyze'"

# View cached payload
sqlite3 workflow.db "SELECT payload_json FROM _smithers_cache WHERE node_id = 'analyze'"