Agentic AI · Module 6 of 41 · 8 concepts · about 13 min

State Management

Stateless and stateful agents, what agent state and workflow state contain, how state is persisted and checkpointed so a task can resume, and how state machines keep long tasks under control.

At work: The difference between a demo that works while the laptop stays open and a service that survives deployments, crashes and a customer who replies two days later.

Stateless and stateful agents

What it is

Two designs, by where the record of the task lives:

  • A stateless agent keeps nothing between requests. Each request arrives with everything needed, is answered, and is forgotten.
  • A stateful agent keeps a record of the task between requests, so it can continue where it stopped.

Why it matters

The model itself is always stateless. The question is whether your system around it is.

  • Stateless is simple and scales easily: any server can handle any request.
  • Stateful is needed the moment a task spans more than one request: a conversation, a wait for approval, a job that runs for an hour.
  • The usual production design is stateless servers with state in a database: any server can load a task's state and continue it.

In the real world: An agent that kept conversations in a Python dictionary lost every live chat on each deploy; moving state to a database made deploys invisible to users.

Visual example

Comparison

Most production systems are the last row: stateless servers, stored state.

Stateless versus stateful agents
designstate lives insurvives a restartsuits
StatelessNowhereNothing to loseOne-shot tasks: classify, extract
Stateful in memoryThe server processNoDemos and notebooks only
Stateful, storedA database, keyed by task idYesConversations, long tasks, approvals

Keeping state in the server's memory. It works on one machine and breaks as soon as there are two servers, a restart or a deployment. State belongs in a store every server can reach.

Practise it: Design an AI Customer Support Agent

Agent state and workflow state

What it is

State is everything needed to continue a task from exactly where it is. It has two layers:

  • Agent state: what the model works from. The messages, tool requests and results so far; the plan; notes it has written.
  • Workflow state: what your code tracks about the run. Which stage it is in, steps and money used, retries so far, approvals pending, ids of actions already taken.

Why it matters

Keeping both, explicitly, is what makes an agent controllable.

  • Agent state lets the model pick up the thread.
  • Workflow state lets your code enforce limits and avoid repeating actions, without asking the model.
  • Typed state, defined as a class, makes it obvious what a run contains and catches mistakes early.

In the real world: After a crash mid-refund, one team's agent re-read its messages, saw no confirmation and refunded again; adding completed_actions to the state ended the double payments.

from typing import Literal
from pydantic import BaseModel

class RunState(BaseModel):
    # identity
    run_id: str
    user_id: str
    # agent state: what the model reads
    messages: list[dict] = []
    plan: list[str] = []
    # workflow state: what the code tracks
    status: Literal["running", "waiting_approval", "done", "failed", "handed_over"] = "running"
    step: int = 0
    cost_rupees: float = 0.0
    pending_approval: dict | None = None
    completed_actions: list[str] = []      # idempotency keys of writes already done

Storing only the messages. After a crash you can replay the conversation but cannot tell whether the refund in the last message was actually issued. Record completed actions as workflow state.

Persistent state and checkpointing

What it is

Two terms for making state survive:

  • Persistent state is state written to durable storage (a database), not held only in memory.
  • A checkpoint is a saved copy of the state at one moment, taken after each step, labelled with the run and step number.

The rule: save after every step, before starting the next.

Why it matters

A checkpoint after each step means the most you can ever lose is one step.

  • A crash at step 9 resumes from the checkpoint after step 8, not from the beginning.
  • Checkpoints are also a history: you can load step 5 to see exactly what the agent knew then.
  • They are cheap: one database write beside a model call that takes seconds.

In the real world: A research agent that used to restart a forty-minute job from zero after any failure now resumes from the last checkpoint; a failed run costs minutes, not the whole job.

def run(run_id: str) -> RunState:
    state = store.load(run_id)                       # resume, or start fresh
    while state.status == "running" and state.step < MAX_STEPS:
        reply = llm(state.messages, tools=TOOL_DEFS)
        state.messages.append(reply.as_message())

        if not reply.tool_calls:
            state.status = "done"
        for call in reply.tool_calls:
            key = f"{run_id}:{state.step}:{call.name}"       # same key on any retry
            result = execute(call, state.user_id, idempotency_key=key)   # safe to repeat
            if key not in state.completed_actions:
                state.completed_actions.append(key)
            state.messages.append(tool_result(call.id, result))

        state.step += 1
        store.save(state)                            # the checkpoint
    return state

