Agent Loop Evolution: pi-mono, LangChain/LangGraph, OpenClaw, Hermes

Agent Loop Evolution: Comparative Analysis

Frameworks covered, roughly chronological by design generation:

Framework Language Role Loop style
LangChain ReAct (legacy) Python Library (loop) Class inheritance + implicit while
pi-mono (agentLoop) TypeScript Library (loop) Nested while + EventStream
Hermes (AIAgent) Python Library (loop) Class-based while + ThreadPoolExecutor
LangChain LangGraph (modern) Python Library (loop) Explicit StateGraph DAG
OpenClaw TypeScript Harness (wraps pi-mono) Gateway + hooks over pi-mono's loop
Anthropic Managed Agents Arch pattern (orthogonal) Brain/Hands/Session decoupling

Architectural caveat: The first four rows are loop implementations — they define the actual turn cycle. OpenClaw is a harness/gateway that delegates its loop entirely to pi-mono (runEmbeddedPiAgent). What OpenClaw contributes is the gateway layer: channel adapters, session management, a rich hook pipeline, and the operator-facing surface above the loop. Comparing OpenClaw's "loop" directly against LangGraph's loop conflates two different abstraction levels. The per-dimension rows below for OpenClaw describe its gateway contributions, not a competing loop design. Anthropic Managed Agents is similarly an architectural pattern, not a loop library.


Dimension-by-dimension comparison

1. Loop representation

Framework Loop form Where it lives
LangChain ReAct while True AgentExecutor.run() — invisible to the agent class
pi-mono Nested outer / inner while agentLoop() function body
Hermes while in run_conversation() AIAgent class method
OpenClaw (harness) No own loop — delegates to pi-mono runEmbeddedPiAgent; gateway layer sits above
LangGraph Explicit DAG: model → tools → model Compiled StateGraph; edges are functions

Evolution (loop implementations only): The loop starts hidden (implicit in a framework base class), becomes explicit code (nested whiles), and finally becomes a first-class data structure (a graph whose edges are named, inspectable routing functions). Each stage makes routing decisions more legible and testable. OpenClaw does not contribute a loop form — it inherits pi-mono's nested-while and adds gateway machinery around it.


2. How the loop exits

Framework Exit condition
LangChain ReAct LLM text contains "Final Answer:"
pi-mono stopReason == error|aborted; no steering or follow-up messages remain
Hermes No tool calls in response; OR 90-turn budget exhausted
OpenClaw (harness) pi-mono's exit conditions apply; before_agent_reply hook can suppress a turn at the gateway layer
LangGraph tool_calls == [] in AIMessage; OR return_direct; OR structured_response; OR jump_to="end"

Evolution: From a text-signal parsed by string matching ("Final Answer:") → implicit loop exhaustion (no messages, no tool calls) → explicit budget caps → typed conditional routing functions that enumerate all possible exits. LangGraph is the first to make exits a first-class concern: each exit condition is a separate branch in _make_model_to_tools_edge.


3. Tool execution model

Framework Parallel? Mechanism Constraints
LangChain ReAct No Synchronous, sequential Hard-coded to
pi-mono Configurable Promise.all on execute() calls after sequential prepareArguments Any BaseTool; beforeToolCall can block
Hermes Yes (N>1) ThreadPoolExecutor; 1 tool → main thread Interactive tools forced sequential
OpenClaw (harness) Delegates to pi-mono Adds block/rewrite semantics via gateway hooks Same as pi-mono underneath
LangGraph Yes Send fan-out — one Send per pending tool call return_direct tools can short-circuit to END

Key insight: Every framework converges on parallel tool execution, but the mechanism differs. pi-mono and Hermes use native concurrency primitives; LangGraph uses graph-level fan-out (Send), which decouples parallelism from the loop logic entirely and makes it observable as graph structure.


4. State model

Framework What is "state"? Persistence
LangChain ReAct Growing prompt string (text) Ephemeral
pi-mono AgentMessage[] (richer than LLM wire format) In-memory; convertToLlm() at boundary
Hermes OpenAI-compat message list Persisted to session store after each turn
OpenClaw (harness) Session transcript (external) External; transformContext for compaction; state lives outside pi-mono
LangGraph Typed AgentState dict: {messages, jump_to, structured_response} Optional checkpointer (pluggable)
Anthropic External append-only event log Survives Brain + Hands restarts

