DEV Community

Cover image for Your AI Agents Need Finite State Machines (FSMs)

Your AI Agents Need Finite State Machines (FSMs)

Remo H. Jansen on July 28, 2026

In my previous article, I argued that AI changes the role of constraints in software development. For years, many developers saw constraints as so...
Collapse
 
mia_keller_ffd2584c046ecb profile image
Mia Keller

Excellent point on using FSMs to keep agents bounded! When scaling complex multi-agent workflows, do you recommend combining traditional state charts (like XState) with LLM tool-calling, or keeping the state logic entirely decoupled in a separate orchestration layer?

Collapse
 
remojansen profile image
Remo H. Jansen

What has worked for me so far is modeling business workflows as an FSM. I explored XState for this, but ultimately decided to build a custom DSL-like interface that uses ubiquitous language (DDD). Under the hood, it's essentially just an FSM.

You then expose this FSM as an MCP server. You could have multiple FSMs (and therefore multiple MCP servers) and multiple agents. The exact architecture depends on your needs. For example, you could have a 1 agent β†’ 1 MCP β†’ 1 FSM mapping, or something like N agents β†’ N MCPs β†’ N FSMs.

I suspect that keeping the mapping 1:1:1 will perform better because it reduces the decision space and increases specialization.

Collapse
 
mia_keller_ffd2584c046ecb profile image
Mia Keller

That 1:1:1 mapping (1 Agent : 1 MCP : 1 FSM) makes a lot of sense for keeping the context window focused and error rates low!

When you need to hand off control between these specialized agents, do you handle orchestration at a parent agent level, or do the individual FSMs trigger state transitions that delegate directly to another agent’s MCP?

Thread Thread
 
remojansen profile image
Remo H. Jansen

I would preffer if the FSM creates and event that can be picked by another agent. This is easier to verify than the parent agent level pattern.

Collapse
 
hannune profile image
Tae Kim

The "LLMs are probabilistic, business processes should not be" framing nails exactly why FSMs matter for agent reliability. I've found that FSMs shine most when the state transitions carry semantic weight that the model otherwise has to guess β€” an agent that can query "what transitions are valid from here?" against a formal model makes far fewer hallucinated workflow jumps than one navigating implicit state through prompt instructions alone. The verifiability property you mention is underrated: being able to prove that every process path reaches a terminal state is a guarantee no prompt can offer.

Collapse
 
vinimabreu profile image
Vinicius Pereira

The strongest consequence of this inversion is what happens on failure. When the model answers "what should happen next" and gets it wrong, the wrong thing executes. When it answers "which valid transition fits best" and gets it wrong, you get a typed rejection you can route: retry, escalate, or park for a human. The FSM does not just constrain the happy path, it gives bad model output somewhere safe to land.

The other underrated gift: the state graph is testable with the LLM completely out of the loop. You can property-test that no path skips an approval or double-executes a purchase before a single prompt exists, and then swap models without retesting the process itself. I build integration flows on exactly this split, the flow routes and a boring tested service decides, and every incident I have debugged in that setup was in the seam, never in the graph.

Collapse
 
remojansen profile image
Remo H. Jansen

About your concern. Yes mistakes are still possible (just like human mistakes). We are just trying to make the odds smaller and that is why your "underrated gift" is so valuable.

Collapse
 
vinimabreu profile image
Vinicius Pereira

Exactly. The goal was never zero mistakes, that bar does not exist for humans either. It is making the mistakes cheap, typed and routable, and the FSM is the cheapest place I know to buy that property. Good post, it deserves the traction.

Collapse
 
reidmarlow profile image
Reid Marlow

The underrated win with FSMs is replay. Once the workflow state is explicit, you can inspect why the agent had only three legal moves at that point instead of trying to reconstruct intent from a transcript after the damage is done. I would still keep payload validation next to the transition, otherwise the graph is deterministic but the data riding through it is not.

Collapse
 
skillselion profile image
Skillselion

Exposing only the valid transitions can be pushed one level deeper: let the state machine own the tool list itself. When the process sits in Submitted, tools/list returns approve, reject, request_more_info and nothing else. The model cannot invent an invalid transition because the capability does not exist at that moment, which is a far stronger guarantee than a prompt line listing the legal moves. MCP servers can do this today by re-advertising tools on state change.

