Skip to main content
This page describes the previously published legacy APIs. For the 0.14.0 typed executable-DSL milestone, its supported capabilities and recovery limits, see Agent Workflow. Durable loops, timers and Workflow Journal projection are deferred.

Why an Annotation Model?

Steps and workflows are the plumbing. Agents are the product β€” the thing you expose to HTTP endpoints, MCP servers, message listeners, and other agents. The annotation model gives you a programming model for declaring agents, naming them, describing them, handling their errors, and looking them up by name at runtime. If you’ve used @Controller + @ControllerAdvice in Spring MVC, you already know how this works. @Agent is to AgentHandler what @Controller is to your handler methods.

AgentHandler<I, O>

The entry-point contract for agents. A @FunctionalInterface that takes context and input, returns output:
Lambda form β€” for simple cases:
Class-based form β€” for production agents that compose a Workflow internally:
AgentHandler is pure Java β€” no Spring dependency. It lives in workflow-api so it’s portable across any runtime.

@Agent

Marks a class as a named agent and registers it as a Spring bean:
@Agent is a Spring @Component stereotype β€” classes annotated with it are picked up by component scanning. The value() is the unique agent name used for registry lookup and protocol addressing.

@StepName and @Description

Two metadata annotations for steps and agents:
@StepName pins a stable name for checkpoint and Temporal keys. When you rename the class, the checkpoint key stays the same β€” crash recovery and Temporal replay still match the right execution record. Without it, Step.name() (which defaults to the class simple name) is used. @Description provides a human-readable description for traces, visualization, and protocol tool listings. Can be placed on types or methods. Both are pure Java annotations β€” no Spring dependency, no runtime cost.

StepNames utility

StepNames.resolve(step) reads the @StepName annotation; falls back to step.name() if absent:

AgentRegistry

An immutable map of name β†’ handler. Manual construction β€” you decide what goes in:
The registry is defensive-copied at construction (Map.copyOf()). Subsequent changes to the source map don’t affect it.
Auto-configuration that scans @Agent beans and populates AgentRegistry automatically is planned but not yet implemented. For now, construct the registry manually or in a @Configuration class.

Exception Handling

Exception handling mirrors Spring MVC’s @ExceptionHandler + @ControllerAdvice pattern exactly. Two scopes:
  1. Per-agent β€” @ExceptionHandler methods inside an @Agent class handle that agent’s exceptions
  2. Cross-cutting β€” @ExceptionHandler methods inside an @AgentAdvice class handle exceptions from any agent
Per-agent handlers take priority. When multiple handlers match, the most specific exception type wins (subclass beats superclass).

Per-agent handling

Handler methods support two signatures:
  • ReturnType method(ExceptionType ex) β€” just the exception
  • ReturnType method(ExceptionType ex, AgentContext ctx) β€” exception plus context
The exception type can be declared explicitly in @ExceptionHandler(SomeException.class) or inferred from the method’s first parameter.

Cross-cutting handling

@AgentAdvice is a Spring @Component β€” picked up by component scanning, applies to all agents.

Using the resolver

AgentExceptionHandlerResolver ties it together. Pass it the @AgentAdvice instances at construction, then call resolve() on exception:
Resolution order: per-agent handlers on the agent instance first, then cross-cutting advice in registration order.

Spring Boot Wiring

In a Spring Boot application, wire the registry and resolver as beans in a @Configuration class:
Spring injects the @Agent and @AgentAdvice beans by type. The registry and resolver become injectable anywhere you need them β€” HTTP controllers, message listeners, MCP tool handlers.

Disambiguating steps with @Qualifier

When two steps share the same Step<I, O> signature, Spring can’t tell them apart by type alone. Use @Qualifier on both the bean definition and the injection point:
Then inject by qualifier in the agent constructor:
This is the standard Spring pattern β€” @StepName gives you a stable checkpoint key, @Qualifier gives you injection disambiguation.

Injecting Durability with withExecutor()

By default, Workflow.run() creates a WorkflowExecutor with LocalStepRunner (in-process, no persistence). Use withExecutor() to inject a configured executor with a durable StepRunner:
This works on both WorkflowBuilder and SupervisorBuilder. See Durability for the full graduation path from LocalStepRunner β†’ CheckpointingStepRunner β†’ TemporalStepRunner.

Full Example

A complete PR review agent using all 5 annotations:
A runnable version of this example β€” with real LLM calls, AgentRegistry lookup, StepNames resolution, and exception handler tests β€” is in AnnotationModelIT.java in the workflow-dsl-examples repo.

Annotation Summary

The first three are pure Java β€” zero Spring dependency, portable across any runtime. The last two are Spring stereotypes that enable component scanning and auto-discovery.

What’s Next

Durability

Crash recovery with CheckpointingStepRunner and Temporal integration

DSL Primitives

Branch, loop, parallel, decision, gate, supervisor β€” 10+ composable patterns

API Reference

Step, AgentContext, Gate, WorkflowGraph, StepRunner

Getting Started

Steps, context, your first workflow