ZeroClaw 开发指南

版本:0.1.6 | 日期:2026-02-23
本文档帮助你从零开始参与 ZeroClaw 的开发。不管你是想加一个新的模型接口,还是接入一个新的聊天平台,或者给 Agent 加一个新工具——看完这篇就能上手。

环境搭建

必备工具

工具 最低版本 说明
Rust 1.87+ (Edition 2021) curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Git 2.x 版本管理
Docker 可选 本地 CI 验证

克隆和构建

# 克隆代码
git clone https://github.com/zeroclaw/zeroclaw.git
cd zeroclaw

# 开发构建(快,带调试信息)
cargo build

# Release 构建(慢,但体积小、性能好)
cargo build --release

# 带特定功能的构建
cargo build --features "channel-lark,memory-postgres"

本地验证

# 三件套(提交前必须跑)
cargo fmt --all -- --check    # 格式检查
cargo clippy --all-targets -- -D warnings  # 代码质量检查
cargo test                     # 跑测试

# 一键全量验证(推荐,如果有 Docker)
./dev/ci.sh all

项目目录速查

zeroclaw-main/
├── src/
│   ├── main.rs              # CLI 入口,命令路由
│   ├── lib.rs               # 模块导出
│   ├── agent/               # Agent 编排循环(核心大脑)
│   │   ├── loop_.rs          #   主循环(196KB,最重要的文件)
│   │   ├── agent.rs          #   Agent 结构体
│   │   ├── dispatcher.rs     #   工具调度器
│   │   └── prompt.rs         #   提示词构建
│   ├── providers/           # 模型提供者
│   │   ├── traits.rs         #   Provider trait 定义
│   │   ├── mod.rs            #   工厂注册
│   │   ├── openai.rs         #   OpenAI 实现
│   │   ├── anthropic.rs      #   Anthropic 实现
│   │   ├── gemini.rs         #   Gemini 实现
│   │   ├── ollama.rs         #   Ollama 实现
│   │   ├── reliable.rs       #   弹性包装器
│   │   └── ...
│   ├── channels/            # 消息频道
│   │   ├── traits.rs         #   Channel trait 定义
│   │   ├── mod.rs            #   工厂注册
│   │   ├── telegram.rs       #   Telegram
│   │   ├── discord.rs        #   Discord
│   │   └── ...(25 个实现)
│   ├── tools/               # 工具
│   │   ├── traits.rs         #   Tool trait 定义
│   │   ├── shell.rs          #   Shell 命令
│   │   ├── file_read.rs      #   文件读取
│   │   ├── browser.rs        #   浏览器自动化
│   │   └── ...(38 个实现)
│   ├── memory/              # 记忆系统
│   │   ├── traits.rs         #   Memory trait 定义
│   │   ├── sqlite.rs         #   SQLite 后端(默认)
│   │   ├── postgres.rs       #   PostgreSQL 后端
│   │   ├── embeddings.rs     #   向量嵌入
│   │   └── vector.rs         #   向量搜索
│   ├── security/            # 安全层
│   ├── gateway/             # 网关 HTTP 服务
│   ├── peripherals/         # 硬件外设
│   ├── runtime/             # 运行时适配
│   ├── observability/       # 可观测性
│   ├── config/              # 配置系统
│   ├── identity/            # 身份/人格
│   ├── cron/                # 定时任务
│   ├── hooks/               # 生命周期钩子
│   └── skills/              # 技能系统
├── tests/                   # 集成测试(15 个)
├── docs/                    # 文档(6 种语言)
├── .github/                 # CI/CD 工作流
├── Cargo.toml               # 依赖和构建配置
└── CLAUDE.md                # Agent 工程协议

快速定位规则


扩展开发:三大常见任务

ZeroClaw 的扩展模式高度一致:实现 trait → 注册到工厂 → 写测试。下面用具体例子演示。

任务一:添加新 Provider

假设你要接入一个新的模型服务 "MyAI"。

第一步:创建实现文件

src/providers/myai.rs

第二步:实现 Provider trait

use crate::providers::traits::*;
use async_trait::async_trait;

pub struct MyAiProvider {
    api_key: String,
    base_url: String,
}

impl MyAiProvider {
    pub fn new(api_key: String) -> Self {
        Self {
            api_key,
            base_url: "https://api.myai.com/v1".to_string(),
        }
    }
}

#[async_trait]
impl Provider for MyAiProvider {
    fn capabilities(&self) -> ProviderCapabilities {
        ProviderCapabilities {
            native_tool_calling: true,  // 是否支持原生函数调用
            vision: false,              // 是否支持图片
        }
    }

