01

What is OpenClaw?

Your personal AI gateway โ€” one brain, every chat app

The Big Idea

Imagine you have a brilliant assistant that can read files, search the web, manage your calendar, and write code. Now imagine it can talk to you through any messaging app โ€” WhatsApp, Telegram, Discord, Feishu, even iMessage.

That's OpenClaw. It's an open-source gateway that connects your favorite chat apps to powerful LLMs like Claude, GPT, and Gemini. One configuration, all your channels.

A Message's Journey (The 30-Second Version)

Let's trace what happens when you send "What's the weather today?" from WhatsApp:

1
You type in WhatsApp

Your message hits the WhatsApp channel plugin

2
OpenClaw's Gateway receives it

The gateway figures out which session you belong to and routes the message

3
Auto-Reply takes over

The message processing pipeline checks for commands, builds context, and calls the LLM

4
The AI responds

The LLM's reply flows back through the pipeline, gets formatted, and lands in your WhatsApp chat

The Entry Point

Every journey starts at src/entry.ts. When you run openclaw in your terminal, this is the first file that executes. Here's what kicks things off:

CODE
if (
  !isMainModule({
    currentFile: fileURLToPath(import.meta.url),
    wrapperEntryPairs: [...ENTRY_WRAPPER_PAIRS],
  })
) {
  // Imported as a dependency โ€” skip
} else {
  process.title = "openclaw";
  ensureOpenClawExecMarkerOnProcess();
  installProcessWarningFilter();
  normalizeEnv();
}
PLAIN ENGLISH

First, check: "Am I being run directly by the user, or was I loaded as a library by someone else?"

If loaded as a library, do nothing โ€” we don't want duplicate gateways crashing into each other.

If we're the main show: label the process "openclaw" so it's easy to find in task managers.

Set up environment markers, filter noisy Node.js warnings, and normalize environment variables so everything is predictable.

๐Ÿ’ก
Why This Guard Matters

The isMainModule check prevents a classic problem: if the bundler imports entry.ts as a dependency while index.ts is the actual entry point, the startup code would run twice โ€” launching two gateways that fight over the same port. This guard pattern is common in any Node.js app that can be both a library and a standalone program.

Check Your Understanding

What is OpenClaw's primary role?

02

Meet the Cast

The five core subsystems that make OpenClaw tick

The Five Pillars

OpenClaw's source code is organized into distinct subsystems, each with a clear responsibility. Think of them as team members in a company โ€” each has a job title and doesn't step on anyone else's toes.

๐Ÿ—๏ธ
Gateway

The boss โ€” boots everything up, manages auth, serves HTTP, handles WebSocket connections

๐Ÿ“ก
Channels

The translators โ€” each plugin speaks a different messaging platform's language

๐Ÿค–
Auto-Reply

The brain coordinator โ€” processes inbound messages, detects commands, calls the LLM

๐Ÿ’พ
Sessions

The memory keeper โ€” tracks conversation state, manages session lifecycle

โš™๏ธ
Config

The rule book โ€” YAML-based settings with hot-reload, schema validation, and migration

The Project Structure

src/ All the source code lives here
gateway/ Core server: auth, boot, HTTP, WebSocket, config reload
channels/ Plugin system for Telegram, WhatsApp, Discord, etc.
auto-reply/ Message pipeline: inbound โ†’ command check โ†’ LLM call โ†’ dispatch
sessions/ Conversation state, lifecycle events, transcript tracking
config/ YAML parsing, schema validation, hot-reload, migration
agents/ LLM runner, tool handling, skills, sub-agent spawning
routing/ Maps inbound messages to the right session and agent
entry.ts CLI entry point โ€” where it all begins

The Team Introduces Themselves

Let's let the components speak for themselves:

#openclaw-internals
0 / 5 messages

The Gateway's Boot Sequence

The Gateway is the most complex subsystem. When it starts, it orchestrates a careful dance:

CODE
const BOOT_FILENAME = "BOOT.md";

function buildBootPrompt(content: string) {
  return [
    "You are running a boot check.",
    "Follow BOOT.md instructions exactly.",
    "",
    "BOOT.md:",
    content,
  ].join("\n");
}
PLAIN ENGLISH

When the gateway boots, it looks for a file called BOOT.md in your workspace.

If found, it builds a special prompt that tells the AI: "Read these boot instructions and follow them."

This lets you automate startup tasks โ€” like sending a "Good morning!" message or checking your calendar when the server starts.

Quick Check

You want to add support for a new chat platform (say, Matrix). Which subsystem do you modify?

03

How a Message Travels

Follow a message from your phone to the AI and back

The Complete Data Flow

Click "Next Step" to trace a message through every component. Watch which subsystem lights up at each stage.

๐Ÿ“ฑ
You
๐Ÿ“ก
Channel
๐Ÿ”€
Routing
๐Ÿค–
Auto-Reply
๐Ÿง 
LLM
Click "Next Step" to begin the journey
Step 0 / 8

The Routing Decision

When a message arrives, the routing system makes a crucial decision. It examines the sender, channel, group, and configuration to determine which agent and session should handle the message:

