| name | langgraph |
| description | LangGraph stateful agent framework: StateGraph, nodes, edges, conditional branching, persistence/checkpointing, human-in-the-loop, multi-agent subgraphs |
LangGraph Skill
When to activate
- Building a stateful agent that needs to pause, resume, or branch based on conditions
- Implementing human-in-the-loop workflows (agent pauses for approval before continuing)
- Orchestrating multi-step agents with complex branching logic
- Persisting agent state across failures or sessions (checkpointing)
- Building multi-agent systems where subgraphs collaborate
When NOT to use
- Simple single-turn AI calls — use the Claude API or Vercel AI SDK skill directly
- Linear pipelines with no branching — overkill, use sequential function calls
- When you need a hosted agent loop — use the Mastra skill instead
- Frontend streaming chat UIs — use the Vercel AI SDK skill
Instructions
Installation
npm install @langchain/langgraph @langchain/anthropic @langchain/core
pip install langgraph langchain-anthropic
Core concepts
LangGraph models agents as graphs:
- Nodes — functions that run and update state
- Edges — connections between nodes (always run → next node, or conditional)
- State — a typed object that flows through the graph and accumulates updates
- Checkpointer — persists state between runs (enables resume, human-in-the-loop)
Basic StateGraph (TypeScript)
import { StateGraph, Annotation, END, START } from '@langchain/langgraph'
import { ChatAnthropic } from '@langchain/anthropic'
import { tool } from '@langchain/core/tools'
import { z } from 'zod'
const AgentState = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (existing, update) => [...existing, ...update],
default: () => [],
}),
toolResults: Annotation<string[]>({
reducer: (existing, update) => [...existing, ...update],
default: () => [],
}),
})
const searchTool = tool(
async ({ query }) => `Search results for: ${query}`,
{ name: 'search', description: 'Search the web', schema: z.object({ query: z.string() }) }
)
const model = new ChatAnthropic({ model: 'claude-opus-4-7' }).bindTools([searchTool])
async function callModel(state: typeof AgentState.State) {
const response = await model.invoke(state.messages)
return { messages: [response] }
}
async function callTools(state: typeof AgentState.State) {
const lastMessage = state.messages[state.messages.length - 1]
const results = []
for (const toolCall of lastMessage.tool_calls ?? []) {
const result = await searchTool.invoke(toolCall.args)
results.push(new ToolMessage({ content: result, tool_call_id: toolCall.id }))
}
return { messages: results }
}
function shouldContinue(state: typeof AgentState.State) {
const lastMessage = state.messages[state.messages.length - 1]
if (lastMessage.tool_calls?.length) return 'tools'
return END
}
const graph = new StateGraph(AgentState)
.addNode('agent', callModel)
.addNode('tools', callTools)
.addEdge(START, 'agent')
.addConditionalEdges('agent', shouldContinue)
.addEdge('tools', 'agent')
.compile()
const result = await graph.invoke({
messages: [new HumanMessage('Research the latest AI news and summarise it')]
})
Persistence and checkpointing (resume after failure)
import { MemorySaver } from '@langchain/langgraph'
const checkpointer = new MemorySaver()
const graph = new StateGraph(AgentState)
.compile({ checkpointer })
const config = { configurable: { thread_id: 'user-123-session-1' } }
await graph.invoke({ messages: [new HumanMessage('Start research')] }, config)
await graph.invoke({ messages: [new HumanMessage('Continue')] }, config)
const state = await graph.getState(config)
console.log(state.values.messages)
Human-in-the-loop (interrupt before sensitive action)
import { interrupt } from '@langchain/langgraph'
async function reviewBeforeSend(state: typeof AgentState.State) {
const draft = state.messages[state.messages.length - 1]
const approved = interrupt({
message: 'Please review this draft before sending:',
draft: draft.content,
})
if (!approved) {
return { messages: [new HumanMessage('Draft rejected — please revise')] }
}
await sendEmail(draft.content)
return { messages: [new AIMessage('Email sent successfully')] }
}
const graph = new StateGraph(AgentState)
.addNode('draft', generateDraft)
.addNode('review', reviewBeforeSend)
.addNode('send', finaliseAndSend)
.(, )
.(, )
.(, )
.({ checkpointer, : [] })
result = graph.(input, config)
graph.( ({ : }), config)
graph.( ({ : }), config)
Multi-agent subgraphs
const researchGraph = new StateGraph(ResearchState)
.addNode('search', searchNode)
.addNode('extract', extractNode)
.addNode('summarise', summariseNode)
.addEdge(START, 'search')
.addEdge('search', 'extract')
.addEdge('extract', 'summarise')
.addEdge('summarise', END)
.compile()
const orchestratorGraph = new StateGraph(OrchestratorState)
.addNode('plan', planNode)
.addNode('research', researchGraph)
.addNode('write', writeNode)
.addNode('review', reviewNode)
.addEdge(START, 'plan')
.addEdge('plan', 'research')
.addEdge('research', 'write')
.addConditionalEdges(, needsRevision, { : , : })
.(, )
.({ checkpointer })
Python equivalent
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from langchain_anthropic import ChatAnthropic
from typing import TypedDict, Annotated
import operator
class AgentState(TypedDict):
messages: Annotated[list, operator.add]
model = ChatAnthropic(model="claude-opus-4-7").bind_tools(tools)
def should_continue(state: AgentState) -> str:
last = state["messages"][-1]
return "tools" if last.tool_calls else END
graph = (
StateGraph(AgentState)
.add_node("agent", lambda s: {"messages": [model.invoke(s["messages"])]})
.add_node("tools", ToolNode(tools))
.add_edge(START, "agent")
.add_conditional_edges("agent", should_continue)
.add_edge("tools", "agent")
.compile(checkpointer=MemorySaver())
)
result = graph.invoke(
{"messages": [HumanMessage("Research AI news")]},
config={"configurable": {"thread_id": "session-1"}}
)
Example
User: Build a content generation agent that: researches a topic, drafts an article, pauses for human review, then publishes — with state persisted so it can be resumed if interrupted.
Expected output:
AgentState with messages, draftContent, approved, published fields
- Nodes:
research, draft, review (with interrupt()), publish
MemorySaver checkpointer, thread_id per content piece
- API endpoint:
POST /generate (starts graph), POST /approve (resumes with Command({ resume: true }))
- State retrieval:
GET /status/:threadId using graph.getState(config)