Saving the checkpoint before the tool runs, or not at all on the failure path. Then the saved state says the action has not happened when it has. Pair every write with an idempotency key so re-running the step is harmless.

Practise it: Design a Multi-Agent AI System

Resumability

What it is

Resumability is the ability to stop a task and continue it later, from saved state. Tasks stop for four reasons:

  • a crash or deployment;
  • a wait for a person: an approval or a missing detail;
  • a wait for the world: a payment to settle, a file to be uploaded;
  • a deliberate pause: a budget reached, or a human taking over.

Why it matters

Real tasks are not finished in one sitting.

  • An approval may take minutes or days; no process should sit running while it waits.
  • With resumable state, waiting costs nothing: the run is a row in a database until something wakes it.
  • It also allows time travel: load an old checkpoint, change one input, and run again to see what would have happened.

In the real world: A loan agent pauses for a manager's sign-off on Friday evening and resumes on Monday morning on a different server, on a newer deployment, with no one noticing the gap.

Visual example

Resuming

Between the second and third box, nothing is running. The task is a row in a database.

A run that pauses for approval and resumes

  1. Run to the risky steprefund_item for ₹1,800
  2. Checkpoint and pausenothing runningSaved; supervisor notified
  3. Decision arrivesHours later, another server
  4. Load and continueResult added; loop resumes
def request_approval(state: RunState, call) -> RunState:
    """Pause the run. Nothing stays running while a person decides."""
    state.status = "waiting_approval"
    state.pending_approval = {"tool": call.name, "input": call.input, "call_id": call.id}
    store.save(state)
    notify_supervisor(state.run_id, state.pending_approval)
    return state

def on_decision(run_id: str, approved: bool, reviewer: str) -> RunState:
    """Called hours later by the approval screen. Loads state and carries on."""
    state = store.load(run_id)
    pending, state.pending_approval = state.pending_approval, None
    result = (execute_pending(pending, state.user_id) if approved
              else f"Declined by {reviewer}. Do not retry; explain to the customer.")
    state.messages.append(tool_result(pending["call_id"], result))
    state.status = "running"
    store.save(state)
    return run(run_id)

Resuming a run on new code that expects a different state shape. Version the state, and either migrate old runs or let them finish on the version they started with.

Practise it: Design a Multi-Agent AI System

State machines

What it is

A state machine is a way of describing a process as:

  • a fixed set of states it can be in (collecting details, verifying, awaiting approval, done);
  • the transitions allowed between them (verifying can go to approved or rejected, not straight to done);
  • the event that triggers each transition.

The process is always in exactly one state, and can only move along an allowed transition.

Why it matters

A state machine puts hard edges around a free-running model.

  • In each state the agent is offered only the tools that make sense there: no refund tool until verification has passed.
  • Impossible sequences cannot happen, however the model is persuaded.
  • Anyone can read the diagram and know every path the system can take, which is what auditors and incident reviews need.
  • Graph frameworks such as LangGraph (module 21) are state machines whose nodes may contain a model.

In the real world: A bank's dispute agent is a nine-state machine; the regulator's question 'can it pay out before identity is checked?' is answered by pointing at the diagram.

Visual example

State machine

The refund tool exists only in the fourth state.

The refund process as states

  1. CollectingOrder id and complaint
  2. VerifyingClaim against order and policy
  3. DecidingSmall goes on; large waits
  4. RefundingOnly state with refund_item
  5. ReplyingWhat exactly was done
TRANSITIONS = {
    "collecting":        {"details_complete": "verifying"},
    "verifying":         {"verified": "deciding", "cannot_verify": "handed_over"},
    "deciding":          {"small_refund": "refunding", "large_refund": "awaiting_approval",
                          "no_refund": "replying"},
    "awaiting_approval": {"approved": "refunding", "declined": "replying"},
    "refunding":         {"refund_ok": "replying", "refund_failed": "handed_over"},
    "replying":          {"sent": "done"},
}

TOOLS_BY_STATE = {
    "collecting": ["get_order"],
    "verifying":  ["get_order", "get_policy"],
    "refunding":  ["refund_item"],          # the only state in which money can move
}

def advance(current: str, event: str) -> str:
    try:
        return TRANSITIONS[current][event]
    except KeyError:
        raise ValueError(f"'{event}' is not allowed in state '{current}'")

