乐于分享
好东西不私藏

OpenClaw 科普系列(23):源码架构——深入理解 OpenClaw 内部

OpenClaw 科普系列(23):源码架构——深入理解 OpenClaw 内部

前几期我们聊了 Agent Workspace,了解了如何组织工作空间。现在让我们深入 OpenClaw 的内部——它的源码架构、核心模块和设计思想。

本期我们来解析 OpenClaw 的源码结构,帮助你理解它是如何工作的。

项目概览

OpenClaw 是一个基于 Node.js/TypeScript 的开源项目,采用模块化架构设计。

仓库结构

openclaw/
├── apps/                    # 应用程序
│   ├── mac/                # macOS 菜单栏应用
│   ├── ios/                # iOS 应用
│   └── android/            # Android 应用
├── packages/               # 核心包
│   ├── gateway/            # Gateway 核心
│   ├── cli/                # 命令行工具
│   ├── agent/              # Agent 运行时
│   ├── tools/              # 工具实现
│   └── protocol/           # 通信协议
├── extensions/             # 扩展插件
│   └── plugins/            # 官方插件
├── docs/                   # 文档
└── scripts/                # 构建脚本

核心架构

Gateway 架构

Gateway 是 OpenClaw 的核心,采用分层架构:

┌─────────────────────────────────────┐
│           API Layer                 │
│  (HTTP REST / WebSocket / CLI)     │
├─────────────────────────────────────┤
│         Service Layer               │
│  (Auth / Routing / Session / Cron) │
├─────────────────────────────────────┤
│         Agent Runtime               │
│  (Model / Tools / Memory / Skills) │
├─────────────────────────────────────┤
│        Channel Adapters             │
│  (WhatsApp / Telegram / Discord)   │
├─────────────────────────────────────┤
│         Data Layer                  │
│  (File System / State / Logs)      │
└─────────────────────────────────────┘

模块职责

       

         
           
           
         

模块 职责
gateway WebSocket/HTTP 服务器,消息路由
agent LLM 调用,工具编排,提示词管理
channels 各消息渠道的适配器
tools 工具实现(exec, fs, browser 等)
skills Skill 加载和管理
memory 记忆存储和检索
cron 定时任务调度
sessions 会话管理

       

     

数据流

消息处理流程

用户消息
    ↓
Channel Adapter (解析消息格式)
    ↓
Gateway (路由到对应 Agent)
    ↓
Agent Runtime (构建提示词)
    ↓
LLM (生成响应)
    ↓
Tool Calls (如需执行工具)
    ↓
Channel Adapter (发送回复)
    ↓
用户

会话生命周期

1. 消息到达 → 创建/恢复 Session
2. 构建上下文 → 加载历史 + 系统提示
3. LLM 调用 → 生成响应或工具请求
4. 工具执行 → 获取结果
5. 继续对话 → 或结束会话
6. 保存状态 → 更新 Session 存储

核心组件详解

Gateway 服务

// 简化的 Gateway 结构
class
 Gateway {
  private
 wss: WebSocketServer;
  private
 httpServer: HTTPServer;
  private
 agents: Map<string, Agent>;
  private
 channels: Map<string, Channel>;
  
  async
 start() {
    // 初始化 WebSocket 服务器

    this
.wss = new WebSocketServer({ port: this.config.port });
    
    // 设置连接处理

    this
.wss.on('connection', this.handleConnection);
    
    // 初始化频道

    await
 this.initChannels();
  }
  
  private
 handleConnection(ws: WebSocket, req: Request) {
    // 认证检查

    // 路由到对应处理器

  }
}

Agent 运行时

// 简化的 Agent 结构
class
 Agent {
  private
 model: LLMProvider;
  private
 tools: ToolRegistry;
  private
 memory: MemoryStore;
  private
 skills: SkillManager;
  
  async
 processMessage(message: string, session: Session) {
    // 1. 构建系统提示词

    const
 systemPrompt = this.buildSystemPrompt(session);
    
    // 2. 获取历史上下文

    const
 history = await this.memory.getHistory(session.key);
    
    // 3. 调用 LLM

    const
 response = await this.model.generate({
      system
: systemPrompt,
      messages
: [...history, { role: 'user', content: message }],
      tools
: this.tools.getAvailableTools(),
    });
    
    // 4. 处理工具调用

    if
 (response.toolCalls) {
      const
 results = await this.executeTools(response.toolCalls);
      // 继续对话...

    }
    
    // 5. 保存到记忆

    await
 this.memory.save(session.key, message, response);
    
    return
 response.content;
  }
}

工具系统

// 工具注册表
class
 ToolRegistry {
  private
 tools: Map<string, Tool>;
  
  register
(tool: Tool) {
    this
.tools.set(tool.name, tool);
  }
  
  async
 execute(name: string, params: any) {
    const
 tool = this.tools.get(name);
    if
 (!tool) throw new Error(`Tool ${name} not found`);
    return
 tool.execute(params);
  }
}

// 工具定义示例

const
 execTool: Tool = {
  name
: 'exec',
  description
: 'Execute shell commands',
  parameters
: {
    type
: 'object',
    properties
: {
      command
: { type: 'string' },
      cwd
: { type: 'string' },
    },
    required
: ['command'],
  },
  async
 execute(params) {
    // 执行命令并返回结果

  },
};

通信协议

WebSocket 协议

