← All posts

From LangGraph to Claude Code: Has Agent Development Split Into Two Worlds?

🌐 閱讀中文版

By mid-2026 there are far more choices for building agents than there used to be, but the gap between tools is wide enough to be confusing. Here are the main frameworks on the field.

LangChain and LangGraph. LangChain is the largest LLM application ecosystem today. It started with the concept of chains linking LLMs to external tools, providing prompt templates, output parsers, retrievers, memory, and other standard components alongside thousands of third-party integrations for quickly assembling RAG pipelines, chatbots, and simple agents. As agent requirements grew more complex (conditional branching, human-in-the-loop, error retries), the team released LangGraph, which redefines control flow using directed graphs. Developers define a StateGraph, draw nodes and edges where each node is a Python function, use conditional edges for routing, and manage global state through a TypedDict schema. LangGraph also ships with built-in checkpointing (pause and resume at any node) and human-in-the-loop support. For deployment there is LangGraph Platform, paired with LangSmith for tracing and eval. Courses and enterprises tend to start here because it lays every decision point out in code.

Google Agent Development Kit (ADK). ADK is Google’s agent development framework released in 2025, positioned between LangGraph and Claude Code. Also code-first, but at a higher abstraction level: developers hand typed, docstringed functions to an Agent, tool schema generation is automatic, and the tool-use loop is handled internally by the Runner. ADK natively supports multi-agent hierarchies (parent-child agent composition), built-in eval tooling, and Event Compaction, which automatically summarizes older interactions into a compact state to cut token costs. Language support covers Python, TypeScript, Go, Java, and Kotlin. The deployment path is Vertex AI Agent Engine, and cross-organization interop runs through the A2A protocol, now governed by the Linux Foundation. The overall feel is one level above LangGraph: no need to build the loop yourself, but you keep code-level control.

Claude Code. Claude Code is Anthropic’s CLI agent and the flagship harness-mode product (I wrote a separate post on what harness means). Developers do not write flowcharts. Instead they declare behavior in markdown files: CLAUDE.md defines project-level rules and guard policies, skills/ holds domain knowledge and executable scripts, and agents/ defines dispatchable subagents. The model decides execution order within those boundaries. Built-in tools cover file I/O (Read, Write, Edit), shell (Bash), search (Glob, Grep, WebSearch), and more. External APIs plug in through MCP (Model Context Protocol).

A core mechanism in Claude Code is hooks: developer-defined shell commands that fire automatically on specific lifecycle events. There are currently six event points: PreToolUse (intercept before tool execution, can block or rewrite), PostToolUse (after execution, for linting or auto-formatting), UserPromptSubmit (when the user sends a prompt), Stop (when the agent finishes), Notification (when waiting for user input), and SubagentStop (when a subagent finishes). PreToolUse deserves special attention: exit code 2 blocks the tool and feeds the error message back to the model, and this block cannot be bypassed even in --dangerously-skip-permissions mode, making it a hard guarantee that the developer can enforce. In early 2026 Anthropic added HTTP hooks (POST events to a remote endpoint) and async hooks (run in the background without blocking the agent), further expanding the control surface.

On the model side, Claude Code is designed primarily for Anthropic’s Claude models, but through LiteLLM or the ANTHROPIC_BASE_URL environment variable it can connect to GPT, Gemini, DeepSeek, local models (Ollama, LM Studio), or any service that speaks the Anthropic Messages API format. To embed this mode in your own service, the path is the Claude Agent SDK (formerly Claude Code SDK, renamed March 2026), which is the same harness exposed as a library, suitable for CI/CD pipelines or custom applications.

OpenClaw. OpenClaw is an open-source instance of the same pattern aimed at personal assistants. It uses AGENTS.md, SOUL.md, and MEMORY.md as its markdown workspace, with built-in tools, session management, and a message gateway. The shape is highly consistent with Claude Code; the difference is the target scenario: Claude Code faces development workflows, OpenClaw faces everyday personal assistant tasks.

My background is almost entirely in the first two. I used LangGraph for multi-agent architecture in coursework, built internship projects on LangGraph too, then picked up ADK at a workshop. But browsing the developer community in 2026, everyone is remixing Claude Code skills and subagents into every shape imaginable. OpenClaw is gaining momentum. It looks like an “agent” is nothing but a few .md files.

