# LangGraph basics: agents as graphs

A [LangChain chain](/guides/langchain-basics) runs one way, start to finish. Real
agents need to loop: call a model, run a tool, feed the result back, and go again
until the work is done. LangGraph models that as a graph of nodes and edges with
shared state, and it is what LangChain's own agents run on under the hood.

## Why use a graph instead of a chain?

A chain is a one-way pipe. A graph adds the three things an agent needs:

- **Cycles** · the model and its tools can pass control back and forth until a
  stopping condition is met.
- **Control flow** · conditional edges route to different nodes based on what
  just happened.
- **Shared state** · every node reads and updates one state object as it runs.

## What are the building blocks of a LangGraph?

LangGraph has a small vocabulary:

- **State** · a typed object that flows through the graph. For chat agents the
  key piece is a `messages` list with a reducer that appends new messages.The easy mistake is declaring `messages` as a plain `list` without the `add_messages` reducer. Without it every node return overwrites the whole history instead of appending, so the model loses the prior turns and the tool loop forgets what it just did. Annotate the field as `Annotated[list, add_messages]` so each update merges in.
- **Nodes** · functions that take the state and return an update to it.
- **Edges** · connect nodes. A plain edge always goes to the next node; a
  conditional edge picks the next node based on the state.
- **START and END** · the entry and exit sentinels.

```python
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from typing_extensions import TypedDict, Annotated

class State(TypedDict):
    messages: Annotated[list, add_messages]
```

## How do I build an agent loop in LangGraph?

Here is a complete agent: a model node, a tool node, and a conditional edge that
loops back whenever the model wants to call a tool.

```python
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langchain_anthropic import ChatAnthropic
from langchain.tools import tool
from typing_extensions import TypedDict, Annotated

@tool
def get_weather(city: str) -> str:
    """Return the weather for a city."""
    return f"Sunny in {city}"

tools = [get_weather]
model = ChatAnthropic(model="claude-sonnet-4-6").bind_tools(tools)

class State(TypedDict):
    messages: Annotated[list, add_messages]

def call_model(state: State):
    return {"messages": [model.invoke(state["messages"])]}

def should_continue(state: State):
    last = state["messages"][-1]
    return "tools" if last.tool_calls else END

builder = StateGraph(State)
builder.add_node("model", call_model)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "model")
builder.add_conditional_edges("model", should_continue, ["tools", END])
builder.add_edge("tools", "model")  # loop back
graph = builder.compile()

graph.invoke({"messages": [{"role": "user", "content": "Weather in SF?"}]})
```

The shape is the whole idea: the model runs, and if it asked for a tool, control
goes to the tool node and then *back* to the model. That cycle is what a flat
chain cannot express.

## Do I need to build the graph by hand?

For a standard agent you rarely need to wire the graph yourself. LangChain ships
a `create_agent` helper that builds this exact loop for you:

```python
from langchain.agents import create_agent

agent = create_agent(
    model="anthropic:claude-sonnet-4-6",
    tools=[get_weather],
    system_prompt="You are a helpful assistant.",
)
agent.invoke({"messages": [{"role": "user", "content": "Weather in SF?"}]})
```

Drop down to a hand-built `StateGraph` when you need control the prebuilt agent
does not give you: parallel nodes, custom branching, retries, or a supervisor
coordinating several workers.

## How do I add persistence and human-in-the-loop?

Two features turn a toy into something usable.

A **checkpointer** saves state between calls, keyed by a thread id, so a
conversation survives across invocations:Attaching a checkpointer but leaving the `thread_id` out of the config silently breaks persistence: with no key to load, every call starts a fresh conversation. Always pass the same `thread_id` to resume, and a new one to start a separate conversation.

```python
from langgraph.checkpoint.memory import MemorySaver

graph = builder.compile(checkpointer=MemorySaver())
config = {"configurable": {"thread_id": "conversation-1"}}
graph.invoke(inputs, config)
```

An **interrupt** pauses the graph to ask a human, then resumes with their answer:

```python
from langgraph.types import interrupt, Command

def review(state: State):
    decision = interrupt({"question": "Approve this action?"})
    return {"messages": [{"role": "user", "content": decision}]}

# First call pauses at the interrupt; resume with the human's value:
graph.invoke(Command(resume="yes, proceed"), config)
```

Interrupts need a checkpointer and a thread id, because pausing means saving the
whole state and picking it back up later.

With state, loops, and pauses, you can build capable agents, and [keep your agent on track](/guides/keep-your-agent-on-track) shows how to stop them looping forever. The next
two guides move to Anthropic's own [Claude Agent SDK](/guides/claude-agent-sdk)
for doing this with less wiring.