    async fn simple_chat(
        &self,
        message: &str,
        model: &str,
        temperature: f64,
    ) -> anyhow::Result<String> {
        // 调用 MyAI 的 API
        let client = reqwest::Client::new();
        let resp = client.post(&format!("{}/chat", self.base_url))
            .bearer_auth(&self.api_key)
            .json(&json!({
                "model": model,
                "temperature": temperature,
                "messages": [{"role": "user", "content": message}]
            }))
            .send()
            .await?;

        // 解析响应
        let body: serde_json::Value = resp.json().await?;
        Ok(body["choices"][0]["message"]["content"]
            .as_str()
            .unwrap_or("")
            .to_string())
    }

    async fn chat_with_system(
        &self,
        system_prompt: Option<&str>,
        message: &str,
        model: &str,
        temperature: f64,
    ) -> anyhow::Result<String> {
        // 类似实现,加上 system 消息
        todo!("实现带系统提示的聊天")
    }

    async fn chat_with_history(
        &self,
        messages: &[ChatMessage],
        model: &str,
        temperature: f64,
    ) -> anyhow::Result<String> {
        todo!("实现多轮对话")
    }

    async fn chat(
        &self,
        request: ChatRequest<'_>,
        model: &str,
        temperature: f64,
    ) -> anyhow::Result<ChatResponse> {
        todo!("实现完整聊天(含工具调用)")
    }

    fn supports_native_tools(&self) -> bool { true }
    fn supports_vision(&self) -> bool { false }
}

第三步:注册到工厂

src/providers/mod.rs 中:

mod myai;

// 在工厂函数中添加匹配分支
pub fn create_provider(name: &str, config: &Config) -> Result<Box<dyn Provider>> {
    match name {
        "openai" => { /* ... */ },
        "anthropic" => { /* ... */ },
        "myai" => Ok(Box::new(myai::MyAiProvider::new(
            config.api_key.clone().context("MyAI 需要 api_key")?,
        ))),
        _ => bail!("未知的 provider: {}", name),
    }
}

第四步:使用

# zeroclaw.toml
default_provider = "myai"
api_key = "your-myai-key"
default_model = "myai-pro"

任务二:添加新 Channel

假设你要接入 "WeChat Work"(企业微信)。

第一步:创建实现文件

src/channels/wechat_work.rs

第二步:实现 Channel trait

use crate::channels::traits::*;
use async_trait::async_trait;

pub struct WeChatWorkChannel {
    corp_id: String,
    agent_id: String,
    secret: String,
}

#[async_trait]
impl Channel for WeChatWorkChannel {
    fn name(&self) -> &str {
        "wechat_work"
    }

    async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
        // 调用企业微信 API 发送消息
        // 1. 获取 access_token
        // 2. POST /cgi-bin/message/send
        todo!("实现发送消息")
    }

    async fn listen(
        &self,
        tx: tokio::sync::mpsc::Sender<ChannelMessage>,
    ) -> anyhow::Result<()> {
        // 长期监听消息
        // 方式 1: 轮询 API
        // 方式 2: 接收 Webhook 回调
        loop {
            // 收到消息时:
            let msg = ChannelMessage {
                id: "msg-id".to_string(),
                sender: "user-id".to_string(),
                reply_target: "user-id".to_string(),
                content: "消息内容".to_string(),
                channel: "wechat_work".to_string(),
                timestamp: 1234567890,
                thread_ts: None,
            };
            tx.send(msg).await?;
        }
    }

    async fn health_check(&self) -> bool {
        // 检查 API 是否可用
        true
    }

    async fn start_typing(&self, _recipient: &str) -> anyhow::Result<()> {
        // 企业微信不支持"正在输入",直接返回 Ok
        Ok(())
    }
}

第三步:注册到工厂并添加配置

src/channels/mod.rs 工厂函数和 src/config/schema.rs 配置结构中注册。


任务三:添加新 Tool

假设你要给 Agent 加一个"翻译"工具。

// src/tools/translate.rs
use crate::tools::traits::*;
use async_trait::async_trait;

pub struct TranslateTool {
    api_key: String,
}

#[async_trait]
impl Tool for TranslateTool {
    fn name(&self) -> &str {
        "translate"
    }

    fn description(&self) -> &str {
        "将文本从一种语言翻译成另一种语言"
    }