What exactly are those markdown files? Are they the same kind of thing as the graphs from class? I implemented the same travel agent in both modes and looked at how nodes call the LLM, how tools get executed, and how subagents get dispatched.


1. The core difference: who owns the agent loop

Every agent runs the same cycle at its core: Plan, Act, Observe, repeated until the task is done. The split between classroom and community is not about the loop itself, but about who implements it and who decides the next step:

  • Framework (LangGraph, ADK): the loop is defined by the developer’s code. Developers draw nodes and edges; the LLM is just one node in the graph. Routing, state transitions, and termination conditions are all explicit code. Control flow belongs to the developer.
  • Harness (Claude Code, OpenClaw): the loop is driven by the model. Developers do not define a flow; they declare behavior in markdown: what to do, what not to do, what quality looks like. The model decides the order of execution within those boundaries. Control flow belongs to the model.

“Agent harness” became the term of art in early 2026, and Addy Osmani’s definition is the cleanest: Agent = Model + Harness. Everything outside the model, including tools, permissions, context management, sandboxing, and error recovery, is the harness. He goes further: a decent model with a great harness beats a great model with a bad harness. The same post documents a team that moved their coding agent from Top 30 to Top 5 on Terminal Bench by changing only the harness, without touching the model.

An analysis of Claude Code’s source reveals the other half of the picture: the vast majority of a production agent’s code is harness infrastructure (permission control, context management, tool routing, recovery), while actual model decision logic is a tiny fraction. That explains the gap. With LangGraph, developers build that infrastructure. With Claude Code, Anthropic builds and maintains it, and the developer’s work shrinks to the behavior declaration layer.

The following dissection uses Claude Code because its mechanics are the best documented; the conclusions hold for OpenClaw as well.

Framework (LangGraph / ADK) Your code owns the loop Node A (LLM call) Conditional edge (explicit routing) Node B (tool call) Explicit state schema, predictable flow Harness (Claude Code / OpenClaw) Model drives the loop, behavior is declared CLAUDE.md / Skills (markdown) Agentic loop (model picks next step) Built-in tools + subagents + hooks Context, permissions, recovery built into harness
Control-flow ownership: in a framework the loop lives in developer code; in a harness the model drives the loop and behavior is declared in markdown.

2. One travel agent, two implementations

Looking at code is more convincing than abstract comparison. The requirement: a multi-role travel-planning agent covering intent analysis, search, itinerary planning, debate-style review, and output guarding. The LangGraph version comes from my course project, a system that actually ran.

2.1 The LangGraph version: everything is explicit

The system consists of 9 nodes: input guard, intent profile, search, orchestrator, planner, replanner, debate, explain, and output guard, with the orchestrator doing conditional routing at the center. The project is roughly 23,000 lines of Python including tests. The heart of the graph definition:

def build_travel_graph():
    builder = StateGraph(State)

    builder.add_node("input_guard", input_guard_node)
    builder.add_node("intent_profile", intent_profile_node)
    builder.add_node("search", search_node)
    builder.add_node("orchestrator", orchestrator_node)
    builder.add_node("planner", planner_node)
    builder.add_node("replanner", replanner_node)
    builder.add_node("debate", debate_node)
    builder.add_node("output_guard", output_guard_node)

    builder.add_edge(START, "input_guard")
    builder.add_edge("input_guard", "orchestrator")
    # every specialist returns to the orchestrator

    builder.add_conditional_edges(
        "orchestrator",
        orchestrator_routing,   # routing function you implement
        {"planner": "planner", "debate": "debate", "END": END, ...},
    )
    return builder.compile()

The compiled graph looks like this (similar to LangGraph’s own visualization output):

START input_guard orchestrator intent_profile search planner debate replanner output_guard END conditional routing
LangGraph node structure of the travel agent: the orchestrator dispatches tasks to specialists via conditional edges; each specialist returns to the orchestrator when done.

How a node calls the LLM

In LangGraph a node is just a Python function: it receives the current state, does whatever computation it needs (usually one or more LLM calls), and returns an update to the state. The LLM call is one explicit invoke. My planner, for example, forces structured output for the reasoning step:

def planner_node(state: State) -> dict:
    prompt = build_planner_prompt(state)          # assemble context from state
    response = llm.invoke(
        [SystemMessage(content=prompt)],
        response_format={"type": "json_object"},  # force JSON for easy state writes
    )
    itineraries = parse_itineraries(response)
    return {"itineraries": itineraries}           # the return value is the state update

