Skip to main content
A workflow step-result cache stores a step’s output keyed by its inputs, so a deterministic step reuses its answer instead of paying the full LLM cost on every run.
This caches entire step outputs inside the workflow, keyed by each step’s actual inputs. It is different from Prompt Cache Optimization, which caches prompt prefixes at the LLM provider.

Quick Start

1

Enable with True

Pass cache=True for a process-scoped LRU cache (default max_entries=128).
2

Tune the cache size

Pass an InMemoryStepCache instance to set capacity or share one cache across flows.
3

Bring your own cache

Any object with get() and set() works — back the cache with Redis, disk, or anything else.
InMemoryStepCache is not re-exported at the top level. Import it from praisonaiagents.workflows.step_cache.

Durable Resume (FileStepCache)

FileStepCache is a drop-in durable replacement for InMemoryStepCache — it keeps cached step results in a JSON file so an interrupted flow resumes from where it stopped in a new process.
FileStepCache is not re-exported at the top level. Import it from praisonaiagents.workflows.step_cache.
1

Start a long flow

Pass cache=FileStepCache("run.json"). Each successful step’s result is written atomically to run.json.
2

Get interrupted

A deploy, crash, Ctrl-C, OOM or a closed laptop stops the run part-way.
3

Re-run the same command

Completed steps are served from the file; the flow continues at the first unfinished step.
4

Edit a step and re-run

A step whose code or instructions changed — and every step after it — runs again automatically. No stale results are served.
5

Changed a tool? Clear first

Tool implementations are not fingerprinted, so call FileStepCache("run.json").clear() before re-running.

When to pick which

Pick the lightest cache that covers where your results need to live.

What invalidates a cached step

Resume is content-addressed, not index-based, so a step re-runs whenever anything it depends on changes.
  • An anonymous step with no stable name never hits a durable cache in a new process — it re-runs.
  • Mutable captured state, imported libraries and tool implementations are not fingerprinted — after changing one, call clear() (or delete the file).
  • Values that don’t JSON round-trip unchanged (tuples, int-keyed dicts, arbitrary objects) are rejected at set() time, not stored lossily. The workflow logs a warning and continues.
  • Pointing FileStepCache at an existing file that isn’t a step-cache JSON object raises ValueError at construction — use a dedicated path.

Crash-safety and concurrent writers

Every set() is a locked, atomic read-modify-write of the file, so a crash mid-write keeps the last good file. Two caches on the same path — in one process or two — both add to the file; neither erases the other’s entries. Only successful steps are cached, as with the in-memory cache.

FileStepCache Options


When to Cache

Cache deterministic steps; skip steps that call tools with side effects, read a clock, or depend on randomness.

How It Works

On a miss the step runs and its output is stored; on a hit the cached output is returned without an LLM call. The key is a stable SHA-256 (first 32 hex chars) built from five fields:
Variable types are part of the key, so True and "True" (or 1 and "1") never collapse to the same entry. Two runs with the same prompt but different variables are different calls.

Correctness Guarantees

Nothing caches unless cache= is set. Agent steps are not always deterministic, so silent caching would be wrong.
Only successful steps are stored. Caching a failure would serve it again on every re-run, turning a transient error into a permanent one.
The linear executor and the pattern executor (used by Parallel, If, etc.) are both wrapped, so caching works regardless of where a step sits.
InMemoryStepCache caps at max_entries (default 128). An unbounded cache would be a memory leak on long-running flows.
InMemoryStepCache uses an internal lock, so it is safe to share across Parallel branches.
Values are deep-copied on set and on get, so a caller mutating a returned value cannot corrupt future hits.
On a cache hit the full step delta is replayed — output, the step’s output_variable (or {step_name}_output), any handler-supplied variables, the step’s status and retries, and the StepResult.stop_workflow signal. Downstream {{step_name_output}} substitutions, ctx.variables[...] reads, and result["steps"][...] metadata all resolve the same way on cold and cached runs.
A handler that returned StepResult(stop_workflow=True) on the cold run also stops the workflow on the cached run — downstream steps never execute on the second run. With verbose=True the cache hit prints 🛑 Workflow stopped at: {step.name}, matching the cold-run message.
StepResult.variables returned by the handler are cached and re-applied on a hit, so a downstream consumer reading ctx.variables["answer"] sees the same value on cold and cached runs. If guardrails rejected one attempt and accepted the next, the union of their variables is cached — matching what the live run accumulated, not just the last attempt’s keys.
If the cold run stopped before writing <step>_output (because stop_workflow=True), the cached hit does not insert it either — variables keeps the exact shape the cold run produced.
A hit replays the whole payload the cold run produced, not just the output.
These examples do not call an LLM — they exercise the handler-only path to make the guarantee concrete.

InMemoryStepCache Options

Public attributes: hits, misses. Public methods: get, set, clear, __len__. Accepted cache= values:

Seeing Cache Hits

Turn on verbose to print a line for each hit.

Best Practices

Formatters, lookups, and summarisers over unchanged text are safe. Their output depends only on their input.
Do not cache steps that call tools with side effects or read a clock — a cached answer would skip the effect the caller expected.
Create a single InMemoryStepCache and pass it to multiple AgentFlows to reuse results across pipelines.
Change a workflow variable when you want a fresh call — different variables produce a different key.
Set max_entries to the number of results you actually reuse; a larger cache only wastes memory.
Any flow that costs real money or minutes to re-run deserves durable resume. The overhead is one atomic JSON write per successful step.
The fingerprint doesn’t cross into tool implementations or mutable captured state.
Or deliberately share a path to pool results across runs and processes — both writers add; neither erases.

Workflows

Build multi-step agent pipelines with AgentFlow.

Prompt Cache Optimization

Different layer — provider prompt-prefix caching.