Evolution: From text string → rich message objects → typed state dict → externalized durable log. A key insight from Anthropic's architecture: the loop's state and the loop's execution should be decoupled into different failure domains. LangGraph approaches this with its checkpointer abstraction.


5. Middleware / extensibility model

Framework Extension mechanism Granularity
LangChain ReAct Subclass Agent; override create_prompt, _validate_tools Class methods
pi-mono AgentLoopConfig callbacks: getApiKey, beforeToolCall, afterToolCall, getSteeringMessages, getFollowUpMessages, transformContext, convertToLlm Per-turn function calls
Hermes Callback functions: tool_progress_callback, thinking_callback, step_callback, etc. Observation only (no routing control)
OpenClaw (harness) 15+ hook points (plugin API + shell scripts); block, cancel, rewrite semantics Full lifecycle, can block/mutate — but these are gateway hooks, not loop-level callbacks
LangGraph AgentMiddleware with 4 graph nodes: before_agent, before_model, after_model, after_agent First-class graph nodes; can set jump_to to re-route

Evolution: Subclassing → callbacks → hook pipelines → middleware-as-graph-nodes. The critical shift in LangGraph is that middleware isn't a side-channel (a callback) — it's a node in the same graph as the model and tools. It can issue jump_to directives that redirect the entire routing graph. Middleware is no longer bolted on; it's load-bearing.


6. Mid-run control (HITL / steering)

Framework Mechanism Granularity
LangChain ReAct None
pi-mono getSteeringMessages() injects between turns; getFollowUpMessages() re-enters outer loop Turn boundary
Hermes Background thread for cancellation; clarify tool forces user response Per-turn cancellation + explicit tool
OpenClaw (harness) before_agent_reply hook claims a turn at gateway; command queue steer/followup feeds into pi-mono's steering callbacks Turn level; OpenClaw maps gateway events → pi-mono's getSteeringMessages/getFollowUpMessages
LangGraph interrupt_before/interrupt_after pauses graph at any named node; jump_to in state Any node; resumable from checkpoint

Evolution: The ability to inject human input progresses from nonexistent → turn-boundary callbacks → explicit tool invocation → graph-level pause/resume. LangGraph's approach is the most general: any node can be a pause point, and the checkpointer stores graph state so the loop can be resumed later (even in a different process).


7. Context management / compression

Framework Approach
LangChain ReAct None
pi-mono transformContext() callback runs before convertToLlm() — used by OpenClaw for compaction
Hermes Two thresholds: preflight at 50% context window (agent-side) and 85% (gateway auto-compress); preserves tool call/result pairs; generates new lineage ID
OpenClaw Overflow-triggered auto-compaction; silent memory flush before summary; configurable compaction model, identifierPolicy, and notifyUser; before_compaction/after_compaction hooks; /compact [guidance] for manual control; distinct from per-request session pruning
LangGraph No built-in; handled outside (e.g. reduce via state schema)

Hermes and OpenClaw lead on different axes. Hermes has the most structured trigger model — two explicit thresholds (50%/85%) with lineage ID rotation for auditability. OpenClaw has the most operator-facing compaction design: a configurable summarization model, identity-preservation policy, user notifications, manual /compact [guidance] invocation, and a pre-compaction memory flush so agents can persist critical state before the summary is written. Both enforce the tool-pair invariant.

A key OpenClaw distinction: compaction vs. session pruning are separate mechanisms. Compaction permanently writes a condensed summary to the session transcript (durable, opt-in notifications). Session pruning silently drops tool outputs per API request to stay within the context window (ephemeral, invisible). Most frameworks conflate or ignore this distinction.


8. Multi-agent composition

Framework Support
LangChain ReAct None
pi-mono None (single loop)
Hermes delegate_task tool spawns subagents with isolated context; parent 90-turn / child 50-turn caps
OpenClaw (harness) Session lanes serialize requests at gateway; no explicit subagent model
LangGraph Each agent is a compiled graph; graphs are composable via LangGraph supervisor patterns
Anthropic Managed First-class: many Brains, many Hands, shared Session; distributed execution

Evolutionary arc: four generations

Generation 1 — Text-format stop-token loop (LangChain ReAct)

The loop is driven by two prompt template variables:

PromptTemplate.from_examples() prepends static few-shot demonstrations that teach the LLM the text protocol. The LLM outputs plain text like Action: Search[Colorado orogeny]; ReActOutputParser reads it back. The framework injects a stop token ("\nObservation:") to truncate generation, prepends "Thought:" as the LLM prefix, and appends "Observation: <result>" to the scratchpad after each tool call. Exit is signalled by the LLM writing Action: Finish[answer].

