Generated banner for Langgraph Fundamentals
Generated logo for Langgraph Fundamentals

Langgraph Fundamentals

LangChainOfficialPublished by LangChain

  • Skill
  • Tool integration
  • Software development
  • MIT
  • No issues found

From the author

INVOKE THIS SKILL when writing ANY LangGraph code. Covers StateGraph, state schemas, nodes, edges, Command, Send, invoke, streaming, and error handling.
— the description by LangChain

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

LangGraph is a framework for building stateful, multi-actor applications with LLMs by modeling workflows as directed graphs. This skill provides the core concepts for implementing StateGraphs, including node definitions, edge routing, state management with reducers, and execution patterns like invoke and stream. It covers both Python and TypeScript implementations, ensuring developers can build complex, branching, or looping agentic workflows with persistence and human-in-the-loop capabilities.

What it does

  • Defines state schemas with reducers for data accumulation.
  • Structures agent workflows as directed graphs with nodes and edges.
  • Implements conditional routing and parallel fan-out using the Send API.
  • Provides patterns for stateful execution and streaming results.
  • Integrates error handling strategies for transient and logic-based failures.
  • Supports both Python and TypeScript development environments.

When to use it

  • When you need fine-grained control over agent orchestration.
  • When building complex workflows that require branching or loops.
  • When your application requires persistence or human-in-the-loop interactions.
  • When you need to manage shared state across multiple agent steps.

Advantages

  • Enables complex, non-linear agentic workflows.
  • Provides robust state management through custom reducers.
  • Supports advanced patterns like parallel execution and dynamic routing.
  • Offers clear separation between state logic and execution flow.

Usage examples

“How do I define a state schema in Python that appends messages instead of overwriting them?”
The agent explains using Annotated with operator.add to ensure the list is updated via a reducer rather than replaced.
“Show me how to route between nodes conditionally in a TypeScript LangGraph project.”
The agent provides a code example using addConditionalEdges and a routing function that returns the next node name based on state.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/langchain-ai/langgraph-fundamentals/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip langgraph-fundamentals.zip -d .agents/skills

Documentation by LangChain

<overview> LangGraph models agent workflows as **directed graphs**:

  • StateGraph: Main class for building stateful graphs

  • Nodes: Functions that perform work and update state

  • Edges: Define execution order (static or conditional)

  • START/END: Special nodes marking entry and exit points

  • State with Reducers: Control how state updates are merged

Graphs must be compile()d before execution. </overview>

<design-methodology>

Designing a LangGraph application

Follow these 5 steps when building a new graph:

  1. Map out discrete steps — sketch a flowchart of your workflow. Each step becomes a node.

  2. Identify what each step does — categorize nodes: LLM step, data step, action step, or user input step. For each, determine static context (prompt), dynamic context (from state), retry strategy, and desired outcome.

  3. Design your state — state is shared memory for all nodes. Store raw data, format prompts on-demand inside nodes.

  4. Build your nodes — implement each step as a function that takes state and returns partial updates.

  5. Wire it together — connect nodes with edges, add conditional routing, compile with a checkpointer if needed.

</design-methodology>

<when-to-use-langgraph>

Use LangGraph WhenUse Alternatives When
Need fine-grained control over agent orchestrationQuick prototyping → LangChain agents
Building complex workflows with branching/loopsSimple stateless workflows → LangChain direct
Require human-in-the-loop, persistenceBatteries-included features → Deep Agents

</when-to-use-langgraph>


State Management

<state-update-strategies>

NeedSolutionExample
Overwrite valueNo reducer (default)Simple fields like counters
Append to listReducer (operator.add / concat)Message history, logs
Custom logicCustom reducer functionComplex merging

</state-update-strategies>


Nodes

<node-function-signatures>

Node functions return partial state updates. Signatures for configuration and runtime access differ by language; use the applicable implementation reference.

</node-function-signatures>


Edges

<edge-type-selection>

NeedEdge TypeWhen to Use
Always go to same nodeadd_edge()Fixed, deterministic flow
Route based on stateadd_conditional_edges()Dynamic branching
Update state AND routeCommandCombine logic in single node
Fan-out to multiple nodesSendParallel processing with dynamic inputs

</edge-type-selection>


Command

Command combines state updates and routing in a single return value. Fields:

  • update: State updates to apply (like returning a dict from a node)

  • goto: Node name(s) to navigate to next

  • resume: Value to resume after interrupt() — see human-in-the-loop skill

<command-return-type-annotations>

Python: Use Command[Literal["node_a", "node_b"]] as the return type annotation to declare valid goto destinations.

TypeScript: Pass { ends: ["node_a", "node_b"] } as the third argument to addNode to declare valid goto destinations.

</command-return-type-annotations>

<warning-command-static-edges>

Warning: Command only adds dynamic edges — static edges defined with add_edge / addEdge still execute. If node_a returns Command(goto="node_c") and you also have graph.add_edge("node_a", "node_b"), both node_b and node_c will run.

</warning-command-static-edges>


Send API

