Concepts
State schema
BaseState channels, reducers, and LangGraph managed values shared across hierarchy graphs.
Every graph in langgraph-hierarchies shares a common state schema. Nodes read and write channels on this schema; reducers define how concurrent writes merge.
BaseState
BaseState is a TypedDict declared in langgraph_hierarchies.state.schema. Pass it as state_schema when constructing any graph factory:
from langgraph_hierarchies.state.schema import BaseState, create_base_state_defaults
orchestrator = OrchestratorAgent(
state_schema=BaseState,
context_schema=BaseContext,
)
Channels
| Channel | Reducer | Purpose |
|---|---|---|
messages | merge by message ID | Conversation history for the current agent |
todo_list | dict merge | Active TODO items for the current agent |
todo_lists | named dict merge | (reserved — no nodes in this release read or write this field) |
chat_with_operator | merge by message ID | (reserved — no nodes in this release read or write this field) |
current_agent_args | last-write wins | Task args passed when a subagent is invoked |
current_agent_report | last-write wins | Report written by an agent before returning to its supervisor |
current_tool_call | last-write wins | Tool call that triggered a subagent invocation |
is_finished | logical OR | Root task completed (finish_task) |
is_cancelled | logical OR | (reserved — no nodes in this release read or write this field) |
progress | highest counts win | Per-agent execution counters |
iteration_number | last-write wins | Current ReAct iteration |
max_iterations | last-write wins | Iteration budget for the current agent |
file_refs | merge by ID | (reserved — no nodes in this release read or write this field) |
__subagent_stack__ | last-write wins | Stack of parent state snapshots during nested invocation |
remaining_steps | (managed) | LangGraph global recursion budget — see below |
Message reducer behavior
Message reducer behavior
reduce_messages merges by message id: matching IDs are replaced, new IDs are appended. Messages without an ID receive a generated UUID. This lets parallel tool branches update the same conversation without duplicating entries.
__subagent_stack__
__subagent_stack__
Internal channel used by CompiledGraph to snapshot parent state on subagent entry and restore it on exit. You normally do not write to this channel from your own nodes. See Subagents.
Root defaults
create_base_state_defaults() returns empty values for every channel except remaining_steps (which LangGraph manages):
from langgraph_hierarchies.state.schema import create_base_state_defaults
root = orchestrator.compile_as_root(
state_defaults=create_base_state_defaults(),
)
result = root.invoke(
create_base_state_defaults(),
config=RunnableConfig(recursion_limit=50),
context=build_context(),
)
Pass RunnableConfig at invocation time to set LangGraph’s global recursion_limit (see LangGraph recursion budget below).
When compiled with compile_as_root(), the wrapper’s after_entry_hook fills any missing keys from state_defaults before the first node runs.
Iteration budget (max_iterations)
Each ReAct agent tracks how many reasoning steps it has taken via iteration_number and enforces a per-agent cap via max_iterations. This is the iteration budget you typically configure.
Set the cap when constructing a ReactGraph:
agent = ReactGraph(
name="researcher",
max_iterations=20,
...
)
SubagentPolicy.max_iterations can override the cap when a subagent is invoked. Each compiled subgraph maintains its own independent iteration_number / max_iterations pair — a parent and child do not share a single ReAct iteration counter at the state level.
LangGraph recursion budget (remaining_steps)
remaining_steps is annotated with LangGraph’s RemainingSteps managed value:
from langgraph.managed import RemainingSteps
class BaseState(TypedDict):
...
remaining_steps: RemainingSteps
LangGraph owns this channel. It is populated from recursion_limit in RunnableConfig and decremented on every node execution across the graph — not just ReAct reasoning steps. You may read it inside nodes or routing functions to detect an approaching limit, but you must never include it in node return values or Send payloads — LangGraph will log a warning and ignore the write.
Each compiled subgraph maintains its own independent remaining_steps counter. Tune per-agent reasoning limits with max_iterations; use recursion_limit only as a global safety net for total node executions.
Runtime context (separate from state)
Mutable state flows through graph channels. Immutable runtime dependencies (model, file store, thread ID) live in BaseContext and are injected via context= at invocation time:
from langgraph_hierarchies.state.context import BaseContext
@dataclass
class BaseContext:
thread_id: str = ""
model: BaseChatModel | None = None
...
CompiledGraph merges Runtime(context=...) into the LangGraph config so runtime.context.model is available in every node. See CompiledGraph.
Extending state
Subclass or extend BaseState with additional Annotated fields and custom reducers when your hierarchy needs domain-specific channels (for example pipeline_artifact). Keep cross-boundary handoff fields explicit — prefer merging named artifact keys via SubagentPolicy.merge_fields over passing full message history.