Everything couples together: the prompt template, the few-shot examples, the output parser, the stop token, the tool names, and the exit condition are all one text convention. There is no structured object — control is a protocol negotiated in prose.

Characteristic: Loop = text protocol. The framework and the LLM are coupled through a shared text contract ({agent_scratchpad}ReActOutputParser).


Generation 2 — Class-based structured loop (Hermes, early pi-mono)

Messages become structured objects (OpenAI-compat), not raw strings. Tool dispatch grows from sequential → parallel (ThreadPoolExecutor, configurable Promise.all). Budget tracking appears (Hermes: 90 turns). Provider failover appears (Hermes). Callbacks emerge for observability. But the loop is still a while-loop in a class method — the structure is implicit, and routing decisions are scattered across if-branches inside the loop body.

Characteristic: Loop = structured while-loop. Better message hygiene, but routing logic is still opaque.


Generation 2.5 — Callback/hook layers (pi-mono) + Harness layer (OpenClaw)

pi-mono separates the loop cleanly: outer loop for follow-up, inner loop for turns. Streaming is first-class (EventStream<AgentEvent>). AgentLoopConfig callbacks let callers inject behavior at well-defined points without subclassing.

OpenClaw adds a gateway/harness layer on top of pi-mono without modifying it — it does not reimplement the loop. The insight: separate the loop executor (pi-mono) from the observation/control surface (15+ gateway hooks, channel adapters, session management). Hooks can block, rewrite, and cancel. Shell-script hooks democratize extension without requiring code.

This is a different kind of evolution from the loop improvements above: rather than making the loop smarter, OpenClaw keeps the loop stable (pi-mono) and builds richness around it. The loop and the harness become separate failure domains and separate upgrade surfaces.

Characteristic (pi-mono): Loop = callback-decorated while. Separation of concerns between execution and observation begins. Characteristic (OpenClaw): Harness = gateway wrapping a stable loop; extensibility lives in the surrounding layer, not the loop itself.


Generation 3 — Explicit graph DAG (LangGraph)

The loop becomes a data structure: a StateGraph with named nodes (model, tools, middleware nodes) and named routing functions (_make_model_to_tools_edge, _make_tools_to_model_edge). Every routing decision is inspectable, testable, and composable.