Modelling everything as one big 'agent' state with all tools available. You lose the main benefit: being able to say with certainty that a refund cannot happen before verification.

Why state matters in long-running systems

What it is

As tasks get longer, state stops being a convenience and becomes the system. It is what provides:

  • Durability: work survives crashes and deployments.
  • Exactly-once effects: recorded actions are not repeated on retry.
  • Pausing: waits cost nothing.
  • Limits: steps, time and money are counted across the whole run.
  • Auditability: every step's inputs and outputs can be replayed.
  • Debugging: load the state at the failing step and look.

Why it matters

A five-step chat can be restarted if it breaks. A two-hour research job, a three-day claim or an overnight data run cannot.

  • The chance of some failure rises with every minute a task runs.
  • A long task without saved state is a bet that nothing will go wrong for its whole duration.
  • Systems built for this are called durable execution engines; module 23 shows where they sit.

In the real world: A team's overnight reconciliation agent failed at 4 a.m. three nights in a row at step 180 of 200; with checkpoints it now resumes at step 180 and finishes before the office opens.

Adding persistence after the agent 'works'. Retrofitting state into a loop written around in-memory variables usually means rewriting the loop. Decide the state shape first.

Interview questions: state management

What it is

The questions interviewers ask about state management, from the first screening call to the senior round. Cover the answer, say yours aloud, then compare.

Why it matters

Reading an idea and explaining it are different skills.

  • Each answer below is the two or three sentences an interviewer is listening for.
  • The reasoning behind each one is in the cards above. If an answer surprises you, go back to that card.

In the real world: An interviewer asks 'your agent is mid-refund and the pod is killed, walk me through it'; the answer they want uses three words: checkpoint, resume, idempotency.

Q: Stateless versus stateful agents?

A stateless agent keeps nothing between requests; a stateful one keeps the task's record so it can continue. In production the servers are stateless and the state is in a database keyed by run id, so any server can resume any task.

Q: What is in an agent's state?

The messages and tool results the model reads, the plan, and workflow fields the code tracks: status, step count, cost, pending approvals and the idempotency keys of completed actions.

Q: What is checkpointing?

Saving the full state after each step. A failure then loses at most one step, the run can be resumed on any machine, and old checkpoints can be loaded to inspect or replay.

Q: How does an agent wait two days for an approval?

It does not wait. It saves state with status 'waiting', notifies the approver, and stops. The approval event loads the state, appends the decision as a tool result, and restarts the loop.

Q: The process crashes mid-tool-call. What happens on resume?

The step is run again from the last checkpoint. Because each write carries an idempotency key derived from the run and step, the downstream service performs it once.

Q: Why use a state machine around an agent?

To restrict which tools and transitions are possible at each stage, so forbidden sequences cannot occur whatever the model outputs, and so the whole process can be read, tested and audited.

Q: How is state different from memory?

State is the exact, complete record of one run, kept until it ends. Memory is selected information kept across runs and retrieved by relevance.

Practise it: Design a Multi-Agent AI System

Exercise: make an agent survive a crash

What it is

A short build that uses this module's ideas. Write it yourself before looking anything up; the checks at the end tell you whether it works.

Why it matters

An idea you have only read fades in a week.

  • Building it once, even in fifty lines, is what makes it yours.
  • Interviewers can tell the difference between someone who has read about a loop and someone who has debugged one.

In the real world: A platform team uses this kill test as a release gate: no agent ships until it has been stopped at a random step and resumed without repeating a write.

Your task. Add checkpointing and resumption to a simple agent loop, then prove it by killing the process mid-task.

Steps

  1. Define a RunState class with messages, status, step, and completed actions.
  2. Write save(state) and load(run_id) using SQLite or one JSON file per run.
  3. Change your loop to load state at the start and save after every step.
  4. Give each write action an idempotency key of run id, step and tool name, and make the fake refund tool ignore a key it has seen.
  5. Add raise SystemExit inside the loop at step 3, run the task, then remove the line and run again with the same run id.

You are done when

  • The second run continues from step 3, not from step 0.
  • The fake refund was issued exactly once across both runs.
  • You can load the checkpoint for any step and print what the model had seen at that point.

Stretch. Add a waiting_approval status: the loop stops at a refund above ₹500, and a separate script approves it and resumes the run.