Source Code Deep Dive: OpenClaw vs Claude Code — Two Radically Different AI Agent Architectures

A source-code-level comparison of how OpenClaw (453K lines TypeScript) and Claude Code (28K lines Markdown) approach agent definition, plugin systems, multi-model support, and security.

zhuermu · · 20 min
OpenClawClaude CodeAI AgentSource CodeArchitecture源码分析AI Agent 设计哲学龙虾

中文版 / Chinese Version: 本文最初发表于微信公众号。阅读中文原文 →

In my previous article I told the story. This one tears apart the engines. I cloned both OpenClaw and Claude Code, read the core modules line by line, and found two projects that answer the question “how should an AI agent be built?” in diametrically opposite ways. One is a 453,000-line TypeScript distributed runtime. The other is 183 files that are almost entirely Markdown. If you are building AI agents today, what follows may reshape how you think about architecture.


The numbers: just how different are they?

Before diving into code, take a moment to absorb the scale gap:

DimensionClaude CodeOpenClaw
Primary languageMarkdown + JSON + PythonTypeScript (ESM)
Non-test source files~183 files~2,548 .ts files
Non-test lines of code~28,000 (mostly Markdown)~453,000 TypeScript
Core runtimeClosed-source binaryFully open source
Plugin definitionMarkdown files + YAML frontmatterTypeScript modules + register() API
Agent definitionA single .md fileSpread across 859 files in agents/
Supported LLM providersAnthropic Claude only20+ (OpenAI, Ollama, Bedrock, Qwen, Moonshot, Kimi, …)
Message channelsTerminal + IDE + GitHub40+ (Telegram, Discord, WhatsApp, Feishu, Slack, iMessage, …)

This is not a story about “more is better.” It is a story about two fundamentally different beliefs about what an AI agent should be, expressed at the source-code level.


Architecture Comparison: Amazon Q Developer CLI vs Claude Code

Architecture overview: Markdown OS vs full-stack runtime

Claude Code: everything is Markdown

Open the Claude Code repository and you will find a startling fact: there is almost no executable code.

claude-code/
├── plugins/           # 13 official plugins
│   ├── code-review/
│   │   ├── .claude-plugin/plugin.json    # metadata
│   │   └── commands/code-review.md       # command = a Markdown file
│   ├── feature-dev/
│   │   ├── commands/feature-dev.md
│   │   └── agents/                       # agent = a Markdown file
│   │       ├── code-explorer.md
│   │       ├── code-architect.md
│   │       └── code-reviewer.md
│   └── security-guidance/
│       └── hooks/pre-tool-use-security-check.md
├── examples/
│   ├── settings/      # permission config examples (JSON)
│   └── hooks/         # hook examples (Python scripts)
└── .claude-plugin/marketplace.json

The core runtime is closed source. What ships in the open-source repo is the plugin ecosystem’s definition layer — Markdown files that describe what agents should do, JSON files that declare permission boundaries, and YAML frontmatter that specifies tool whitelists.

The implication is profound: Claude Code treats Markdown as a programming language.

OpenClaw: a complete distributed system

OpenClaw sits at the opposite extreme:

openclaw/
├── src/
│   ├── agents/        # 859 files -- model configs, tool catalogs, sub-agent policies
│   ├── gateway/       # 341 files -- HTTP server, WebSocket, session management
│   ├── commands/      # 305 files -- CLI commands, auth flows, diagnostics
│   ├── config/        # 212 files -- config parsing, secret management, model routing
│   ├── plugins/       # 79 files  -- plugin loader, registry, runtime
│   ├── channels/      # 65 files  -- message channel abstraction layer
│   ├── cli/           # 164 files -- terminal UI, command parsing
│   ├── browser/       # 145 files -- browser automation
│   └── security/      # 31 files  -- sandbox, permission policies
├── extensions/        # 40+ channel plugins (Telegram, Discord, Feishu, ...)
├── skills/            # 50+ skill packs (GitHub, Weather, Obsidian, ...)
└── apps/              # macOS, iOS, Android native clients

OpenClaw is not a CLI tool. It is a gateway-backed distributed agent runtime.


Agent definition: declarative Markdown vs imperative TypeScript

This is where the two projects diverge most sharply.

Claude Code: one agent = one Markdown file

Here is the complete source of Claude Code’s code-reviewer agent definition:

---
name: code-reviewer
description: Reviews code for bugs, logic errors, security vulnerabilities...
tools: Glob, Grep, LS, Read, NotebookRead, WebFetch, TodoWrite, WebSearch
model: sonnet
---

You are a code reviewer. Your job is to...
(the rest is pure natural-language system prompt)

That is the entire thing. A YAML header declares the name, description, available tools, and model. Everything else is natural-language instructions.

Now look at the code-review command definition — a 200+ line Markdown file that describes a complex multi-agent orchestration flow in plain English:

---
allowed-tools: Bash(gh pr diff:*), Bash(gh pr view:*)...
description: Code review a pull request
---

1. Launch a haiku agent to check if PR is closed/draft...
2. Launch a haiku agent to return CLAUDE.md file paths...
3. Launch a sonnet agent to summarize changes...
4. Launch 4 agents in parallel:
   - Agent 1+2: CLAUDE.md compliance (sonnet)
   - Agent 3: Bug scanning (opus)
   - Agent 4: Security issues (opus)
5. For each issue, launch parallel validation subagents...
6. Filter unvalidated issues...
7. Output summary...

This Markdown describes a 7-step pipeline involving 10+ parallel sub-agents, yet contains zero lines of code. The Claude Code runtime parses this natural language and automatically orchestrates agent scheduling.

Design philosophy: trust the model’s comprehension. Natural language replaces code.

OpenClaw: the agent is an engineered system

OpenClaw’s agent system is entirely different. From src/agents/agent-scope.ts (simplified):

export function resolveAgentConfig(
  cfg: OpenClawConfig,
  agentId: string
): ResolvedAgentConfig {
  const entry = resolveAgentEntry(cfg, id);
  return {
    name: entry.name,
    workspace: entry.workspace,
    model: entry.model,        // string or { primary, fallbacks[] }
    skills: entry.skills,      // skill filters
    identity: entry.identity,  // identity config
    subagents: entry.subagents,// sub-agent policy
    sandbox: entry.sandbox,    // sandbox config
    tools: entry.tools,        // tool policy
  };
}

Every agent has an explicit configuration structure: model selection (with fallback chains), workspace isolation, sub-agent spawn depth control, sandbox policy, tool whitelist/blacklist.

Sub-agent capability control (src/agents/subagent-capabilities.ts) is particularly revealing:

export function resolveSubagentCapabilities(params: {
  depth: number;
  maxSpawnDepth?: number;
}) {
  const role = resolveSubagentRoleForDepth(params);
  // role: "main" | "orchestrator" | "leaf"
  return {
    depth: params.depth,
    role,
    canSpawn: role === "main" || role === "orchestrator",
    canControlChildren: role !== "leaf",
  };
}

OpenClaw uses code to precisely control spawn depth — a main agent can spawn an orchestrator, an orchestrator can spawn a leaf, but a leaf cannot spawn anything. This prevents infinite agent recursion through a compile-time-verifiable structural guarantee, not a prompt asking the model to please stop.

Design philosophy: do not trust the model’s self-restraint. Build deterministic boundaries in code.


Plugin systems: convention-based discovery vs runtime registration

Claude Code: a plugin is a folder convention

Claude Code’s plugin structure is purely convention-driven:

plugin-name/
├── .claude-plugin/plugin.json    # metadata
├── commands/*.md                 # slash commands
├── agents/*.md                   # sub-agents
├── skills/*/SKILL.md             # skill knowledge bases
├── hooks/*.md                    # event hooks
└── .mcp.json                     # MCP server config

No register() function. No lifecycle hooks. No API calls. The runtime scans the folder structure and auto-discovers everything.

Hook implementation is equally interesting. The security-guidance plugin’s hook is a Markdown file describing 9 security patterns (command injection, XSS, eval usage, etc.). The Claude Code runtime reads this Markdown before every tool invocation and lets the model itself decide whether a security risk exists.

For cases where you need deterministic hook logic, there are external scripts. From the official bash_command_validator_example.py:

# Hook receives JSON via stdin, controls behavior via exit code
# exit 0 = allow, exit 1 = warn user, exit 2 = block and notify model
def main():
    input_data = json.load(sys.stdin)
    command = input_data.get("tool_input", {}).get("command", "")
    issues = _validate_command(command)
    if issues:
        sys.exit(2)  # block execution

Inter-process communication using exit codes as an API. Absurdly simple, but it works.

OpenClaw: plugins as first-class runtime citizens

OpenClaw’s plugin system is a full module-loading framework. The plugin API from src/plugins/types.ts:

export type OpenClawPluginApi = {
  registerTool: (tool: AnyAgentTool | ToolFactory) => void;
  registerHook: (events: string[], handler: HookHandler) => void;
  registerHttpRoute: (params: HttpRouteParams) => void;
  registerChannel: (registration: ChannelPlugin) => void;
  registerGatewayMethod: (method: string, handler: GatewayHandler) => void;
  registerCli: (registrar: CliRegistrar) => void;
  registerService: (service: PluginService) => void;
  registerProvider: (provider: ProviderPlugin) => void;
  registerCommand: (command: CommandDefinition) => void;
  registerContextEngine: (id: string, factory: ContextEngineFactory) => void;
};

A single plugin can register tools, hooks, HTTP routes, message channels, gateway methods, CLI commands, background services, model providers, and context engines. This is not a plugin system. It is the extension point surface of a microkernel architecture.

OpenClaw defines 24 lifecycle hooks:

export type PluginHookName =
  | "before_model_resolve"
  | "before_prompt_build"
  | "before_agent_start"
  | "llm_input" | "llm_output"
  | "before_tool_call" | "after_tool_call"
  | "message_received" | "message_sending" | "message_sent"
  | "subagent_spawning" | "subagent_spawned" | "subagent_ended"
  | "gateway_start" | "gateway_stop"
  | ...;  // 24 total

The plugin loader (src/plugins/loader.ts, 600+ lines) implements:

  • Multi-source discovery (bundled / global / workspace / config)
  • Priority-based sorting and deduplication
  • Memory slot mutual exclusion (only one memory plugin at a time)
  • Configuration schema validation
  • Security boundary checks (path traversal detection)
  • LRU caching (up to 32 registry instances)
  • TypeScript hot-reloading via Jiti

Design philosophy: plugins are part of the system, not appendages to it.


Multi-model support: single vendor vs model router

Claude Code: Claude only

Claude Code supports only Anthropic’s Claude models. This is not a technical limitation — it is a product decision. When your agent definition is natural language, the model’s comprehension ability is your runtime. Switch to a different model and the entire system’s behavior could change unpredictably.

In plugin agent definitions, model selection is maximally concise:

model: sonnet    # or opus, haiku

Three options. No fallbacks. No routing logic. The simplicity is the point.

OpenClaw: a routing engine for 20+ providers

OpenClaw’s src/agents/models-config.providers.ts is a 32,000-line model configuration system supporting:

  • Anthropic, OpenAI, Google Gemini
  • Ollama (local models), vLLM, sglang
  • Amazon Bedrock, Azure
  • Qwen (Tongyi), Moonshot (Kimi), MiniMax, ByteDance (Doubao/BytePlus), Xiaomi
  • OpenRouter, Together, Nvidia, Huggingface
  • GitHub Copilot, Vercel AI Gateway
  • …and any provider added through the plugin system

Each provider has independent capability declarations (src/agents/provider-capabilities.ts):

const PROVIDER_CAPABILITIES = {
  anthropic: {
    providerFamily: "anthropic",
    dropThinkingBlockModelHints: ["claude"],
  },
  mistral: { transcriptToolCallIdMode: "strict9" },
  openrouter: { geminiThoughtSignatureSanitization: true },
  // ...
};

Different providers have different tool-call formats, chain-of-thought handling, and tool call ID conventions. OpenClaw adapts at runtime automatically.

Agents also support model fallback chains:

{
  "model": {
    "primary": "claude-3.5-sonnet",
    "fallbacks": ["gpt-4o", "gemini-pro"]
  }
}

When the primary model is unavailable, the system degrades gracefully — a concept that does not exist in Claude Code’s world, by design.


Message channels: terminal vs every platform

Claude Code: terminal + IDE + GitHub

Claude Code’s interaction surface is the command-line terminal, VS Code/JetBrains extensions, and GitHub PR comments. Every channel targets developers.

OpenClaw: a gateway architecture for 40+ channels

OpenClaw’s src/gateway/ directory contains 341 files implementing a full message gateway:

extensions/
├── telegram/     # Telegram
├── discord/      # Discord
├── slack/        # Slack
├── whatsapp/     # WhatsApp
├── feishu/       # Feishu (Lark)
├── msteams/      # Microsoft Teams
├── matrix/       # Matrix
├── signal/       # Signal
├── imessage/     # iMessage
├── line/         # LINE
├── irc/          # IRC
├── nostr/        # Nostr
├── twitch/       # Twitch
├── voice-call/   # Voice calls
└── ...           # and more

Each channel is a standalone extension package registered through the plugin API’s registerChannel(). The message routing system (src/routing/resolve-route.ts) dispatches messages to the correct agent session based on channel type, account, conversation context, and group role.

This is why OpenClaw’s codebase is 16x larger than Claude Code’s. It is not just an agent — it is an agent runtime platform.


Security models: trusting the model vs trusting the code

Claude Code: layered permissions + sandboxing

Claude Code’s security model is configuration-driven. From the official settings-strict.json:

{
  "permissions": {
    "disableBypassPermissionsMode": "disable",
    "ask": ["Bash"],
    "deny": ["WebSearch", "WebFetch"]
  },
  "allowManagedPermissionRulesOnly": true,
  "allowManagedHooksOnly": true,
  "sandbox": {
    "autoAllowBashIfSandboxed": false,
    "network": {
      "allowedDomains": [],
      "allowLocalBinding": false
    }
  }
}

Three permission tiers (allow / ask / deny), Bash sandboxing, network domain whitelisting. Enterprise administrators can enforce overrides through managed-settings.json. The outer boundary is deterministic (sandbox, network policy), but the inner boundary — whether the model follows the intent described in a Markdown hook — relies on the model’s compliance.

OpenClaw: code-enforced security boundaries

OpenClaw’s security is enforced in code. The tool policy system (src/agents/tool-policy.ts):

// Tools are grouped into 4 profiles: minimal / coding / messaging / full
// Each profile has an explicit allow-list
const CORE_TOOL_PROFILES = {
  minimal: { allow: ["session_status"] },
  coding: { allow: ["read", "write", "edit", "exec", "web_search", ...] },
  messaging: { allow: ["message", "sessions_list", "sessions_history", ...] },
  full: {},  // unrestricted
};

// Owner-only tools: invisible to non-owner users
function applyOwnerOnlyToolPolicy(tools, senderIsOwner) {
  if (senderIsOwner) return tools;
  return tools.filter(tool => !isOwnerOnlyTool(tool));
}

Because OpenClaw is multi-user (via message channels), it must distinguish between owners and regular users. Sensitive tools like whatsapp_login, cron, gateway, and nodes are not “denied” for non-owners — they are removed from the tool list entirely. The model cannot call what it does not know exists.

The plugin loader also implements path traversal detection:

// Plugin entry files must reside within the plugin root directory
const opened = openBoundaryFileSync({
  absolutePath: candidate.source,
  rootPath: pluginRoot,
  boundaryLabel: "plugin root",
  rejectHardlinks: candidate.origin !== "bundled",
});

This is defense-in-depth through code, not through prompts asking the model to behave.


Design philosophy summary: what this means for agent builders

DimensionClaude CodeOpenClaw
Core beliefThe model is smart enough; natural language is codeThe model is not reliable enough; code is the boundary
Agent definitionDeclarative MarkdownImperative TypeScript
OrchestrationNatural-language-described pipelines; model self-orchestratesCode-defined lifecycles; runtime enforces execution
Plugin modelFolder conventions, zero codeMicrokernel + register() API
Model strategySingle vendor, deeply optimizedMulti-vendor, runtime-adapted
Security boundaryDeclarative config; trusts model complianceCode-enforced; does not trust model
Extension directionVertical: deep developer toolingHorizontal: all platforms, all scenarios
ComplexityLow (~28K lines of Markdown)Very high (~453K lines of TypeScript)

When to choose each approach

Go the Claude Code route (declarative / Markdown-driven) when:

  • Your agent targets a single, well-defined scenario (code review, doc generation, feature development)
  • You trust the underlying model and are willing to trade code for prompt engineering
  • You want non-programmers to be able to define agent behavior
  • Your users are developers and your interface is a terminal or IDE
  • You prioritize rapid iteration over runtime control

Go the OpenClaw route (imperative / code-driven) when:

  • Your agent must support multiple LLM providers
  • You need precise control over agent behavior boundaries, especially in multi-user scenarios
  • Your agent must deploy across platforms (messaging channels, APIs, native clients)
  • You need deterministic security guarantees that cannot depend on model “self-discipline”
  • You are building an agent platform, not a single agent

The deepest divergence

The split between Claude Code and OpenClaw comes down to one question:

“Can an AI model’s comprehension replace code’s determinism?”

Claude Code says: yes. Natural language is expressive enough to capture intent. The model is capable enough to execute on that intent. Code is an unnecessary intermediary. As model capabilities improve, the entire system gets stronger for free.

OpenClaw says: no. Models hallucinate. Models exceed their authority. Models break down at boundary conditions. Code is the only control mechanism that can be audited, tested, and formally verified. Safety and reliability cannot be built on probability.

Both answers are correct. It depends on what you are building.

If you are building a precision instrument for programmers — Claude Code’s approach is more elegant. A 200-line Markdown file that orchestrates 10 parallel agents is a genuinely beautiful piece of engineering, just not the kind most of us are used to recognizing.

If you are building a 24/7 agent platform serving diverse users across dozens of channels — OpenClaw’s approach is more robust. The 453,000 lines of TypeScript exist because every line is a boundary the model cannot cross.

Convergence ahead

The most interesting observation is that these two architectures are converging from opposite directions. Claude Code is steadily adding code-level security controls — sandboxing, managed settings, deterministic hooks via external scripts. OpenClaw is introducing more declarative configuration — YAML-based skills, Markdown knowledge bases, convention-over-configuration patterns.

Perhaps the ultimate answer is a hybrid: declarative where the model can be trusted (creative tasks, knowledge synthesis, code generation), imperative where it cannot (security boundaries, multi-tenancy isolation, financial transactions). The agent frameworks that figure out where to draw that line will define the next generation of AI infrastructure.


Analysis based on source code as of March 15, 2026. Claude Code: github.com/anthropics/claude-code. OpenClaw: github.com/openclaw/openclaw.