FSMBehavior - Finite State Machine¶
Overview¶
FSMBehavior models agent logic as a Finite State Machine: each state maps to a child Behavior, and guarded transitions move the FSM from one state to the next based on runtime conditions.
Since: v0.3.0 | Type: BehaviorType.FSM | Package: dev.agenor.runtime.behavior.composite | Extends: CompositeBehavior
Module:
agenor-runtime-ext(ADR-027) — not included inagenor-runtimealone.Declaring<dependency> <groupId>dev.agenor</groupId> <artifactId>agenor-runtime-ext</artifactId> </dependency>@Behavior(type = FSM)withoutagenor-runtime-exton the classpath fails atAgenorRuntime.start()withIllegalStateException.Scheduling note:
FSMBehaviorhas typeBehaviorType.FSM, which is not scheduled automatically by theSimpleBehaviorScheduler. You must drive it explicitly — see Basic Usage below.
Key Features¶
- ✅ State-behavior mapping — each state executes its own
Behavior - ✅ Guarded transitions —
Predicate<FSMBehavior>controls when to switch states - ✅ Transition actions — optional
Behaviorexecuted during a transition - ✅ State timeout — abort a state if it exceeds a configured duration
- ✅ Force transitions —
transitionTo()bypasses guards - ✅ Fluent builder — construct complex FSMs declaratively
Concepts¶
States: [IDLE] ──► [PROCESSING] ──► [DONE]
│ │
└─────────────────────────────┘ (reset → IDLE)
Transitions: guarded by Predicate<FSMBehavior>
optional: execute a Behavior during transition
On each call to execute():
1. The Behavior bound to currentState is executed.
2. Transitions from currentState are evaluated in insertion order.
3. The first matching transition fires, changing currentState.
Basic Usage¶
FSMBehavior is not scheduled automatically by SimpleBehaviorScheduler — its type BehaviorType.FSM is not in the set of types the scheduler drives. You must tick it explicitly using a CyclicBehavior driver that calls execute() at a fixed interval.
FSMBehavior fsm = FSMBehavior.builder("order-fsm", "IDLE")
.state("IDLE", new WaitForOrderBehavior())
.state("PROCESSING", new ProcessOrderBehavior())
.state("DONE", new NotifyCompletionBehavior())
.transition("IDLE", "PROCESSING", fsm -> orderQueue.hasItems())
.transition("PROCESSING", "DONE", fsm -> orderProcessor.isComplete())
.transition("DONE", "IDLE", fsm -> true) // always reset
.build();
// FSMBehavior is on-demand: a CyclicBehavior driver ticks it at regular intervals
CyclicBehavior driver = CyclicBehavior.from(
"order-fsm-driver",
Duration.ofMillis(300),
() -> fsm.execute());
agent.addBehavior(driver);
Common mistake: calling
agent.addBehavior(fsm)directly — the FSM is registered but never executed. Always add the driver, not the FSM itself.
Transitions with Actions¶
FSMBehavior fsm = FSMBehavior.builder("payment-fsm", "PENDING")
.state("PENDING", new AwaitPaymentBehavior())
.state("CONFIRMED", new FulfillOrderBehavior())
.state("FAILED", new RefundBehavior())
.transition("PENDING", "CONFIRMED",
fsm -> paymentService.isConfirmed(),
"payment-confirm",
new LogTransitionBehavior("CONFIRMED"))
.transition("PENDING", "FAILED",
fsm -> paymentService.hasFailed(),
"payment-fail",
new LogTransitionBehavior("FAILED"))
.build();
State Timeout¶
// Abort a state if it takes longer than 5 seconds
FSMBehavior fsm = FSMBehavior.builder("timed-fsm", "INIT", Duration.ofSeconds(5))
.state("INIT", new InitBehavior())
.state("RUNNING", new RunBehavior())
.transition("INIT", "RUNNING", fsm -> initialized)
.build();
Constructors¶
// Direct construction
new FSMBehavior(String behaviorId, String initialState)
new FSMBehavior(String behaviorId, String initialState, Duration stateTimeout)
API Reference¶
Builder¶
FSMBehavior.builder(String behaviorId, String initialState)
FSMBehavior.builder(String behaviorId, String initialState, Duration stateTimeout)
builder
.state(String name, Behavior behavior)
.transition(String from, String to, Predicate<FSMBehavior> condition)
.transition(String from, String to, Predicate<FSMBehavior> condition,
String transitionName, Behavior transitionAction)
.stateTimeout(Duration timeout)
.build() // throws IllegalStateException if initialState is not defined
Runtime Control¶
String state = fsm.getCurrentState();
boolean inIt = fsm.isInState("PROCESSING");
Set<String> s = fsm.getStateNames();
fsm.transitionTo("IDLE"); // force transition, ignores guards
fsm.reset(); // return to initialState
fsm.setStateTimeout(Duration); // change timeout at runtime
Duration t = fsm.getStateTimeout();
Scheduling Reference¶
| Type | Scheduled by SimpleBehaviorScheduler | Notes |
|---|---|---|
ONE_SHOT |
✅ Yes | Executed once immediately |
CYCLIC |
✅ Yes | Repeated at fixed interval |
FSM |
❌ No | On-demand — the owner calls execute(), typically from a CYCLIC behavior |
Error Handling¶
- If
currentStateis not found in the state map, the FSM logs an error, setsactive=false, and returns a failedCompletableFuture. - Exceptions thrown by state behaviors are caught and logged; the FSM continues.
- Timeout causes a
TimeoutExceptionwhich is logged as a warning; the FSM continues to evaluate transitions.
Use Cases¶
- Multi-step order processing workflows
- Connection lifecycle management (CONNECTING → CONNECTED → DISCONNECTED)
- Game agent AI (PATROL → CHASE → ATTACK → RETREAT)
- Protocol state machines
See Also¶
- SequentialBehavior - Linear step-by-step execution
- Behavior Overview