Fan-out with Send: return [Send("worker", {...})] from a conditional edge to spawn parallel workers. Requires a reducer on the results field.


Running Graphs: Invoke and Stream

<invoke-basics>

Call graph.invoke(input, config) to run a graph to completion and return the final state.

</invoke-basics>

<stream-mode-selection>

ModeWhat it StreamsUse Case
valuesFull state after each stepMonitor complete state
updatesState deltasTrack incremental updates
messagesLLM tokens + metadataChat UIs
customUser-defined dataProgress indicators

</stream-mode-selection>


Error Handling

Match the error type to the right handler:

<error-handling-table>

Error TypeWho FixesStrategyExample
Transient (network, rate limits)SystemRetryPolicy(max_attempts=3)add_node(..., retry_policy=...)
LLM-recoverable (tool failures)LLMToolNode(tools, handle_tool_errors=True)Error returned as ToolMessage
User-fixable (missing info)Humaninterrupt({"message": ...})Collect missing data (see HITL skill)
UnexpectedDeveloperLet bubble upraise

</error-handling-table>


Core boundaries

  • Return partial state updates from nodes instead of mutating state directly.

  • Route loops through a named node; START is entry-only.

  • Define reducers for accumulated list fields; otherwise, the last write wins.

  • Account for static edges when using Command with goto, because both routes execute.

Implementation references

If writing, modifying, or debugging LangGraph code, determine the project's language from its existing files, then read the applicable reference before implementing:

Read both only when the task covers both languages. For conceptual questions that require no code, do not load either reference.

Files

references/python.md

Python implementation reference

Use this reference only for LangGraph projects written in Python. The shared concepts, decisions, and invariants remain in ../SKILL.md.

Contents

  • State Management

  • Nodes

  • Edges

  • Command

  • Send API

  • Running Graphs: Invoke and Stream

  • Error Handling

  • Common Fixes

State Management

State with reducer

Define state schema with reducers for accumulating lists and summing integers.

from typing_extensions import TypedDict, Annotated
import operator

class State(TypedDict):
    name: str  # Default: overwrites on update
    messages: Annotated[list, operator.add]  # Appends to list
    total: Annotated[int, operator.add]  # Sums integers
Forgot reducer for list

Without a reducer, returning a list overwrites previous values.

# WRONG: List will be OVERWRITTEN
class State(TypedDict):
    messages: list  # No reducer!

# Node 1 returns: {"messages": ["A"]}
# Node 2 returns: {"messages": ["B"]}
# Final: {"messages": ["B"]}  # "A" is LOST!

# CORRECT: Use Annotated with operator.add
from typing import Annotated
import operator

class State(TypedDict):
    messages: Annotated[list, operator.add]
# Final: {"messages": ["A", "B"]}
Return partial state updates

Nodes must return partial updates, not mutate and return full state.

# WRONG: Returning entire state object
def my_node(state: State) -> State:
    state["field"] = "updated"
    return state  # Don't mutate and return!

# CORRECT: Return dict with only the updates
def my_node(state: State) -> dict:
    return {"field": "updated"}

Nodes

Node function signatures
SignatureWhen to Use
def node(state: State)Simple nodes that only need state
def node(state: State, config: RunnableConfig)Need thread_id, tags, or configurable values
def node(state: State, runtime: Runtime[Context])Need runtime context, store, or stream_writer
from langchain_core.runnables import RunnableConfig
from langgraph.runtime import Runtime

def plain_node(state: State):
    return {"results": "done"}

def node_with_config(state: State, config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]
    return {"results": f"Thread: {thread_id}"}

def node_with_runtime(state: State, runtime: Runtime[Context]):
    user_id = runtime.context.user_id
    return {"results": f"User: {user_id}"}

Edges

Basic graph

Simple two-node graph with linear edges.

from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict

class State(TypedDict):
    input: str
    output: str

def process_input(state: State) -> dict:
    return {"output": f"Processed: {state['input']}"}

def finalize(state: State) -> dict:
    return {"output": state["output"].upper()}

graph = (
    StateGraph(State)
    .add_node("process", process_input)
    .add_node("finalize", finalize)
    .add_edge(START, "process")
    .add_edge("process", "finalize")
    .add_edge("finalize", END)
    .compile()
)

result = graph.invoke({"input": "hello"})
print(result["output"])  # "PROCESSED: HELLO"
Conditional edges

Route to different nodes based on state with conditional edges.

from typing import Literal
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    query: str
    route: str
    result: str

def classify(state: State) -> dict:
    if "weather" in state["query"].lower():
        return {"route": "weather"}
    return {"route": "general"}

def route_query(state: State) -> Literal["weather", "general"]:
    return state["route"]

graph = (
    StateGraph(State)
    .add_node("classify", classify)
    .add_node("weather", lambda s: {"result": "Sunny, 72F"})
    .add_node("general", lambda s: {"result": "General response"})
    .add_edge(START, "classify")
    .add_conditional_edges("classify", route_query, ["weather", "general"])
    .add_edge("weather", END)
    .add_edge("general", END)
    .compile()
)

Command

