Skip to main content

The Graduation Path

Agent Workflow separates workflow definition from execution. The StepRunner interface is the seam — swap the bean, not the workflow: Same workflow code at every level:

CheckpointingStepRunner

Persists step outputs to a JDBC database. On restart with the same runId, completed steps are skipped — their cached output is returned directly.

How it works

  1. Before executing a step, queries by (runId, stepName) — the checkpoint key
  2. If a COMPLETED record exists, returns the cached outputPayload (skip)
  3. Otherwise, creates a STARTED record, executes the step, upgrades to COMPLETED with the serialized output
  4. On exception, records FAILED with the error message

Maven coordinates

Requires Spring Data JPA and a JDBC DataSource on the classpath. H2 works for development; Postgres or MySQL for production.

Restart semantics

runId is the stable identity for a workflow instance. COMPLETED steps are skipped permanently for that runId. FAILED steps are not automatically retried — the system leaves them in place until an operator explicitly decides to retry. This is intentional. Not all failures are transient: a bad prompt, a schema mismatch, or a programming error will fail again without a fix. Automatic retry would mask the real problem.

Crash-and-resume with CheckpointManager

When a step fails, call CheckpointManager.getRunState() to inspect what happened, then resetFailedSteps() only after confirming the failure was transient:
resetFailedSteps deletes FAILED records; COMPLETED records are untouched. The next execution creates a fresh STARTED record for each reset step and re-runs it.

Basic crash-and-resume example

A 4-step workflow crashes at step 3. After operator reset, steps 1-2 are skipped (cached), step 3 retried:
A complete runnable example is in workflow-dsl-examples/CrashRecoveryIT — @DataJpaTest + H2, no LLM needed.

JPA entities

Two JPA entities back the checkpoint system: Both use BatchStatus (severity-ordered enum) and ExitStatus (embeddable record with severity-based composition via and()).

Typed output deserialization

Each checkpoint stores the step’s output type alongside its serialized payload. On restore, CheckpointingStepRunner uses Class.forName(outputType) to deserialize back to the original type rather than raw Object. This means that when a step is skipped and its cached output is returned to the next step, the type is preserved:
Steps that declare outputType() participate fully. Step.named() lambdas return Object.class by default — deserialization falls back to Jackson’s type inference for those.

JdbcTraceRecorder

Records every step transition to a step_transitions table. Auto-creates the table on first use.
Each StepTransition record includes: run_id, workflow_name, from_step, to_step, timestamp, duration_ms, tokens_used, cost_usd, node_type, label, trace_path. The trace_path column stores the absolute path to the step’s JSONL trace file when using a trace-aware AgentClientStep — see Trace Capture. Query traces for a run:

TemporalStepRunner

Dispatches each step as a Temporal Activity. Steps must be registered with StepActivityImpl on the worker side.

Maven coordinates

Activity dispatch

Worker-side step registration

Steps are resolved by name from a ConcurrentHashMap registry. The activity creates a fresh AgentContext with the runId for each execution.
Steps dispatched via Temporal must be idempotent — Temporal may retry activities on timeout or failure.
Sub-workflows run inline, not as activities. A Workflow used as a step inside another Workflow bypasses the TemporalStepRunner and executes in-process. Only leaf steps are dispatched as Temporal activities. This is required for correct context propagation — the activity worker receives only the runId, not the full parent context.

API Reference

StepRunner interface, TraceRecorder, WorkflowExecutor

DSL Primitives

Sequential, parallel, gate, loop, branch, and more