LangGraph Node Anatomy — What a Single Node Can Contain

Status: ACTIVE (conversation observation) Agent: opencode/ext-agent (sandshrew) Timestamp UTC: 2026-05-11T18:45:00Z Claim: synthesis | 2026-05-11T18:40:00Z Session: MjF conceptual exploration — cataloging everything a LangGraph node can hold, reference, emit, and obey

Prior Context

1. INPUT: What Arrives at the Node

State (current graph snapshot)

The full state dictionary. All keys across all schemas (input, output, internal, private) are accessible. A node can READ any channel even if its declared input schema doesn't include it. It can only WRITE channels it references in its return — but it can write to channels outside its input schema (the graph state is the union of all schemas at init).

Config (execution metadata)

Field What It Is Example
thread_id Session/run identifier "game-session-42"
configurable User-provided arbitrary dict {"model": "claude", "difficulty": "hard"}
metadata.langgraph_step Current super-step counter 17
metadata.langgraph_node Name of the currently executing node "hex_17"
metadata.langgraph_triggers What triggered this node ["hex_16"] (which node sent the message)
metadata.langgraph_path Path through graph so far ["START", "planning", "hex_16", "hex_17"]
metadata.langgraph_checkpoint_ns Current checkpoint namespace "" or subgraph NS
recursion_limit Max super-steps before error 1000 (default)
tags Tracing tags for observability ["production", "combat"]

Runtime (operational services)

Field What It Provides Tangible Use
context Per-invocation typed runtime context DB pool, model client, MCP endpoint handle — injected at .invoke(context=...)
store LangGraph Store: cross-thread key-value persistence Save state visible to OTHER graphs/threads
stream_writer Emit streaming chunks mid-execution "Unit is thinking..." progress to UI
execution_info Thread ID, graph info Logging, correlation
heartbeat Idle timeout refresh Keep-alive during long LLM calls
control Graceful shutdown (RunControl.request_drain) Stop cleanly at next super-step
server_info Deployment metadata Version, environment

2. PROCESS: What the Node Can Do

Deterministic Logic

Computations, data transformations, validations — anything Python. This is where game rules live: damage calculation, movement validation, fog-of-war updates, inventory checks.

Side Effects (External Calls)

None of these require LangChain. The node is a plain Python function.

Control Flow (Graph Manipulation)

Mechanism What It Does When Used
return dict State update, follow static edges Standard path
return Command(update=..., goto=...) State update + dynamic routing override "Move to hex 17 AND check for ambush"
return Command(goto=..., graph=Command.PARENT) Navigate to parent graph node Subgraph → parent handoff
interrupt(value) Pause graph, return value to caller, await resume Human decision point
return None No state change, follow static edges Pass-through / no-op
return [Send("target_node", state_slice)] Fan-out: spawn parallel node executions "All units in this hex attack"

What It Cannot Do

3. OUTPUT: What the Node Emits

State Update (return dict)

A partial update. Only keys that changed. Merged via the key's reducer: - Default reducer → overwrite - Annotated[list, operator.add] → append - Annotated[list, add_messages] → append + deduplicate by message ID - Custom reducer → user-defined merge logic

Node can write to ANY state channel regardless of declared input schema — the graph state is the union of all schemas.

Return Type Variants

Return Effect
{"key": value} State update, static edges execute
Command(update={"key": value}, goto="node_x") State update + explicit next node
Command(goto="node_x", graph=Command.PARENT) Jump to parent graph
None or no return No state change, static edges execute

Side-Stream Output

4. REFERENCES: What the Node Can Access

Internal (Graph-Aware)

Reference Type Access Pattern Example
Other node names String literals in Command/Send goto="hex_17"
State channels dict key access on state state["units"], state["fog_map"]
Edge topology Implicit via return routing Conditional edge function decides next
Subgraphs Command.PARENT, StateGraph nesting Battle subgraph, dialogue subgraph
Checkpointer State persistence, time travel graph.get_state(config, checkpoint_id=n)
Recursion counter config["metadata"]["langgraph_step"] Proactive limit handling
RemainingSteps Managed value in state "2 steps left, wrapping up"

External (Runtime-Injected)

Reference Type Access Pattern Example
Context object runtime.context Model client, DB pool, MCP server handle
Config dict config["configurable"] User preferences, feature flags
Store store.get/put/search Cross-thread key-value data
Any Python import Standard imports import requests, import pygame

Node-Local (Fragile)

A node can reference its own function body, closure variables, or module-level globals. This works but is fragile — state persists only in LangGraph's State, not in Python memory across invocations. Closure variables may not survive graph serialization or thread migration.

5. CONSTRAINTS: What the Node Must Obey

Constraint Detail
Function signature (state: State, config: RunnableConfig, runtime: Runtime[Context]) — state required, others optional
Return type Must be dict, Command, or None — must not break state schema
Write scope Can write to any channel regardless of declared input schema
Type annotations Required for Command return types (Command[Literal["node_a", "node_b"]]) — used for graph rendering
interrupt() dependency Graph must be compiled with a checkpointer
Static + dynamic edges If a node returns Command(goto=...) AND has static edges via add_edge, BOTH execute. Don't mix.
Max step guard recursion_limit caps total super-steps. Node can monitor via config["metadata"]["langgraph_step"]
Serialization State must be serializable if using checkpointer. Not all Python objects survive serialization.
Caching Can be cached per-input via CachePolicy(ttl=...) in add_node

Summary: Node == Function + Access + Side Effects + Routing

A LangGraph node is a Python function that: - Receives the full graph state (read access to everything) - Receives execution context (thread ID, step counter, user config, runtime dependencies) - Runs arbitrary Python logic (LLM calls, rules, HTTP, DB, I/O) - Emits a partial state update (merged via per-key reducers) - Optionally overrides routing (Command.goto) or pauses execution (interrupt) - Can fan out parallel work (Send) - Can reference other nodes by name, state channels by key, external dependencies via context

It is not a "prompt template" or a "tool binding" or a "task definition" — it is a general-purpose function with graph-aware superpowers. Everything else (prompts, tools, configs, task logic) lives inside the function body, organized however the developer wants.