ZeroClaw 开发指南
本文档帮助你从零开始参与 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 工程协议
快速定位规则:
- 想改模型调用? →
src/providers/ - 想改消息收发? →
src/channels/ - 想改 Agent 行为? →
src/agent/loop_.rs - 想改安全策略? →
src/security/ - 想改配置项? →
src/config/schema.rs - 想加新工具? →
src/tools/
扩展开发:三大常见任务
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,
})
}
}
要点:
parameters_schema()返回标准 JSON Schema,LLM 根据这个 schema 构造参数execute()接收的args就是 LLM 传过来的参数 JSON- 返回
ToolResult,success: false时填写error字段
配置系统详解
配置文件位置
ZeroClaw 按以下优先级查找配置:
- 命令行参数(
--provider,--model等) - 环境变量
- 工作区配置文件(
./zeroclaw.toml) - 用户配置文件(
~/.config/zeroclaw/config.toml) - 内置默认值
配置结构全景
# ===== 基础 =====
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 定义
热重载
以下配置修改后下一条消息自动生效,无需重启:
default_provider/default_model/default_temperaturereliability相关配置
测试
运行测试
# 所有测试
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 | 消息线程回归 |
写测试的原则
- 测试命名格式:
<主题>_<预期行为>(如config_missing_api_key_returns_error) - 使用项目内的 mock 实现,不依赖外部服务
- 测试数据用中性标识符(
test_user,zeroclaw_bot),不用真实个人信息 - 保持确定性——不依赖网络、不依赖时序
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 必须说清楚:
- 改了什么(Problem + Solution)
- 没改什么(Non-goals)
- 风险(Side effects / Blast radius)
- 回滚方案(How to rollback)
- 验证方式(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 工作流说明 |