State machines for agents

RoadmapsAI Engineering

Scenario

Two parallel agent nodes run at the same time. They both return a dictionary with the key `messages`.

Does one node's output accidentally overwrite the other's, causing a race condition?

Mental model

Think of agent state as a shared clipboard that gets passed from worker to worker.

In a state machine graph, each node (agent or tool) takes the current state, does its work, and returns an update. Instead of overwriting the whole clipboard, rules called 'reducers' decide how to safely merge the new notes with the old notes.

Deep dive

Production agents no longer use basic chains; they use **directed graphs (DAGs)**. In frameworks like LangGraph, the AI and tools are 'nodes'. The shared memory passed between them is the 'state'.

Graphs pass a shared state object between execution nodes.

When a node finishes, it doesn't just overwrite the state variable. If it returns a list of messages, the framework needs to know: do I replace the old messages, or add to them? We solve this with **reducers** (like `add`), which safely concatenate outputs—even from parallel nodes.

Reducers merge updates instead of overwriting the previous state.

Python's `TypedDict` is often used for state, but it has a fatal flaw: it doesn't check data at runtime. A rogue tool might inject junk data into the state, polluting it. To stop this, production systems use strict data validators like **Pydantic** to reject bad data immediately.

TypedDict allows silent state pollution; Pydantic enforces strict boundaries.

Prediction: If a developer uses a basic `TypedDict` and a tool accidentally returns a nested dictionary instead of a string, the agent might run fine for a minute, then crash completely three steps later when another node tries to read it.

Always use runtime validation for agent state to catch errors early.

Code examples

A strict, reducer-enabled state schema

from pydantic import BaseModel, ConfigDict
from typing import Annotated
import operator

class AgentState(BaseModel):
    # Forbid extra random keys to prevent state pollution
    model_config = ConfigDict(extra='forbid')
    
    # The 'add' reducer safely combines lists
    messages: Annotated[list, operator.add]
    current_task: str

This Pydantic model guarantees that rogue tools cannot add illegal keys (thanks to `extra='forbid'`). The `operator.add` reducer ensures that parallel nodes returning messages will concatenate their lists rather than overwriting each other.

Common mistakes

Glossary

state machine
A system that moves between different states (like nodes in a graph) while passing shared data along.
reducer
A function that tells the framework how to combine new data with old data (e.g., adding to a list instead of overwriting it).
pollution
When rogue data accidentally gets written into the agent's state, causing unexpected crashes downstream.

Recall questions

Questions & answers

A developer complains that their LangGraph state is losing the early conversation history every time the agent loops. What is the most likely architectural flaw?

The developer likely forgot to apply an `operator.add` reducer to the messages field in their state schema, causing the framework to overwrite the list entirely on each node execution instead of appending to it.

Approach: Identify the role of reducers in graph-based state management.

How do you prevent a rogue tool from injecting arbitrary nested dictionaries into your agent's state?

By defining the state schema using Pydantic BaseModels with `extra='forbid'` rather than Python's basic TypedDict. This enforces runtime validation and instantly rejects illegal mutations.

Approach: Focus on runtime validation boundaries versus static type hinting.

Continue learning

Previous: Agent autonomy spectrum

Next: Multi-agent orchestration patterns

Next: Agent checkpointing & resumability

Return to AI Engineering Roadmap