Skip to main content

Step<I, O>

The atomic unit of a workflow.
updateContext() β€” override to publish side-channel metadata alongside the primary output. Called by the executor after execute(). The output parameter is the value returned by execute() β€” derive metadata from it directly rather than capturing state in fields:
The primary output (ClassificationResult) flows forward as the next step’s input; metadata (confidence, label) flows via context keys. Default implementation returns ctx unchanged. AgentStep marker β€” implement on steps that make LLM calls for correct NodeType.AGENT cost tracking:

Built-in Step Types

ClaudeStep options

Workflow Builder

Named shortcuts:

Gate

GateDecision: PASS, FAIL, ESCALATE, TIMEOUT

Implementations

Gate builder

On failure, Verdict written to AgentContext.JUDGE_VERDICT. Reflector transforms verdict into constructive feedback for the retry step.

Gate.updateContext()

Gates implement updateContext() just like steps do. Override it to write the gate’s assessment to AgentContext β€” both the onPass and onFail branches can then read it:

AgentContext

Immutable, threaded through every step. Copy-on-write via mutate().

Well-known Keys

ContextKey<T> implements Bloch’s Typesafe Heterogeneous Container (Effective Java Item 33).

mergeFrom

Overlays all entries from donor onto base. Donor wins on key conflict. Returns a new immutable instance. Used internally by WorkflowExecutor to propagate sub-workflow context writes back to the parent β€” you rarely call this directly, but it is part of the public API if you need manual context composition.

Sub-workflow Composition

A Workflow implements Step, so it can be used anywhere a step is expected β€” as a sequential step, inside a branch, inside a gate path, or inside a parallel branch. The executor handles the nesting transparently:
How it works: The executor detects step instanceof Workflow and runs it inline rather than dispatching through the StepRunner. The sub-workflow receives the parent’s current context, executes normally, and the final context (including all updateContext() writes from every nested step) is merged back into the parent via mergeFrom(). Leaf steps still go through the StepRunner (so TemporalStepRunner dispatch works correctly for individual steps). Nesting is unlimited: sub-workflows can contain sub-workflows. Each level merges its context writes back up the chain. Parallel branches: context writes from parallel sub-workflow branches are merged at the join node. If two branches write to the same key, the last branch to complete wins.

RunOptions

WorkflowGraph (IR)

Pure data structure β€” no execution logic, no Spring AI imports.

Node Types (sealed)

Edge Conditions (sealed)

Type Checking

WorkflowGraphAssert.assertTypeCompatible(graph) walks a compiled graph and checks that each step’s declared output type is assignable to the next step’s declared input type. Catches ClassCastException-style bugs at test time. Opt-in: Steps that override inputType() / outputType() participate. Lambda steps and Step.named() return Object.class by default and are silently skipped β€” no false positives.
If types don’t match:
Untyped steps (lambdas) break the typed chain β€” no check across the gap:
Use in CI to validate workflow structure without making LLM calls.

WorkflowAbortException

Thrown by a step to abort the workflow and return a typed result. Unlike an unhandled exception (which propagates as an error), WorkflowAbortException is caught by the executor and its carried result becomes the workflow’s output:
Use WorkflowAbortException when a step detects an unrecoverable condition but can still produce a meaningful output β€” for example, a typed error response, a failure report, or a structured rejection. Prefer it over returning null or throwing a raw exception when the caller needs to distinguish β€œcompleted with degraded output” from β€œcrashed.”

StepRunner

The substrate swap seam. Same workflow code, different durability:
Three runners are available: Same workflow code β€” swap the @Bean, not the workflow. See Durability for setup and crash-recovery examples. Operator retry (CheckpointManager): FAILED steps are not retried automatically β€” the system holds them until an operator decides the failure was transient. CheckpointManager.resetFailedSteps(runId) deletes FAILED records; re-running with the same runId then skips COMPLETED steps and retries the reset ones.
Sub-workflows are always inline: a Workflow used as a step bypasses the StepRunner entirely and runs in-process. Only leaf steps go through the runner. This is intentional β€” TemporalStepRunner dispatches to a separate activity worker thread and cannot carry full parent context; sub-workflows must stay in-process to propagate context correctly.

TraceRecorder

Records every step transition:
TraceRecorder.noop() is the default β€” zero overhead unless opted in. Trace data feeds Markov analysis, run diagnosis, and replay. When a step produces a trace file (via AgentClientStep backed by a trace-aware client), the tracePath is included in the transition. See Trace Capture for the full guide.

Module Structure