Command state and routing

Command lets you update state AND choose next node in one return.

from langgraph.types import Command
from typing import Literal

class State(TypedDict):
    count: int
    result: str

def node_a(state: State) -> Command[Literal["node_b", "node_c"]]:
    """Update state AND decide next node in one return."""
    new_count = state["count"] + 1
    if new_count > 5:
        return Command(update={"count": new_count}, goto="node_c")
    return Command(update={"count": new_count}, goto="node_b")

graph = (
    StateGraph(State)
    .add_node("node_a", node_a)
    .add_node("node_b", lambda s: {"result": "B"})
    .add_node("node_c", lambda s: {"result": "C"})
    .add_edge(START, "node_a")
    .add_edge("node_b", END)
    .add_edge("node_c", END)
    .compile()
)

Send API

Orchestrator worker

Fan out tasks to parallel workers using the Send API and aggregate results.

from langgraph.types import Send
from typing import Annotated
import operator

class OrchestratorState(TypedDict):
    tasks: list[str]
    results: Annotated[list, operator.add]
    summary: str

def orchestrator(state: OrchestratorState):
    """Fan out tasks to workers."""
    return [Send("worker", {"task": task}) for task in state["tasks"]]

def worker(state: dict) -> dict:
    return {"results": [f"Completed: {state['task']}"]}

def synthesize(state: OrchestratorState) -> dict:
    return {"summary": f"Processed {len(state['results'])} tasks"}

graph = (
    StateGraph(OrchestratorState)
    .add_node("worker", worker)
    .add_node("synthesize", synthesize)
    .add_conditional_edges(START, orchestrator, ["worker"])
    .add_edge("worker", "synthesize")
    .add_edge("synthesize", END)
    .compile()
)

result = graph.invoke({"tasks": ["Task A", "Task B", "Task C"]})
Send accumulator

Use a reducer to accumulate parallel worker results (otherwise last worker overwrites).

# WRONG: No reducer - last worker overwrites
class State(TypedDict):
    results: list

# CORRECT
class State(TypedDict):
    results: Annotated[list, operator.add]  # Accumulates

Running Graphs: Invoke and Stream

Invoke basics
result = graph.invoke({"input": "hello"})
# With config (for persistence, tags, etc.)
result = graph.invoke({"input": "hello"}, {"configurable": {"thread_id": "1"}})
Stream llm tokens

Stream LLM tokens in real-time for chat UI display.

for chunk in graph.stream(
    {"messages": [HumanMessage("Hello")]},
    stream_mode="messages"
):
    token, metadata = chunk
    if hasattr(token, "content"):
        print(token.content, end="", flush=True)
Stream custom data

Emit custom progress updates from within nodes using the stream writer.

from langgraph.config import get_stream_writer

def my_node(state):
    writer = get_stream_writer()
    writer("Processing step 1...")
    # Do work
    writer("Complete!")
    return {"result": "done"}

for chunk in graph.stream({"data": "test"}, stream_mode="custom"):
    print(chunk)

Error Handling

Retry policy

Use RetryPolicy for transient errors (network issues, rate limits).

from langgraph.types import RetryPolicy

workflow.add_node(
    "search_documentation",
    search_documentation,
    retry_policy=RetryPolicy(max_attempts=3, initial_interval=1.0)
)
Tool node error handling

Use ToolNode from langgraph.prebuilt to handle tool execution and errors. When handle_tool_errors=True, errors are returned as ToolMessages so the LLM can recover.

from langgraph.prebuilt import ToolNode

tool_node = ToolNode(tools, handle_tool_errors=True)

workflow.add_node("tools", tool_node)

Common Fixes

Compile before execution

Must compile() to get executable graph.

# WRONG
builder.invoke({"input": "test"})  # AttributeError!

# CORRECT
graph = builder.compile()
graph.invoke({"input": "test"})
Infinite loop needs exit

Provide conditional path to END to avoid infinite loops.

# WRONG: Loops forever
builder.add_edge("node_a", "node_b")
builder.add_edge("node_b", "node_a")

# CORRECT
def should_continue(state):
    return END if state["count"] > 10 else "node_b"
builder.add_conditional_edges("node_a", should_continue)
Additional common mistakes
# Router must return names of nodes that exist in the graph
builder.add_node("my_node", func)  # Add node BEFORE referencing in edges
builder.add_conditional_edges("node_a", router, ["my_node"])

# Command return type needs Literal for routing destinations (Python)
def node_a(state) -> Command[Literal["node_b", "node_c"]]:
    return Command(goto="node_b")

# START is entry-only - cannot route back to it
builder.add_edge("node_a", START)  # WRONG!
builder.add_edge("node_a", "entry")  # Use a named entry node instead

# Reducer expects matching types
return {"items": ["item"]}  # List for list reducer, not a string

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Python, Needs Node.js

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
LangChain
Publisher
LangChain (Official)
Source
langchain-ai/langchain-skills · config/skills/langgraph-fundamentals · commit 126c384
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by LangChain or LangChain.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download langgraph-fundamentals.zip