There is no magic here: what context the LLM sees and which state field the output lands in are entirely the developer’s decisions. That is the first layer of explicitness in the framework mode.

How tools get called: two paths

The second layer is tooling. LangGraph offers two paths for tool calls, matching two answers to the question “who decides to call the tool.”

Path one: the LLM decides (bind_tools mode). Tool schemas are bound to the LLM, the model emits tool_calls, the developer (or the prebuilt ToolNode) executes them, feeds results back as ToolMessage, and calls the LLM again until it stops asking for tools:

llm_with_tools = llm.bind_tools([search_flights, search_hotels, search_weather])

def research_node(state: State) -> dict:
    messages = state["messages"]
    while True:
        response = llm_with_tools.invoke(messages)
        messages.append(response)
        if not response.tool_calls:              # model no longer needs tools
            break
        for call in response.tool_calls:         # execute each requested tool
            result = TOOLS[call["name"]].invoke(call["args"])
            messages.append(ToolMessage(content=result, tool_call_id=call["id"]))
    return {"messages": messages, "research": response.content}

Note what this while loop is: the minimal version of the “agent loop” that the word harness refers to. In LangGraph this is written by hand (or generated with prebuilt components like create_react_agent), and the developer also owns the iteration cap, timeouts, and retries.

Path two: the code decides. Most of my project takes this path: the node calls tool functions directly from Python, and the LLM only handles the reasoning before and after. Tool permissions live in an explicit allowlist dict:

# agent_tools.py: explicit per-agent tool permissions
TOOLS_BY_AGENT: Dict[str, Dict[str, Callable]] = {
    "planner_agent": {
        "search_weather":  search_weather,
        "search_flights":  search_flights,
        "search_hotels":   search_hotels,
        "web_search":      web_search,
    },
    "research_agent": { ... },
}

def get_tools_for_agent(agent_name: str) -> Dict[str, Callable]:
    # an unregistered agent gets no tools by default
    return TOOLS_BY_AGENT.get(agent_name, {})

Path two trades flexibility (tool timing is hard-coded) for full predictability: which agent can touch which tool, when it fires, and how arguments are assembled are all unit-testable. For guard nodes and anything touching money, that determinism is not optional.

The full inventory of explicitness

Beyond the graph, the system maintains a state schema of thirty-plus fields, including budget, debate_count, replan_attempts, and threat_blocked; every node’s reads and writes against it form an explicit contract. Put together, the LangGraph developer personally holds four things:

  • The loop: hand-written (or generated), with caps and termination conditions as code
  • Tools: explicitly registered and bound, permissions as an allowlist dict
  • Context: every prompt that reaches the LLM is assembled by the developer; state is the single source of truth
  • Routing: every transition out of the orchestrator is a conditional edge

The upside is that each item is testable and auditable: the loop cap (max_replan_attempts: 2) lives in state and holds regardless of model cooperation; CI/CD, red-team tests, and OWASP checks all attach to well-defined node boundaries. The cost is equally concrete: every new capability touches four places: state schema, node, edge, routing. And late in the project a peculiar feeling sets in: the orchestrator’s routing logic is essentially a reimplementation, in code, of decisions the model could have made on its own.

2.2 The Claude Code version: declare behavior, hand over the flow

The same requirement in harness mode has no graph. The deliverable is a set of behavior declarations:

.claude/
├── CLAUDE.md                    # project-level rules (= system prompt + guard policies)
├── skills/
│   └── travel-planning/
│       └── SKILL.md             # planning knowledge: steps, output format, quality bar
└── agents/
    ├── researcher.md            # subagent: search flights/hotels, return structured results
    ├── planner.md               # subagent: produce itinerary drafts
    └── critic.md                # subagent: debate-style review, scores and revision notes

Where the loop went: the built-in tool-use cycle

Claude Code’s core mechanism is that while loop from path one, fully internalized. Each turn follows a fixed sequence:

  1. The model emits a response that may contain one or more tool_use blocks (for example “call WebSearch with these arguments”)
  2. The harness intercepts the block and runs it through the permission layer: allow/deny rules in settings, the current permission mode, and PreToolUse hooks (arbitrary code that can audit or even rewrite the call)
  3. If approved, the tool executes and the tool_result is written back into context
  4. The model reads the result and decides what is next: another tool call, a change of approach, or task completion

