NorthGradient
Start reading
Building Production Agents with LangGraph Browse lessons

Building Production Agents with LangGraph · Building Production Agents with LangGraph · 5 min read

State fundamentals

Get one thing right and most of this course follows: keep state and logic apart. State is what the agent knows. Logic is what it does.

State is what the agent knows. Logic is what it does. Never mix the two.

What state is

State is plain data. It holds the current task, results so far, a step counter, flags, and anything else the agent carries between steps. It has no methods and no logic. It is just a dictionary with named fields.

In LangGraph you define it as a TypedDict:

from typing import TypedDict

class AgentState(TypedDict):
    task:     str    # what the user asked for
    messages: list   # conversation so far
    result:   str    # final answer
    step:     int    # which step we are on

This is the agent’s notebook. Any node can read it, and any node can return an updated copy.

What logic is

Logic is a pure function. It takes the state, does its work (calls the model, fetches data, makes a choice), and returns a new state. It never writes to self, never touches a global, and never changes the state it was given.

def plan_step(state: AgentState) -> AgentState:
    plan = call_llm(f"Plan how to complete: {state['task']}")
    return {**state, "messages": state["messages"] + [plan]}

State in, new state out. Nothing else changes.

Why this matters

When state and logic are tangled together, three things get hard:

  • Testing. A pure function can be tested with a plain dictionary. No setup, no mocks. Pass in a fake state, check the result.
  • Replay. LangGraph can save state at a checkpoint and resume from it, but only if the state is clean, serializable data. Logic mixed into state cannot be saved this way.
  • Debugging. You can look at the exact state that went into each node and find which one produced the wrong result. Tangled state hides where the bug came from.

The pattern to avoid

# BAD: state and logic trapped in one object
class Agent:
    def __init__(self):
        self.state = {}

    def run(self):
        self.state["x"] = call_llm(...)   # logic mutates state in place

You cannot test this without building the whole agent. You cannot checkpoint it without writing custom save code. And when it fails, the state at the moment of failure is already gone.

Everything that follows is one idea applied again and again: keep data and logic apart, make transitions explicit, and keep every part of the agent easy to inspect and test. The next lesson turns this into five concrete rules for structuring an agent.