CODE
export type ResolveAgentRouteInput = {
  cfg: OpenClawConfig;
  channel: string;
  accountId?: string | null;
  peer?: RoutePeer | null;
  parentPeer?: RoutePeer | null;
  guildId?: string | null;
  memberRoleIds?: string[];
};
PLAIN ENGLISH

To figure out where a message goes, the router needs several pieces of context:

cfg โ€” the full configuration (rules, bindings, agent definitions)

channel โ€” which platform? (telegram, whatsapp, discordโ€ฆ)

peer โ€” who's talking? (a DM contact, a group chat)

guildId / memberRoleIds โ€” Discord-specific: which server and what roles does the user have? This enables role-based agent routing.

The Dispatch

After routing, the dispatchInboundMessage function takes over. It's the central coordinator that ties everything together:

CODE
export async function dispatchInboundMessage(params: {
  ctx: MsgContext;
  cfg: OpenClawConfig;
  dispatcher: ReplyDispatcher;
}) {
  const finalized = finalizeInboundContext(params.ctx);
  return await withReplyDispatcher({
    dispatcher: params.dispatcher,
    run: () =>
      dispatchReplyFromConfig({
        ctx: finalized,
        cfg: params.cfg,
        dispatcher: params.dispatcher,
      }),
  });
}
PLAIN ENGLISH

This function is the handoff point. It receives the message context (who sent it, from where, what they said) and the full config.

First, "finalize" the context โ€” fill in any missing details, normalize the data.

Then, wrap everything in a reply dispatcher that manages typing indicators, buffering, and cleanup. The dispatcher ensures replies go out in order and resources are always released โ€” even if something crashes.

Inside, dispatchReplyFromConfig does the real work: it calls the AI, streams the response, and sends it back to the user.

๐Ÿ’ก
The "withReplyDispatcher" Pattern

Notice how the code wraps the AI call inside withReplyDispatcher? This is the resource management pattern โ€” the dispatcher is "reserved" before the work starts and "released" in a finally block, guaranteeing cleanup even if the AI call fails. This is the same principle behind database connection pools, file handles, and mutexes. It ensures typing indicators stop and channels aren't left hanging.

04

The Channel Plugin System

How one codebase speaks every messaging language

Built-in Channels

OpenClaw ships with plugins for nine messaging platforms, defined in a single array:

CODE
export const CHAT_CHANNEL_ORDER = [
  "telegram",
  "whatsapp",
  "discord",
  "irc",
  "googlechat",
  "slack",
  "signal",
  "imessage",
  "line",
] as const;
PLAIN ENGLISH

This is the official list of built-in chat channels. The order matters โ€” it defines priority when the system needs to pick a default.

The as const makes TypeScript treat these as exact literal strings, not just "any string." This means the compiler catches typos like "telegarm" at build time.

Beyond these built-in channels, external plugins (like Feishu, Microsoft Teams) can register themselves at runtime through the plugin system.

The Plugin Architecture

Each channel plugin implements a set of adapters that define how it interacts with the rest of the system:

๐Ÿ“จ

MessagingAdapter

Receiving and sending messages โ€” the core of any channel plugin

๐Ÿ”

AuthAdapter

Login, QR codes, token refresh โ€” each platform authenticates differently

๐Ÿงต

ThreadingAdapter

Thread/topic support โ€” Discord threads, Telegram topics, Slack threads

๐Ÿ“Š

StatusAdapter

Connection health, error reporting, and diagnostics

๐ŸŽค

StreamingAdapter

Real-time message streaming โ€” edit-in-place as the AI types

๐Ÿ‘ฅ

GroupAdapter

Group chat behavior โ€” mentions, join/leave, group-specific settings

How Plugins Register

The plugin registry uses a pattern where bundled plugins are auto-discovered from generated code, and external plugins can register at runtime:

CODE
export function normalizeAnyChannelId(
  raw?: string | null
): ChannelId | null {
  const key = normalizeChannelKey(raw);
  if (!key) return null;
  return findRegisteredChannelPluginEntry(key)
    ?.plugin.id ?? null;
}
PLAIN ENGLISH

When the system encounters a channel name (from config, a message, etc.), it normalizes it โ€” trims whitespace, lowercases it.

Then it searches all registered plugins (both built-in and external) by ID and aliases. "wa" might match "whatsapp", for example.

If no match is found, it returns null โ€” the message came from an unknown channel and can't be processed.

๐Ÿ”Œ
The Plugin Pattern

OpenClaw's channel system is a textbook example of the Strategy pattern: define an interface (the adapters), then let each implementation (Telegram, WhatsApp, etc.) handle the details differently. The core system never needs to know how WhatsApp sends a message โ€” it just calls send() on whatever plugin is active. This makes adding new platforms a matter of implementing the adapters, not rewriting the core.

Application Quiz

A user reports that their Discord bot shows typing indicators but never sends the actual reply. Which adapter would you investigate first?

05

Skills & Agents

How OpenClaw extends its capabilities and spawns workers

What Are Skills?

Skills are OpenClaw's extensibility layer. They're simple Markdown files (SKILL.md) that get injected into the AI's system prompt. Each skill teaches the agent how to do something new.