Gateway 使用 WebSocket 进行实时通信:

// 消息格式
interface
 WSMessage {
  id
: string;           // 消息 ID
  type
: 'request' | 'response' | 'event';
  method
: string;       // 方法名
  params
?: any;         // 参数
  result
?: any;         // 结果
  error
?: ErrorInfo;    // 错误信息
}

// 示例:发送消息

{
  id
: "msg-123",
  type
: "request",
  method
: "chat.send",
  params
: {
    sessionKey
: "agent:main:main",
    message
: "Hello"
  }
}

内部事件系统

// 事件总线
class
 EventBus {
  private
 handlers: Map<string, Function[]>;
  
  on
(event: string, handler: Function) {
    if
 (!this.handlers.has(event)) {
      this
.handlers.set(event, []);
    }
    this
.handlers.get(event).push(handler);
  }
  
  emit
(event: string, data: any) {
    const
 handlers = this.handlers.get(event) || [];
    handlers.forEach(h => h(data));
  }
}

// 使用示例

eventBus.on('message.received', (msg) => {
  console
.log(`New message: ${msg.content}`);
});

扩展机制

插件架构

// 插件接口
interface
 Plugin {
  id
: string;
  version
: string;
  activate
(context: PluginContext): void;
  deactivate
(): void;
}

// 插件上下文

interface
 PluginContext {
  registerTool
(tool: Tool): void;
  registerCommand
(command: Command): void;
  on
(event: string, handler: Function): void;
}

// 示例插件

const
 myPlugin: Plugin = {
  id
: 'my-plugin',
  version
: '1.0.0',
  activate
(context) {
    context.registerTool(myCustomTool);
    context.on('message.received', handleMessage);
  },
  deactivate
() {
    // 清理资源

  },
};

Skill 加载机制

class SkillManager {
  private
 skills: Map<string, Skill>;
  
  async
 loadFromDirectory(path: string) {
    const
 files = await fs.readdir(path);
    
    for
 (const file of files) {
      if
 (file.endsWith('SKILL.md')) {
        const
 content = await fs.readFile(`${path}/${file}`, 'utf-8');
        const
 skill = this.parseSkill(content);
        this
.skills.set(skill.name, skill);
      }
    }
  }
  
  parseSkill
(content: string): Skill {
    // 解析 YAML frontmatter + Markdown

    const
 { frontmatter, body } = parseFrontmatter(content);
    return
 {
      name
: frontmatter.name,
      description
: frontmatter.description,
      instructions
: body,
      metadata
: frontmatter.metadata,
    };
  }
}

数据存储

文件结构

~/.openclaw/
├── openclaw.json          # 主配置
├── credentials/           # 凭证存储
│   ├── whatsapp/
│   └── oauth.json
├── agents/
│   └── <agentId>/
│       ├── agent/
│       │   └── auth-profiles.json
│       └── sessions/
│           ├── sessions.json
│           └── *.jsonl
├── cron/
│   ├── jobs.json
│   └── runs/
└── logs/
    └── openclaw-YYYY-MM-DD.log

会话存储格式

sessions.json

{
  "agent:main:main"
: {
    "sessionId"
: "sess-abc123",
    "updatedAt"
: 1712487600000,
    "model"
: "anthropic/claude-sonnet-4",
    "contextTokens"
: 2048,
    "totalTokens"
: 10240
  }

}

*.jsonl(会话记录):

{"role":"system","content":"You are a helpful assistant...","timestamp":"2024-04-07T10:00:00Z"}
{"role":"user","content":"Hello","timestamp":"2024-04-07T10:00:05Z"}
{"role":"assistant","content":"Hi there!","timestamp":"2024-04-07T10:00:06Z"}

构建和开发

本地开发

# 克隆仓库
git clone https://github.com/openclaw/openclaw.git
cd
 openclaw

# 安装依赖

npm install

# 构建

npm run build

# 运行测试

npm test

# 本地运行 Gateway

npm run dev:gateway

调试技巧

# 启用详细日志
DEBUG=openclaw:* npm run dev:gateway

# 特定模块日志

DEBUG=openclaw:agent npm run dev:gateway

# TypeScript 调试

npm run build:watch

贡献代码

提交 PR 流程

  1. 1. Fork 仓库
  2. 2. 创建分支git checkout -b feature/my-feature
  3. 3. 编写代码:遵循现有代码风格
  4. 4. 添加测试:确保代码有测试覆盖
  5. 5. 提交更改git commit -m "feat: add new feature"
  6. 6. 推送分支git push origin feature/my-feature
  7. 7. 创建 PR:描述更改内容和测试方法

代码规范

  • TypeScript:严格类型检查
  • ESLint:统一代码风格
  • Prettier:自动格式化
  • 测试:单元测试 + 集成测试

总结

OpenClaw 的架构设计遵循以下原则:

  • 模块化:各组件职责清晰,易于扩展
  • 插件化:通过插件机制支持自定义功能
  • 协议化:标准通信协议,支持多客户端
  • 文件化:数据以文件形式存储,便于管理和备份

理解源码架构有助于:

  • • 更好地使用和配置 OpenClaw
  • • 开发自定义插件和 Skill
  • • 排查问题和贡献代码
  • • 构建基于 OpenClaw 的应用

OpenClaw 是开源项目,欢迎参与贡献!

下一期,我们将探讨 贡献开源——如何为 OpenClaw 社区做出贡献。