This cycle is owned by the harness; the developer never sees it and never writes it. The mapping to LangGraph is precise: bind_tools corresponds to the built-in tool list, the developer’s hand-written while loop corresponds to the harness loop, and the TOOLS_BY_AGENT allowlist corresponds to the permission layer. The difference is the medium: Python on one side, configuration and markdown on the other.

Where tools come from: three layers

  • Built-in tools: Read, Write, Edit, Bash, Glob, Grep, WebSearch, and friends, covering the filesystem, shell, and network out of the box
  • MCP servers: external services mounted through the Model Context Protocol; third-party APIs like flight or hotel search plug in here, and to the model they are indistinguishable from built-ins
  • Skill scripts: a skill can ship executable scripts alongside its knowledge, which the model runs through Bash when needed

The search_flights and search_hotels that were hand-written Python functions with allowlist registration in the LangGraph version become two endpoints of an MCP server here; the model decides when to call them and how to assemble arguments.

How subagents get dispatched

Subagents are the key to understanding harness mode: to the model, dispatching a subagent is itself a tool call (the Task tool in Claude Code). The main agent judges “this belongs to the critic” and emits a tool call carrying the task description. The harness receives it, boots a child execution environment with its own context window using critic.md as the system prompt, and when it finishes, condenses the result into a single message returned to the main agent.

---
name: critic
description: Review itinerary drafts for budget, feasibility, preference fit
tools: Read, WebSearch          # tool allowlist: this subagent gets only these two
---
You are an itinerary reviewer. Upon receiving a draft:
1. Check for budget overruns and transit feasibility
2. Score 0-100; below 75 you must attach concrete revision instructions
3. Review at most two rounds; after the second, approve regardless and note the risks

The tools: field in the frontmatter is this subagent’s tool allowlist, functionally equivalent to the TOOLS_BY_AGENT dict. Context isolation replaces the state schema: LangGraph shares thirty fields across nine nodes, while Claude Code gives each subagent a clean context and brings only the conclusion back. The former is precise but requires schema maintenance; the latter is effortless, but the granularity of information transfer depends on the model’s summarization quality.

Guarantee strength: hard guarantees versus soft agreements

The crux is the last line of critic.md. The loop cap that the LangGraph version guarantees structurally through the max_replan_attempts: 2 state field is, here, a sentence of natural language. The model follows it most of the time, but the guarantee is probabilistic, not structural. This is the most fundamental difference between the two modes: limits written in framework code are hard guarantees; instructions written in harness markdown are soft agreements. Hooks can promote select soft agreements back into hard guarantees (as noted above, PreToolUse hook exit code 2 cannot be bypassed even with skip-permissions), but by default the strength of a behavioral boundary is bounded by the model’s instruction following.

LangGraph: 9 nodes + state schema orchestrator input_guard search planner debate replanner output_guard State (TypedDict, 30+ fields) ~23,000 lines of Python (incl. tests) . flow = code Claude Code: markdown declarations CLAUDE.md (rules + guards) skills/travel-planning/SKILL.md researcher planner critic Agentic loop + context (built into harness) 4-5 markdown files . flow = model decisions
Two shapes of the same requirement: on the left the flow is fixed in a graph; on the right the model decides the flow at runtime within the harness boundary.

Read the gap in implementation size correctly: the markdown mode does not eliminate infrastructure, it transfers the loop, tool routing, permissions, and context management to the harness vendor. The developer gives up control and gets iteration speed. To embed this mode in a custom service, the path is the Claude Agent SDK, which Anthropic positions as a general-purpose agent harness.

3. Google ADK: the middle route

ADK sits between framework and harness: code-first like the former, with built-in infrastructure approaching the latter. After using it, my impression is that it works one level above LangGraph. Tooling shows this best. Typed, docstringed Python functions are handed straight to an Agent, and schema generation, the tool-use loop, and result feedback are all handled by the Runner:

def search_flights(origin: str, destination: str, date: str) -> dict:
    """Search available flights between two cities on a given date."""
    ...

agent = Agent(
    model="gemini-2.5-pro",
    instruction="You are a travel planner. Check flights and weather before drafting.",
    tools=[search_flights, search_hotels, search_weather],
)