Middleware is first-class: AgentMiddleware hooks are graph nodes, not callbacks. They can set jump_to in state to re-route the entire graph. interrupt_before/interrupt_after pauses execution at any node. The checkpointer externalizes state (approaching Anthropic's Session concept). Structured output becomes a first-class loop concern — a separate model→model retry loop for response format enforcement.

Characteristic: Loop = inspectable DAG. Routing logic is explicit, composable, and decoupled from execution.


Architectural Pattern — Distributed state decoupling (Anthropic Managed Agents)

This is not a generation in the loop-representation lineage. It is an orthogonal axis: where does loop state live? It can be combined with any loop implementation above.

The loop's execution (Brain), its tools (Hands), and its memory (Session) are placed in three separate failure domains. The "loop" is no longer owned by one process — a Brain can crash and a new one resumes from the Session. This dissolves the assumption that loop state and loop execution must be co-located.

Characteristic: Loop state is externalized and decoupled from execution. Any loop implementation can adopt this pattern; LangGraph's checkpointer is the closest approximation in a framework.


Summary table

Dimension Gen 1 (ReAct) Gen 2 (Hermes/pi-mono) Gen 2.5 loop (pi-mono) / harness (OpenClaw) Gen 3 (LangGraph)
Loop form Implicit while Explicit while pi-mono: nested while; OpenClaw: no own loop (delegates to pi-mono) Explicit DAG
State Prompt string Message list (in-memory) pi-mono: in-memory messages; OpenClaw: external transcript (harness-owned) Typed state dict + checkpointer
Exit signal Text token No messages/budget pi-mono: natural exhaustion; OpenClaw gateway: before_agent_reply can suppress Typed routing condition
Tool parallelism No Yes (thread/promise) pi-mono: configurable Promise.all; OpenClaw: same, + gateway block/rewrite hooks Yes (Send fan-out)
Extensibility Subclass Callbacks pi-mono: AgentLoopConfig callbacks; OpenClaw: 15+ gateway hooks (separate layer) Middleware-as-nodes
Mid-run control None Steering messages pi-mono: getSteeringMessages; OpenClaw: command queue feeds these callbacks interrupt_before/after
HITL None Limited OpenClaw: hook claim + command queue Pause/resume at any node
Composition None Subagent tool OpenClaw: session lanes (gateway) Graph composition
Compression None pi-mono callback OpenClaw harness: auto + /compact; configurable model; memory flush; pruning distinction External concern

Anthropic Managed Agents (architectural pattern, not a loop generation): Loop form = distributed process; State = append-only external event log; survives Brain + Hands restarts. Applicable on top of any generation above.


Key cross-cutting insights

0. The most fundamental shift: prompt-as-protocol → tool-call-as-protocol.
In ReAct, the control protocol is a text convention: {agent_scratchpad} grows by appending Thought:/Action:/Observation: strings; the LLM writes Action: ToolName[arg]; ReActOutputParser reads it back. The protocol exists only as a shared text contract between the prompt template, the few-shot examples, and the parser — it is invisible to the framework's type system.

In modern loops (LangGraph, pi-mono, Hermes), the LLM emits a structured tool_calls array in its JSON response. The framework reads AIMessage.tool_calls directly — no text parsing, no stop tokens, no scratchpad string. Tool invocation is a first-class typed object, not a convention in prose. This single shift eliminates the coupling between prompt format and parser, makes tool selection inspectable, enables reliable parallel dispatch, and is what makes the rest of the LangGraph graph architecture possible.

1. The loop structure mirrors trust in the model.
ReAct embeds control in the LLM's text output ("Final Answer:") — it trusts the model to signal completion. LangGraph moves routing entirely to the framework layer (typed functions, named edges) — the model just outputs messages; the framework decides what to do next.

2. Extensibility follows the layer cake — but harness ≠ loop.
pi-mono→OpenClaw demonstrates that you can add rich hook/plugin behavior without modifying the loop executor, by building a harness layer around a stable loop. But this is categorically different from LangGraph's approach: OpenClaw hooks are a side-channel to pi-mono's loop (they can block/rewrite but cannot redefine routing). LangGraph promotes middleware to first-class graph nodes that are the routing — the hook can re-route, not just observe. These are two distinct strategies: stable-loop + rich-harness vs. everything-is-the-graph.

3. Parallel tool execution is table stakes.
Every generation >= 2 supports it. The mechanism evolves: thread pool → promise-based → graph fan-out (Send). The Send model is the most principled: it expresses fan-out in the graph topology rather than in a concurrency primitive inside the loop body.

4. Durability decoupling is the hardest problem.
Hermes persists messages per turn but is still a single-process loop. OpenClaw externalizes the transcript. Anthropic's architecture makes durability a separate failure domain. LangGraph's checkpointer is the closest approximation in a framework, but requires explicit wiring.

5. Budget / termination is an afterthought until it isn't.
ReAct has none. pi-mono relies on natural exhaustion. Hermes is the only framework with explicit budget enforcement (90/50 turn caps), treating runaway loops as a production reliability concern. LangGraph leaves it to recursion_limit (default 10,000) — essentially unlimited.

6. "Explicit" and "implicit" refer to two orthogonal dimensions — which is why they appear inverted across different analyses.

A common point of confusion: some analyses call LangChain ReAct explicit and modern tool-call loops implicit, which is the opposite of how this article uses those terms. Both readings are correct — they measure different things.

Dimension ReAct LangGraph / tool-call loops
Loop structure (is routing a first-class, inspectable object?) Implicit — loop hidden inside AgentExecutor.run() Explicit — routing is a named StateGraph DAG
Reasoning protocol (are state transitions visible in the prompt?) ExplicitThought:/Action:/Observation: scratchpad is human-readable text in the prompt Implicit — state transitions are encoded in tool_calls JSON; nothing appears in the prompt text

This article uses "explicit/implicit" to describe loop architecture (framework-facing). Analyses that call ReAct "explicit" are describing the reasoning protocol (prompt-facing) — the scratchpad makes every step visible in plain text. The two dimensions are orthogonal: ReAct's loop is architecturally implicit but its protocol is textually explicit; LangGraph's loop is architecturally explicit but its protocol is structurally implicit. This is precisely the shift named in §0: prompt-as-protocol → tool-call-as-protocol moves explicitness from the text layer to the type-system layer.