Where this gets hard in practice is compensation. Forward transitions map cleanly onto an FSM, but rollback usually needs information from the failed attempt (how far the purchase got, what was already charged), which tempts teams to hand cleanup back to the agent's judgment, precisely where probabilistic behavior hurts most. Did you end up modeling compensations as first-class states with their own transitions, or as actions attached to failure edges?

Collapse
 
remojansen profile image
Remo H. Jansen

In my case if a FSM action fails with something other than a 400 the agent has no way to recover. (we monitor failures and a developer follows up on what went worng). It is also a good idea to provide as much infromation as possible in 400 errors instead of returning "Invalid request" return something like "Invoice number doesn't match current user ID" something that the agent can reason about.

The FSM does allow the agent to rollback some of the actions but that is hard coded in specific nodes.

Collapse
 
cailab profile image
CAI

The FSM pattern for agent workflows is something I have been thinking about too, and your framing of AI navigating the workflow instead of owning it really clarifies the architectural shift. The hardest part in practice is not defining the happy-path states and transitions. It is deciding how much error-handling and edge-case logic to encode in the FSM versus letting the agent handle dynamically. Too rigid and the agent cannot recover from unexpected failures. Too loose and you lose the safety guarantees that make the FSM approach worthwhile. The MCP integration you mentioned at the end feels like a natural fit here, since the tool contract itself can encode availability constraints, making the state machine a property of the interface rather than a separate layer. One question I keep coming back to is whether you have found a good heuristic for where to draw the line between rigidity and flexibility in practice.

Collapse
 
remojansen profile image
Remo H. Jansen

"A good heuristic for where to draw the line between rigidity and flexibility in practice" I think is hard to document because it has a case-by-case context-dependent answer. The only think I'm sure of is that when possible your constraints should be explicit so they become part of the agent context. I wrote a big article about this already if you are interested:

dev.to/remojansen/from-rigidity-to...

Collapse
 
xm_dev_2026 profile image
Xiao Man

The FSM-as-boundary framing maps nicely onto a pattern I keep running into: agent reliability tracks with how explicitly the state machine is separated from the prompt.

When the workflow lives in the prompt, you get what you'd expect β€” drift that's hard to reproduce because the constraint surface is soft. When it lives in an explicit FSM, failures become enumerable. You can actually mutation-test your agent's guardrails.

One thing I'd push on: the MCP integration is the part that makes or breaks this for production use. If the agent can bypass the FSM by calling tools directly (skipping state validation), you've just added documentation, not enforcement. The FSM needs to be the only path to side effects, not a parallel track the agent can ignore. -s devto

Collapse
 
valentin_monteiro profile image
Valentin Monteiro

The FSM bounds which transition fires, not what rides along with it. Picking "Approve" from three valid options is safe. The amount or vendor ID the model extracted and attached to that transition is still a guess, and the graph will accept it happily. Do you validate transition payloads at the same layer, or is that a separate guard?

Collapse
 
remojansen profile image
Remo H. Jansen

Yes, payload validation belongs inside the FSM boundary when it represents workflow/business rules. The FSM should own guards and transition contracts. However, validating that the payload is factually grounded may require external evidence or a trusted source, because no state machine can verify facts it cannot observe.

Collapse
 
motedb profile image
mote

The verifiability argument is the strongest part of this. Being able to answer "can this workflow reach an invalid state?" before deploying is something prompt-driven agents structurally cannot offer.

One thing I'd add from the robotics side: we've used FSMs for robot control for decades, and the pattern that keeps surviving is Harel statecharts (hierarchical FSMs with orthogonal regions). Plain FSMs work for 5-10 states. Past that, you get state explosion where every error condition needs its own state, and the transition matrix becomes unreadable. Statecharts let you nest states and run parallel regions, which handles "the robot is navigating AND monitoring battery AND listening for safety stops" without enumerating every combination.

The tension I'd flag: your example workflow (Draft to Submitted to Approved to Purchased) is clean because it's linear. Most business processes have exception paths that branch and merge. How do you model "Request More Information" in an FSM? It's not a forward transition. It loops back, potentially multiple times, and each loop might carry different context. A pure FSM treats it as a new state each time (Submitted_v1, Submitted_v2...), which defeats the simplicity argument. Statecharts solve this with history states, but then you're not really using a plain FSM anymore.

Have you looked at how XState handles this? Their actor model plus statechart combination is the closest I've seen to an FSM that doesn't collapse under real-world complexity.

Collapse
 
gagan_chouhanhmgagan_4 profile image
Gagan chouhan h m Gagan

Uiui