Against the previous two sections: in LangGraph that loop is the developer’s hand-written while; in Claude Code it hides inside the harness; in ADK the Runner provides it, but the developer stays at the code level and can intercept events, attach callbacks, and compose multi-agent hierarchies. What gets defined is what the agent is made of, not the flowchart; graph-style routing is available but optional. The official quickstart covers Python, TypeScript, Go, Java, and Kotlin, and the onboarding bar really is lower than LangGraph’s.

ADK’s recent investment also lands squarely on infrastructure. Event Compaction automatically summarizes older interactions into a compact state to cut token costs in long conversations, exactly the “built-in context management” the harness camp prides itself on. The A2A protocol, now governed by the Linux Foundation, paired with managed deployment on Vertex AI Agent Engine, targets cross-organization multi-agent interop, which is the actual problem behind the A2A travel example from class.

In one sentence: LangGraph gives maximum control, ADK gives control plus infrastructure, Claude Code gives infrastructure plus speed.

4. Choosing between them

The choice comes down to three questions: does the flow need to be deterministic, how expensive is failure, and what is the team’s ecosystem preference.

DimensionLangGraphGoogle ADKClaude CodeAgent SDKOpenClaw
What it isGraph-based agent frameworkCode-first agent frameworkInteractive CLI agentClaude Code as a libraryPersonal assistant harness
Control-flow ownerDeveloper codeDeveloper code (loop built in)The modelThe model (application-driven)The model
Tool callsbind_tools or direct code calls, loop self-builtFunctions given to Agent, Runner runs the loopModel initiates, harness intercepts and executesSame as Claude CodeModel initiates, harness executes
Tool permissionsAllowlist dict (self-built)Code-level controlsettings + frontmatter + hooksCode-level hooks + settingsMarkdown workspace declarations
Loop guaranteesStructural (state + edges)StructuralSoft agreements (instructions + hooks can upgrade to hard guarantees)Same as Claude CodeSoft agreements
Context managementDeveloper assembles prompts and stateEvent Compaction built inBuilt into harness, subagent isolationSame as Claude CodeBuilt into harness
TestabilityPer-node unit testsBuilt-in eval toolingMostly end-to-endCan embed in CI/CDMostly end-to-end
Model supportAny modelGemini, Claude (via Vertex), Ollama, LiteLLM, etc.Claude primarily, other models via LiteLLMSame as Claude CodeMulti-model
DeploymentSelf-hosted / LangGraph PlatformVertex AI Agent EngineLocalSelf-hosted, embed in servicesLocal / self-hosted
Time to first agentDaysHoursMinutesHours (requires code)Minutes
Best fitCompliance, finance, high-stakes flowsProduction services in the GCP ecosystemExploration, internal tools, dev automationEmbedding in apps or CIPersonal assistant, everyday task automation

Concrete calls:

  • Pick a framework (LangGraph) when flow errors cause real losses (money, compliance), when step-by-step audits are needed, or when system behavior must be explained to non-AI engineers. This is why companies and courses start here: these scenarios demand guarantee strength.
  • Pick ADK when the team lives in the GCP ecosystem, wants managed deployment without building context management and eval infrastructure, or needs A2A interop across organizations.
  • Pick Claude Code when the task is inherently open-ended (research, coding, data exploration), when the flow genuinely should be the model’s call, or when validating an agent idea in the shortest possible time. For embedding in a custom service, switch to the Agent SDK.
  • Pick OpenClaw when the need is a personal assistant rather than a development tool: everyday task automation, information organization, cross-platform message management.
  • Mixing is the norm: prototype quickly in Claude Code or OpenClaw to validate behavior, then harden the parts that need hard guarantees into a LangGraph or ADK service.

Conclusion

Beyond the markdown sits an entire harness infrastructure that always existed and is simply no longer the developer’s to implement. The tool-use while loop written by hand in LangGraph, the TOOLS_BY_AGENT allowlist, the prompt-assembly code: all of it lives on inside Claude Code as the harness loop, frontmatter permissions, and context management, with the implementer changed from the developer to Anthropic. Nor are LangGraph and ADK outdated; they keep control in the developer’s hands and charge for it in lines of code.

The graphs taught in class and the markdown played with in the community are two ownership models of the same agent loop. Once that clicks, the choice stops being tribal and becomes an ordinary engineering trade-off. Take a problem you know well and implement it once in each mode; the differences surface on their own.


Further reading

#ai#agents