AI Engineer Summit 2025
Building Multi-agent Systems with Finite State Machines
Read the talk
Building Multi-Agent Systems with Finite State Machines
Statecharts make agent behavior explicit, from guarded tool calls and human approval to collaborating specialists, central planners, and generated workflows.
From a talk by Adam Terlson
Before you start: Familiarity with LLM tool calling and basic TypeScript will help; statecharts and actors are introduced from first principles.
What controls an autonomous agent?
An AI system can plan and act, but how do you make its behavior predictable, observable, and controllable? Better model performance alone does not answer that operational question. Adam Terlson starts with this gap: as models gain autonomy, the surrounding application needs a way to govern their decisions and actions.
Terlson points to agentic AI and AI governance platforms as the first two trends listed in Gartner’s 2025 outlook. Governance includes ethical, legal, and operational performance. State machines do not resolve all of those concerns; their contribution here is narrower and concrete: define which behaviors are possible and enforce how execution moves between them.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
A game makes the control model visible
Consider rock, paper, scissors. A finite state machine describes the game as a set of states, with events triggering transitions between them. The initial state is locking eyes. The players then select their throws in parallel, and the flow eventually reaches game over. These are declarative descriptions of the game’s modes, rather than scattered conditions whose combined meaning must be reconstructed from code.
Starting a round changes the state. Selecting rock, paper, or scissors can instead use a targetless transition: the event is handled without moving to another state. When the players shoot, a guard checks whether their throws match. A tie sends them back to select again; otherwise, the game continues toward its result.
Transitions can also run actions, such as sending a message or updating stored data. That data is the machine’s context: it supplies values for transition decisions and for the final output.
| Component | Role in the game |
|---|---|
| States | Locking eyes, selecting throws, game over |
| Transitions | Handle starting a round or selecting a throw |
| Guards | Check whether the throws match |
| Actions and effects | Update context or send messages |
| Context | Retain data used by decisions and output |
The distinction between state and context keeps the control flow readable: the state says what the game is doing, while context holds the data it needs to do it.
Strictly speaking, this example is a statechart. Statecharts extend finite state machines with a richer vocabulary, including hierarchical states and parallel states such as the two players’ simultaneous selections. Terlson uses the terms interchangeably for the remainder of the presentation, but the parallel behavior in the game depends on that richer model.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Actors execute the behavior; event history makes it recoverable
A statechart models behavior, but an application also needs an execution framework. The actor model supplies concurrent, distributed, encapsulated execution. An actor is an autonomous entity that communicates through messages; it is not synonymous with an AI agent. Its capabilities are deliberately small: send and receive messages, update its internal state, and create other actors. A state machine can define the actor’s internal logic, while messages connect it to the rest of the application.
Terlson recalls a Best Buy mentor—a senior principal security architect with more than forty years in IT—who had never met a finite state machine he disliked. The appeal is operational: explicit transitions make behavior predictable and provide a structure for tracing and auditing what happened. With an explicitly retained event history, replay can reconstruct lost state. Stopping replay at an earlier event also creates a point from which execution can branch. Recovery therefore depends on keeping the history and being able to interpret it, not merely on having drawn a statechart.
The same explicit model supports testing. Model-based testing can generate paths through the machine and check that tests exercise every valid transition. Terlson also highlights low-latency transition handling and declarative behavior: the control logic is available as a model that tools and operators can inspect. Transition coverage gives a precise testing target, though it does not by itself establish that every external effect is correct.
Modularity, adaptability, and scalability become especially useful when a workflow fails halfway through. In a saga, a transaction spans multiple requests. If a later step fails, compensating transactions undo the effects of earlier steps and return the customer to a valid state. The state machine can make both the forward path and the compensation path explicit, instead of treating recovery as an exception outside the workflow.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Explicit control brings explicit obligations
The cost of a declared transition model is limited flexibility. Trying to represent thousands of situations—or an effectively unbounded set—as separate states can produce state explosion. Transitions also have to be modeled before the machine can take them. In Terlson’s described handling, an unmodeled event leaves the machine unchanged and is logged for remediation. The machine does not invent a missing path.
Concurrency needs a separate design. Each actor processes one event at a time, even though many actors can run concurrently. Terlson names Azure Service Bus sessions and Amazon SQS FIFO message groups as relevant mechanisms. Their ordering applies within a session or message group, not globally across the application.
Versioning complicates the promise of replay. If a new machine definition removes a state, where should an old event sequence land? Historical events and the logic interpreting them must remain compatible enough to produce a meaningful result. That requirement, along with explicit transition modeling and the learning curve, belongs in the design from the beginning.
These constraints explain why state machines and LLMs can complement one another. A model call does not itself supply predictable transitions, recovery, or a declarative execution contract. Terlson’s architecture assigns different jobs to different components:
| Building block | Responsibility |
|---|---|
| Actor | Autonomy, execution, and communication |
| State machine | Structure and enforcement of allowed behavior |
| LLM | Dynamic interpretation and reasoning |
The model can make a decision without owning the rules that permit its execution. The following patterns progressively increase what the model decides while retaining an explicit control structure.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Give the recipe writer access to inventory
Tool use sits at the bottom of the showcase’s autonomy chart because it supplies a capability the later patterns need: a way for the model to act through the application. The tool protocol has three parts:
- Give the model descriptions of available methods and their expected arguments.
- Receive the model’s description of a requested call, then execute it in the caller.
- Return the tool response as the next message so the model can continue.
The LLM does not call the method directly. That boundary is where the application can govern execution.
Tools can provide external information as well as perform actions, supporting retrieval-augmented generation. In the recipe example, the writer first retrieves the available ingredients through getInventory, then creates a recipe using only those ingredients. The statechart loops from calling the model to using the tool and back to the model before reaching done. Inventory becomes part of the model’s working information rather than something it must guess.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Pause a proposed call for human approval
Once a model can request tools, the next question is how to control those requests. Human-in-the-loop behavior is a general state-machine capability: insert an intermediate approval state and wait. Execution resumes only in response to an authenticated and authorized message. In the demonstration, the administrator first denies and then approves a request. A proposed call and permission to execute it are separate pieces of state.
A compact TypeScript transition function illustrates that separation for the recipe writer’s inventory request. The trusted application boundary supplies the principal after authentication; the transition checks authorization. Approval returns an execution command to the caller, rather than pretending the tool has already run.
typescript
type ToolCall = {
name: "getInventory";
arguments: Record<string, never>;
};
type State = {
status: "awaitingApproval" | "denied" | "readyToExecute";
request: ToolCall;
};
type Principal = {
permissions: readonly string[];
};
type Decision = { type: "APPROVE" | "DENY" };
type Result = {
state: State;
commands: ToolCall[];
};
function decide(
state: State,
event: Decision,
principal: Principal | null,
): Result {
const canDecide = principal?.permissions.includes("approve-tools");
if (!canDecide || state.status === "readyToExecute") {
return { state, commands: [] };
}
if (event.type === "DENY") {
return {
state: { ...state, status: "denied" },
commands: [],
};
}
return {
state: { ...state, status: "readyToExecute" },
commands: [state.request],
};
}
const pending: State = {
status: "awaitingApproval",
request: { name: "getInventory", arguments: {} },
};
const admin: Principal = { permissions: ["approve-tools"] };
const denied = decide(pending, { type: "DENY" }, admin);
const approved = decide(denied.state, { type: "APPROVE" }, admin);
// approved.commands contains the call for the executor to perform.
Here, denial preserves the request for a later decision. Approval changes its execution eligibility; a subsequent tool response would record the result of actually running it.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
From a critic to a meal-delivery team
Feedback adds a second agent to the recipe workflow. The writer sends its output to a critic, perhaps a nutrition specialist. The critic responds with guidance such as “Use less sugar,” and the writer gets another opportunity to revise the recipe. Feedback can come from another agent, a human, or both. The mechanism is iterative revision of an output, not an automatic update to the model’s trained parameters.
Collaboration expands that pair into a team of specialists. Terlson motivates the decomposition through limitations in model training and the difficulty of scaling a single prompt to cover a broad, multifaceted goal. For meal delivery, the workflow includes recipe writing, ingredient procurement, meal preparation, feedback collection, and report publication. Each unit owns a narrower responsibility, and the state machine composes their work into a broader process.
That process need not terminate. The report and other accumulated context can return to the recipe writer as feedback for the next cycle. A state machine does not require a final state: it can govern an ongoing loop in which the sequence remains predictable while the content improves through feedback.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Let a planner choose the next transition
The collaboration workflow fixes the sequence of work. Agentic orchestration moves that sequencing decision into a central planning node. The planner reasons over context to decide what should happen next, whether the goal has been achieved, and whether the process can exit. Execution moves back and forth between task states and the planning agent.
In Terlson’s implementation, the planner’s tools are event emitters. The model requests an event, and transition guards determine whether it is valid and where execution may move. The planner therefore chooses among opportunities exposed by the machine; a model-generated event does not bypass the machine’s rules. This preserves an enforcement boundary even when the next step requires contextual reasoning.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Generate the process before executing it
A further step gives the LLM responsibility for generating the statechart itself. Terlson provisionally calls this agentic chartering; he does not present the name as established terminology. Where orchestration chooses the next step during execution, chartering separates planning from execution by first producing a process that can be run.
The proposed building materials are constrained and reusable: predetermined events, known context, available actors, and existing patterns. Terlson suggests prompting and fine-tuning as ways to help a model compose those materials into a new workflow. That shifts the planning output from a single routing decision to an explicit description of the process.
His motivating example is postal junk mail. He wants a personal agent that can remove him from the lists behind unwanted mail, so he gives o1 an article from Privacy Rights Clearinghouse describing removal from Whitepages and asks it to build a process. Terlson reports that the generated process was remarkably good, especially for an essentially zero-shot attempt with no examples. This is a qualitative report of process generation; the presentation does not establish that the opt-out was executed or completed.
A possible next step is a visualization agent that inspects the generated statechart and sends feedback to the chartering agent. Terlson invokes a long history of diagrams as potential training material, although his reference to seventy years of statecharts conflates them with the older history of state machines: David Harel’s bibliography dates the preliminary statecharts report to 1984 and the journal paper to 1987. Visual critique remains a proposed feedback loop here, not a measured capability. The suggestion ends with his joke that the solution to any agent problem is to add another agent.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Build larger behavior from specialized units
The showcase points toward emergence, but Terlson says he has not expressed that pattern with a statechart. His hypothesis is that useful emergent behavior will come from making agentic systems feel like building with Lego, rather than from modeling them more closely on the human brain. React Native applications, GitHub Actions deployments, and AWS infrastructure as code provide his analogies: specialized units compose into systems with capabilities beyond any one unit. He closes with the hope that state machines will remain foundational for decades to come.
All of the presentation’s statecharts and visualizations use XState, with particular praise for its v5 changes. That is the demonstrated version; the live documentation inspected for further reading now carries a v6-alpha label. For a continuation focused on operational behavior, Terlson points to Red vs. Blue, promising a surprise emergence at the end of that companion demo.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Resources
From the talk
Session IDs and exclusive receiver locks support ordered processing of related messages while allowing separate sessions to run concurrently.
Explains how MessageGroupId groups messages for ordered processing in an SQS FIFO queue.
Further reading
Adam Terlson's companion repository collects state-machine examples for tool use, feedback, collaboration, human approval, orchestration and chartering.
The speaker introduces his longer Code Freeze presentation and points readers to the Red vs Blue demonstration at 35:00.
Updates since the talk
Current documentation explains actor communication, internal state, mailboxes and lifecycle management; the retrieved site is labeled v6 alpha.
Current documentation covers saving snapshots, replaying stored events and handling restoration caveats; the retrieved site is labeled v6 alpha.
Read the complete timestamped transcript
- 0:04
Hi, I'm Adam Terlson, and in this video, I'll walk you through how finite state machines provide a structured, reliable foundation for building applications, including AI agents. As AI becomes more autonomous, the challenge isn't just intelligence or model performance.
- 0:23
It's also how to build systems with predictability, observability, and control, especially important qualities when orchestrating large language models.
- 0:35
So the year is twenty twenty-five, and the top AI trends, as listed by Gartner and probably everyone else too, is agentic AI, where autonomous AI plans and acts to achieve all our hopes and dreams.
- 0:50
But the number two trend called out by Gartner is just as, if not more important, and that is the development of AI governance platforms. We humans need to find a way to manage the ethical, legal, and operational performance of agentic systems.
- 1:09
Now, state machines probably don't have much to offer to the ethical or perhaps legal challenges of AI governance, but I believe they may have a foundational role to play in building platforms that effectively govern agent decision-making and behavior.
- 1:27
So let's dive right in. What is a finite state machine? Well, it's a computational model of a system with states and transitions that are triggered by events. The example shown here is for the game of rock, paper, scissors.
- 1:42
Our game is defined by five key components. The first is, are the state definitions. This is a declarative, uh, set of system modes and configurations. In our game, it's the initial state of locking eyes, the parallel states where players are selecting their throws, all the way to the final state when the game is over.
- 2:06
There is transition logic for how we move between these states when events occur, like when we start the round, uh, but these can also be targetless transitions, as is the case with rock, paper, and scissors.
- 2:20
On shoot, we apply a guard which enforces valid transitions. So on the case of shoot, we check if the throws match, and if they do, then we go back to select another throw.
- 2:33
Otherwise, we can continue on in the flow.
- 2:37
In each transition, we can apply actions and effects, side effects that are triggered by transitions, like sending a message or updating context. And context is stored data that is used for transition logic and also, uh, final output.
- 2:55
Now, the lovely nerds out there are likely yelling at their screens at this point saying, "That's not a state machine. It's a statechart." You're right. A statechart is an extension of a state machine that adds a richer vocabulary to the model, including things like hierarchical and parallel states, which I used in my game.
- 3:17
In this presentation, however, I may use these terms interchangeably. It'll be okay.
- 3:24
A statechart models an application's behavior, but on its own, it lacks essential capabilities needed to build a whole application. The Actor model complements statecharts by providing a framework for concurrent, distributed, and encapsulated execution.
- 3:43
That's right. Not an agent, an actor. So what is an actor? Uh, an actor, uh, is an autonomous entity that interacts with other actors via message passing. Actors in software only have three capabilities.
- 4:00
They can send and receive messages, they can update their internal state, and they can create new actors. And of course, finite state machines can be used to define that actor's internal behavior or logic.
- 4:15
So together, state machines and actors create a scalable and maintainable application architecture.
- 4:24
A mentor of mine at Best Buy said that in more-- in his more than forty years in IT, he's never met a finite state machine he did not like.
- 4:34
Why would he today, working as a senior principal security architect, say that? And the answer is because of the significant advantages that state machines bring to the operator. They are predictable.
- 4:47
They're traceable and auditable. They're reliable and recoverable. If you lose your current state, you can actually recalculate it by playing your-- replaying your log of events. If you only play back some of those events, stopping at a certain point in time, you can then create a new branch in history, effectively traveling through time.
- 5:11
They're low latency. They're declarative, as we saw with the statechart itself. They're easily tested. You can do what's called model-based testing, where you can be programmatically certain that your tests are exercising every valid transition in your machine.
- 5:28
They're modular and adaptable. They're scalable, and they allow you to handle the most complex error, error scenarios, even implementing what's called the sagas pattern for distributed transactions. In the sagas pattern, you have a transaction that takes place over multiple requests.
- 5:46
If a step in the process fails, you can apply compensating transactions to undo the previous steps and get the customer back into a valid state.
- 5:58
Of course, they also come with challenges. There's a limited, uh, flexibility to the system. And if you're trying to model, uh, uh, a system with thousands, perhaps infinite number of states, your state can really explode.
- 6:13
You have to model your transitions explicitly, and you can sometimes have, uh, have an issue where an event lands on your machine that you haven't modeled yet. And so thankfully, your machine doesn't do anything in that case, except for log it for remediation.
- 6:31
You do have to handle, uh, concurrency and parallelism because, as I mentioned before, the actor, uh, can only process one event at a time. This is where something like Azure Service Bus sessions and AWS SQS FIFO message groups would come into play.
- 6:50
Versioning is a definite challenge as maybe you have a, a state in one version that goes away in the next. Now, if you were to replay your log of events through your machine, what state do you land in?
- 7:02
You have to take this into account. And there's a learning curve associated with state machines. I think that curve is a joy, but you may be feeling something else right now.
- 7:14
What I want to draw your attention to is that these advantages brought by state machines are everything that LLMs are not. LLMs are not predictable. They're not traceable or auditable.
- 7:29
They're not reliable or recoverable. They're not declarative or low latency, et cetera. The opportunity is that by combining LLMs and state machines together, we can build a system that leverages the strengths of one to mitigate the weaknesses of the other.
- 7:50
So we have our building blocks of agentic systems. Our actors enable autonomy and communication. The state machine provides structure and enforces predictable behaviors, and the LLM enables dynamic intelligence.
- 8:08
So let's put these building blocks into action in the Agentic State Machines pattern showcase.
- 8:16
Our showcase begins, as did the human race, with tool use. Tool use is at the bottom of the autonomy chart because it really is foundational for the other patterns that follow.
- 8:26
That is, it gives the LLM the ability to act. In tool use, a description of available tools is passed to the LLM, including method names and, and expected arguments, and the LLM returns a response describing to the caller how the tool should be called.
- 8:44
The LLM doesn't call that method directly. The LLM then expects the next message, uh, in the flow to be the tool response.
- 8:54
This is motivated by especially needing to give additional capability and functionality to the LLM, allowing it to take action. Tools can also be used to improve response relevance via retrieval-augmented generation.
- 9:09
In this example, a model with a prompt to create a recipe is given a tool to fetch the inventory first, so that the recipe returned uses only the ingredients that are actually available.
- 9:23
Now, right away, we can talk about human-in-the-loop. Human-in-the-loop is something that state machines are naturally very good at. This doesn't have anything to do with agents. But we all know that LLMs are famously unreliable, so how do we put controls around its use of tools?
- 9:40
Well, we can simply add an intermediate, intermediate approval step and wait for an authenticated and authorized message to direct what happens next. In this example, the admin first denies and then approves the request.
- 9:59
Next, feedback. In feedback, instead of having one agent, we have two, and agents, just like humans, improve over time with feedback. We need to give LLMs time to think, and iterating with feedback, either from another agent, another human, or both, can dramatically improve response quality.
- 10:24
In this example, our recipe writer from before, uh, sends its output to the critic agent, perhaps a specialist in nutrition. The critic sends its feedback back, like, "Use less sugar," and the recipe writer has the opportunity to improve its, its response.
- 10:43
Here, we have two agents working directly together. But what if we want a whole team of agents? Well, that is collaboration.
- 10:56
With collaboration, multiple agents, each specializing in their own small slice of a task, work together to achieve a broader outcome. This is motivated by the training limitations of LLMs and the scaling limitations of prompting them.
- 11:13
We can't achieve broad, multifaceted goals with a single prompt to a model. Instead, we create specialized units and compose them together into a broader workflow. In this example of collaboration, multiple agents work together to deliver a meal to a customer.
- 11:34
We have the recipe writer from before, but also an agent specialized in procuring ingredients, one that is responsible for preparing the meal, getting feedback, and finally publishing a report.
- 11:48
State machines don't need to have a final state. In this example, the report, in any other context, is used by the recipe writer as feedback for how to continuously improve.
- 12:01
With state machines, that flow is predictable and tightly governed.
- 12:07
But what if we want to change that? What if we want to make the sequence of states itself determined by the LLM? And that is agentic orchestration.
- 12:20
With orchestration, a centralized planning node can direct the next state. This is helpful when reasoning around context is required to determine the next state. For example, by understanding if the goal has been achieved and whether it can exit the flow.
- 12:39
Now, the example that I'm showing here got a bit big, so I, I apologize that it's hard to see. But notice the bouncing back and forth between various states and the central planning agent.
- 12:51
The tool in this case that I gave to the planning agent was in fact a set of event emitters, and then I implemented transition guards to enforce that these transitions are valid and direct to the appropriate state.
- 13:08
So we're definitely having fun now. Beyond selecting the next state, how can we take this even further? Well, what if we gave the whole statechart over to the LLM and asked it to generate it?
- 13:25
I call this Agentic chartering. I wasn't able to find much about this pattern online. There may be a better name for it, and if you know of one, please let me know in the comments.
- 13:38
Chartering is helpful when you want to separate your planning phase from your execution phase. With chartering, the agent has the capability of inventing a wholly new and novel process to achieve its goal.
- 13:52
With prompting and fine-tuning, one can imagine LLMs being able to build these processes using off-the-shelf patterns and tools like predetermined events, known context, available actors, et cetera.
- 14:07
So for example, I hate postal junk mail. When I get something sent to my house, I do my best to get myself off whatever list they were using to send it to me.
- 14:20
Wouldn't it be nice if I had a personal agent [chuckles] that could do this work for me? To experiment with this, I gave an article I found on privacyrights.org for how to remove myself from the White Pages data broker, uh, and I gave this process to o1 to build a new process, uh, and it did a remarkable good
- 14:41
job, especially considering this was done essentially zero-shot with no examples, uh, given.
- 14:50
LLMs are also good at interpreting statecharts visually. Just think, we have seventy years of statecharts to train on. We could add a visualization agent as feedback to the chartering agent to get, uh, on how to improve its plan.
- 15:09
That's the fun thing about agents. Whatever the problem is, the solution is always to just add another agent.
- 15:18
So where is this graph leading? I think the answer is towards emergence, and now that's one pattern I haven't been able to express with a statechart yet. But if we are going to achieve something that performs and feels like emergence, I do not be-- I do not believe it will be because we've created a
- 15:43
system modeled more closely to the human brain. It'll be because we found a way to make building agentic systems feel like building with Legos. All the best technology, in my opinion, has this quality in common.
- 16:00
Whether you're building an app with React Native, deploying it with GitHub Actions, or provisioning services on AWS with infrastructure as code, each of these technologies have a model of composing specialized units together into a broader system that is greater than the sum of its parts.
- 16:19
So perhaps in achieving that, the humble state machine may have a foundational role to play for the next seventy years as well.
- 16:30
A special thank you to XState, the library I used for all the statecharts and visualizations in this presentation. If you haven't tried XState already, it's a really powerful tool for tackling system complexity, and it's a joy to use, especially with the changes they made in v5.
- 16:49
If you wanna see these patterns in action, check out my demo, Red vs. Blue, where I explore more about the operational side of state machines with a surprise emergence at the end.
- 17:03
Please reach out to me on LinkedIn if you have any questions, feedback, or just wanna connect. Thank you so much.