    fn parameters_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "text": {
                    "type": "string",
                    "description": "要翻译的文本"
                },
                "from": {
                    "type": "string",
                    "description": "源语言(如 en, zh, ja)"
                },
                "to": {
                    "type": "string",
                    "description": "目标语言(如 en, zh, ja)"
                }
            },
            "required": ["text", "to"]
        })
    }

    async fn execute(
        &self,
        args: serde_json::Value,
    ) -> anyhow::Result<ToolResult> {
        let text = args["text"].as_str().unwrap_or("");
        let to = args["to"].as_str().unwrap_or("en");

        // 调用翻译 API
        let translated = call_translation_api(text, to).await?;

        Ok(ToolResult {
            success: true,
            output: translated,
            error: None,
        })
    }
}

要点


配置系统详解

配置文件位置

ZeroClaw 按以下优先级查找配置:

  1. 命令行参数(--provider, --model 等)
  2. 环境变量
  3. 工作区配置文件(./zeroclaw.toml
  4. 用户配置文件(~/.config/zeroclaw/config.toml
  5. 内置默认值

配置结构全景

# ===== 基础 =====
api_key = "sk-..."                    # 模型 API 密钥
api_url = "https://api.openai.com/v1" # 自定义 API 地址
default_provider = "openai"           # 默认 Provider
default_model = "gpt-4o"             # 默认模型
default_temperature = 0.7             # 温度参数

# ===== 记忆 =====
[memory]
backend = "sqlite"           # sqlite / postgres / markdown / lucid / none
auto_save = true             # 自动保存对话
embedding_provider = "none"  # 向量嵌入:none / openai / custom:https://...
vector_weight = 0.7          # 混合搜索中向量的权重
keyword_weight = 0.3         # 混合搜索中关键字的权重

# ===== 自治与安全 =====
[autonomy]
level = "supervised"           # readonly / supervised / full
workspace_only = true          # 只能操作工作区目录
allowed_commands = ["git", "npm", "cargo"]  # 命令白名单
forbidden_paths = ["/etc", "/root", "~/.ssh"]  # 路径黑名单

[security]
# 沙箱、速率限制等

# ===== 网关 =====
[gateway]
port = 42617
host = "127.0.0.1"
require_pairing = true
allow_public_bind = false

# ===== 运行时 =====
[runtime]
kind = "native"               # native / docker
[runtime.docker]
image = "alpine:3.20"
memory_limit_mb = 512

# ===== 频道 =====
[channels_config.telegram]
bot_token = "123456:ABC-DEF..."
allowed_users = ["user_id_1", "user_id_2"]
mention_only = false

[channels_config.discord]
bot_token = "..."
guild_id = "123456789"
allowed_users = []             # 空 = 拒绝所有
listen_to_bots = false

# ===== 可观测性 =====
[observability]
# Log / Prometheus / OTEL 配置

# ===== 弹性 =====
[reliability]
# 重试、超时、降级配置

# ===== 硬件 =====
[peripherals]
[[peripherals.boards]]
board = "nucleo-f401re"
path = "/dev/ttyACM0"

# ===== 高级 =====
[model_routes]     # 按任务类型路由模型
[proxy]            # 代理配置
[tunnel]           # 隧道配置
[composio]         # Composio 集成
[browser]          # 浏览器配置
[hooks]            # 生命周期钩子
[agents]           # 子 Agent 定义

热重载

以下配置修改后下一条消息自动生效,无需重启:


测试

运行测试

# 所有测试
cargo test

# 指定模块
cargo test --test agent_e2e            # Agent 端到端
cargo test --test config_schema        # 配置校验
cargo test --test memory_restart       # 记忆持久化
cargo test --test whatsapp_webhook_security  # WhatsApp 安全

# 带输出
cargo test -- --nocapture

# 带特定 feature
cargo test --features "channel-lark"

集成测试一览

测试文件 测试什么
agent_e2e.rs Agent 完整流程
agent_loop_robustness.rs Agent 压力测试
channel_routing.rs 消息路由
config_schema.rs 配置校验
config_persistence.rs 配置热重载
memory_restart.rs 记忆跨重启持久化
memory_comparison.rs SQLite vs Markdown 性能对比
provider_resolution.rs Provider 工厂解析
telegram_attachment_fallback.rs Telegram 附件降级
whatsapp_webhook_security.rs WhatsApp HMAC 校验
hooks_integration.rs 生命周期钩子
otel_dependency_feature_regression.rs Feature 门控回归
dockerignore_test.rs Docker 构建排除
reply_target_field_regression.rs 消息线程回归

写测试的原则


CI/CD 流程

核心工作流

工作流 触发条件 做什么
ci-run.yml PR 提交 格式 + Clippy + 测试 + 覆盖率
ci-build-fast.yml PR 提交 快速增量构建
feature-matrix.yml PR 提交 Feature flag 组合测试
sec-audit.yml 定期 依赖安全审计
main-promotion-gate.yml 合并到 main 合并前门禁
pub-release.yml 发布 构建发布产物
pub-docker-img.yml 发布 Docker 镜像
pub-homebrew-core.yml 发布 Homebrew tap

本地跑 CI

# 如果有 Docker(推荐)
./dev/ci.sh all

# 没有 Docker 时的最小验证
cargo fmt --all -- --check && cargo clippy --all-targets -- -D warnings && cargo test

代码规范

命名约定

场景 规则 示例
模块/文件 snake_case telegram.rs, file_read.rs
类型/Trait/枚举 PascalCase DiscordChannel, MemoryCategory
函数/变量 snake_case health_check, api_key
常量/静态 SCREAMING_SNAKE_CASE DEFAULT_MAX_TOOL_ITERATIONS
trait 实现命名 <名称><类型> OpenAiProvider, TelegramChannel, ShellTool
工厂注册键 小写,用户可见 "openai", "telegram", "shell"

错误处理

// ✓ 好:用 anyhow 的 bail!/context
use anyhow::{bail, context};

async fn do_thing(config: &Config) -> anyhow::Result<()> {
    let key = config.api_key.as_ref()
        .context("需要配置 api_key")?;

    if key.is_empty() {
        bail!("api_key 不能为空");
    }

    Ok(())
}

// ✗ 坏:unwrap / expect 在运行时路径
let key = config.api_key.unwrap(); // 别这么干

安全编码

// ✓ 好:路径规范化 + 工作区检查
let path = std::fs::canonicalize(user_path)?;
if !path.starts_with(&workspace_dir) {
    bail!("路径超出工作区范围: {}", path.display());
}

// ✓ 好:凭据脱敏
let output = scrub_credentials(&raw_output);

// ✗ 坏:直接拼接用户输入到命令
let cmd = format!("ls {}", user_input); // SQL 注入的 Shell 版本!

PR 提交流程

分支策略

main ← 稳定分支,所有改动通过 PR 合并
  └── feature/add-wechat-work ← 你的功能分支
  └── fix/telegram-attachment ← 修复分支

禁止直接推送到 main

提交信息格式

使用 conventional commits:

feat(channels): add WeChat Work channel
fix(memory): handle empty query in recall
docs(readme): update provider list
chore(ci): add feature matrix workflow
refactor(agent): extract credential scrubbing
test(gateway): add pairing timeout test

PR 模板要点

每个 PR 必须说清楚:

  1. 改了什么(Problem + Solution)
  2. 没改什么(Non-goals)
  3. 风险(Side effects / Blast radius)
  4. 回滚方案(How to rollback)
  5. 验证方式(Tests run / Manual verification)

PR 大小建议

标签 行数 说明
size: XS < 20 行 配置修改、Typo
size: S 20-100 行 小功能、Bug 修复
size: M 100-500 行 中等功能
size: L 500-1000 行 大功能(尽量拆分)
size: XL > 1000 行 避免,除非有充分理由

常见问题

编译报错 "feature X not found"

# 某些模块需要开启对应 feature
cargo build --features "channel-lark"       # 飞书
cargo build --features "memory-postgres"    # PostgreSQL
cargo build --features "browser-native"     # 浏览器

Clippy 报错

# 查看具体哪里有问题
cargo clippy --all-targets -- -D warnings 2>&1 | head -50

# Clippy 的建议通常是对的,照做就行

测试超时

# 默认超时可能不够,加长
cargo test -- --test-threads=1  # 单线程跑,避免资源竞争

想看某个模块的文档

# 生成并打开 Rust 文档
cargo doc --open

# 看特定模块
cargo doc --open -p zeroclaw --document-private-items

参考文档

文档 路径 说明
工程协议 CLAUDE.md Agent 编码规范(最权威)
贡献指南 CONTRIBUTING.md 贡献流程
命令参考 docs/commands-reference.md CLI 命令详解
配置参考 docs/config-reference.md 配置项全集
Provider 参考 docs/providers-reference.md 各 Provider 配置
Channel 参考 docs/channels-reference.md 各频道配置
运维手册 docs/operations-runbook.md 日常运维
故障排除 docs/troubleshooting.md 常见问题
安全文档 docs/security/ 安全架构和审计
硬件文档 docs/hardware-peripherals-design.md 外设协议
PR 流程 docs/pr-workflow.md PR 工作流
评审手册 docs/reviewer-playbook.md 代码评审指南
CI 地图 docs/ci-map.md CI 工作流说明