feishu-bitable Create and manage Feishu spreadsheet-like databases
github Issues, PRs, CI runs, code review via gh CLI
coding-agent Delegate coding tasks to Codex, Claude Code, or Pi agents
weather Get forecasts via wttr.in or Open-Meteo APIs

How Skills Get Loaded

The system prompt builder scans for skills and presents them to the AI as a menu of capabilities:

CODE
function buildSkillsSection(params: {
  skillsPrompt?: string;
  readToolName: string;
}) {
  return [
    "## Skills (mandatory)",
    "Before replying: scan <available_skills>",
    `- If exactly one skill applies: read its SKILL.md with \`${params.readToolName}\`, then follow it.`,
    "- If multiple could apply: choose the most specific one.",
    "- If none apply: do not read any SKILL.md.",
    params.skillsPrompt,
  ];
}
PLAIN ENGLISH

This builds the "Skills" section that goes into the AI's system prompt. It's like handing someone a toolbox catalog.

The instructions tell the AI: "Before you respond to the user, check if any skill matches their request."

If a match is found, the AI reads the full SKILL.md file using its file-reading tool โ€” loading detailed instructions on-demand rather than cramming everything into the prompt upfront.

This is a lazy-loading pattern: skills are listed as summaries, and full instructions are only fetched when needed.

Sub-Agent Spawning

Sometimes a single AI brain isn't enough. OpenClaw can spawn independent sub-agents for parallel work:

CODE
export type SpawnSubagentParams = {
  task: string;
  label?: string;
  agentId?: string;
  model?: string;
  thinking?: string;
  runTimeoutSeconds?: number;
  mode?: SpawnSubagentMode;
  attachments?: Array<{
    name: string;
    content: string;
    encoding?: "utf8" | "base64";
  }>;
};
PLAIN ENGLISH

When spawning a sub-agent, you configure it with:

task โ€” what should this worker do? (natural language description)

model โ€” which AI brain? (Claude, GPT, Geminiโ€ฆ)

thinking โ€” how deeply should it reason? (low/medium/high)

runTimeoutSeconds โ€” kill it if it takes too long

attachments โ€” files to give the sub-agent to work with

It's like hiring a contractor: here's the job, the budget (timeout), and the materials (attachments).

Sub-Agents in Action

#agent-orchestration
0 / 5 messages
๐Ÿ’ก
Concurrency Without Complexity

Sub-agents are OpenClaw's answer to the "one thing at a time" limitation of AI conversations. Instead of making you wait while the AI writes a 3,000-line file, the main agent spawns a worker, stays responsive, and notifies you when the work is done. It's the same pattern as web workers in browsers or goroutines in Go โ€” delegate heavy work so the main thread stays snappy.

Scenario Challenge

A user asks their OpenClaw assistant: "Research this company and write a report." The task will take 10+ minutes. What's the best approach?

06

The Big Picture

Full architecture diagram + when to customize what

Interactive Architecture

Click any component to see what it does:

Messaging Platforms (External)
๐Ÿ’ฌ WhatsApp
โœˆ๏ธ Telegram
๐ŸŽฎ Discord
๐Ÿฆ Feishu
๐Ÿ“ฑ Others
OpenClaw Gateway (Core)
๐Ÿ“ก Channel Registry
๐Ÿ”€ Router
๐Ÿค– Auto-Reply
๐Ÿ’พ Sessions
โš™๏ธ Config
๐ŸŒ HTTP Server
AI & Tools Layer
๐Ÿง  LLM Runner
๐Ÿ”ง Tools
๐Ÿ“š Skills
๐Ÿ‘ฅ Sub-Agents
LLM Providers (External)
๐ŸŸฃ Claude
๐ŸŸข GPT
๐Ÿ”ต Gemini
โšก Others
๐Ÿ‘† Click any component above to learn what it does

When to Customize What

Now that you understand the architecture, here's your guide for where to make changes:

๐Ÿ“ก
New messaging platform?

Create a channel plugin in src/channels/plugins/. Implement the adapter interfaces. The rest of the system works automatically.

๐Ÿ“š
New AI capability?

Write a SKILL.md file and install it to ~/.openclaw/skills/. No code changes needed โ€” skills are pure Markdown.

๐Ÿ”€
Custom routing logic?

Edit the YAML config with binding rules. The routing engine supports per-peer, per-guild, per-role, and per-channel bindings.

๐Ÿง 
Different AI provider?

Add a provider config in YAML. OpenClaw supports 50+ providers through its OpenAI-compatible API layer, plus native integrations for Anthropic, Google, and more.

๐Ÿค–
Custom message processing?

Use hooks in the config to inject custom behavior at various points in the auto-reply pipeline โ€” before/after tool calls, on message receive, etc.

Final Challenge

You want your OpenClaw agent to respond in Spanish when messages come from a specific Telegram group, but English everywhere else. Where do you configure this?

๐ŸŽ‰ Course Complete!

You now understand how OpenClaw works โ€” from entry point to AI response. The codebase is open source, so go explore!

View on GitHub โ†’