OpenClaw 08:从零构建个人 Coding Agent——工程实战与部署

在理解了 Agent Loop、工具并行调度、上下文压缩与多智能体隔离的底层原理后,最扎实的进阶方式就是亲自动手组装一个属于自己的独立 Coding Agent。

本节我们将借鉴 Pi 的分层架构与 OpenClaw 的长期记忆哲学,从克隆运行时、定制模型提供商、编写领域工具、接入外部记忆,一直到集成飞书/Slack 机器人并实现 PM2 生产守护,带你完成代号为 [YourName]Claw 的个人助手。


整体架构装配图

我们要构建的系统架构如下:


第一步:克隆并编译 Pi 基础运行时

首先获取上游开源运行时并确认基础环境可用:

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 克隆代码仓库
git clone https://github.com/earendil-works/pi.git my-claw
cd my-claw

# 2. 安装 npm workspace monorepo 依赖
npm install --ignore-scripts

# 3. 编译所有 TypeScript 包
npm run build

# 4. 跑通基础测试,确认终端交互正常
./pi-test.sh

第二步:配置 LLM 模型 Provider

在项目根目录创建 .env 文件(务必加入 .gitignore,切忌提交密钥):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 方案 A:使用 Anthropic Claude(推荐,指令遵循与工具调用能力极强)
ANTHROPIC_API_KEY="sk-ant-api03-xxx"
LLM_PROVIDER="anthropic"
LLM_MODEL="claude-3-7-sonnet-20250219"

# 方案 B:使用 OpenAI 兼容协议(DeepSeek / 智谱 / Kimi)
# OPENAI_API_KEY="sk-xxx"
# OPENAI_BASE_URL="https://api.deepseek.com/v1"
# LLM_PROVIDER="openai"
# LLM_MODEL="deepseek-chat"

# 方案 C:使用 Google Gemini
# GOOGLE_API_KEY="AIzaSyxxx"
# LLM_PROVIDER="google"
# LLM_MODEL="gemini-2.0-flash"

第三步:定制系统人设与行为守则(AGENTS.md)

创建专属的系统指令文件 AGENTS.md,明确定义你的 Agent 的行为边界和工作方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- AGENTS.md - [YourName]Claw 的工作契约 -->
你是 [YourName]Claw,专注于现代前端与 Node.js 架构的资深编码助理。

## 核心行为准则
1. **读先于写**:在修改任何文件前,必须先调用 Read 工具确认目标代码及其上下游依赖;
2. **渐进式验证**:单次代码修改控制在单一逻辑单元内,修改完毕立即执行针对性单测;
3. **模糊主动确认**:若用户需求存在歧义,主动提出明确选项,严禁凭空猜测;
4. **统一表达**:技术思考过程与用户对话统一使用中文,代码标识符与注释遵循英文规范。

## 安全与物理红线
- 严禁读写或打印 `.env` 及任何带有私钥格式的文件;
- 严禁直接执行未经用户确认的 `git push` 命令;
- 严禁递归删除工程根目录以外的物理路径。

第四步:注入领域专属工具

除了默认的 Read、Write、Edit、Bash 和 Grep 外,针对你的业务流程注入自动化工具。例如自动部署预发环境的 DeployTool:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// packages/coding-agent/src/core/tools/deploy.ts
import { exec } from 'child_process'
import { promisify } from 'util'
import { ToolDefinition } from './types'

const execAsync = promisify(exec)

export const deployTool: ToolDefinition<{ environment: 'staging' | 'preview' }> = {
name: 'Deploy',
description: '将当前工作区分支构建并部署到指定的测试/预发环境。仅在用户明确发出部署请求时调用。',
parameters: {
type: 'object',
properties: {
environment: {
type: 'string',
enum: ['staging', 'preview'],
description: '目标部署环境'
}
},
required: ['environment']
},
execute: async ({ environment }) => {
try {
const { stdout } = await execAsync(`pnpm run build && ./scripts/deploy.sh --env ${environment}`)
return `部署指令执行成功:\n${stdout}`
} catch (err: any) {
return `部署失败: ${err.message}`
}
}
}

在工具注册中心中挂载:

1
2
3
4
import { defaultTools } from './default'
import { deployTool } from './deploy'

export const myClawTools = [...defaultTools, deployTool]

第五步:挂载 MEMORY.md 跨会话长期记忆

在用户主目录下建立持久化记忆目录与索引文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// packages/agent/src/memory/file-memory.ts
import * as fs from 'fs'
import * as path from 'path'
import * as os from 'os'

const MEMORY_DIR = path.join(os.homedir(), '.myclaw', 'memory')
const INDEX_FILE = path.join(MEMORY_DIR, 'MEMORY.md')

export function loadLongTermMemory(): string {
if (!fs.existsSync(INDEX_FILE)) {
fs.mkdirSync(MEMORY_DIR, { recursive: true })
fs.writeFileSync(INDEX_FILE, '# Long-term Memory Index\n- [偏好配置](preferences.md)\n', 'utf-8')
return ''
}
return fs.readFileSync(INDEX_FILE, 'utf-8')
}

在组装 systemPrompt 时动态拼装记忆内容:

1
2
3
4
5
const memoryText = loadLongTermMemory()
const finalSystemPrompt = [
baseSystemPrompt,
memoryText ? `\n\n## 跨会话长期记忆\n${memoryText}` : ''
].join('\n')

第六步:部署为常驻服务并接入飞书/Slack

1. 消息网关服务封装

编写一个轻量的 Express 监听服务,使 Agent 具备 HTTP 与 Webhook 接入能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// packages/coding-agent/src/server.ts
import express from 'express'
import { agentLoop } from '@earendil-works/pi-agent-core'
import { myClawTools } from './core/tools'

const app = express()
app.use(express.json())

const sessionStore = new Map<string, any[]>()

app.post('/api/chat', async (req, res) => {
const { sessionId = 'default', message } = req.body
const history = sessionStore.get(sessionId) || []
history.push({ role: 'user', content: message })

let replyText = ''

for await (const event of agentLoop({
tools: myClawTools,
messages: history,
systemPrompt: '...'
})) {
if (event.type === 'message_end' && event.content.role === 'assistant') {
replyText = event.content.content
}
}

history.push({ role: 'assistant', content: replyText })
sessionStore.set(sessionId, history)

res.json({ reply: replyText })
})

// 飞书 Webhook 回调接入
app.post('/webhook/feishu', async (req, res) => {
const body = req.body
// 1. 处理飞书开放平台 URL 校验握手
if (body.type === 'url_verification') {
return res.json({ challenge: body.challenge })
}

// 2. 异步触发 Agent 处理消息并调用飞书 API 回传
// ...
res.json({ code: 0 })
})

app.listen(3000, () => console.log('[YourName]Claw running on port 3000'))

2. 使用 PM2 守护进程

1
2
3
4
5
6
7
8
9
# 全局安装 PM2
npm install -g pm2

# 启动常驻进程
pm2 start "node packages/coding-agent/dist/server.js" --name "myclaw-agent"

# 设置开机自启
pm2 save
pm2 startup

第七步:搭建 Eval 评测集(量化可用性)

不要仅凭主观感觉评估 Agent 的效果,编写一套自动化 Eval 基准:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// eval/benchmark.ts
interface TestCase {
id: string
prompt: string
expectedKeywords: string[]
expectedToolCalls: string[]
}

const testSuites: TestCase[] = [
{
id: 'T1_read_readme',
prompt: '读取 README.md 的前 20 行并输出项目名称',
expectedKeywords: ['MyClaw'],
expectedToolCalls: ['Read']
},
{
id: 'T2_fix_syntax',
prompt: '修复 src/utils.ts 中未闭合括号的语法错误',
expectedKeywords: ['修复完成'],
expectedToolCalls: ['Edit', 'Bash']
}
]

export async function runBenchmark() {
let passCount = 0
for (const tc of testSuites) {
const { output, calls } = await executeAgentTask(tc.prompt)
const kwPass = tc.expectedKeywords.every(k => output.includes(k))
const toolPass = tc.expectedToolCalls.every(t => calls.includes(t))

if (kwPass && toolPass) {
passCount++
console.log(`[PASS] ${tc.id}`)
} else {
console.error(`[FAIL] ${tc.id}`)
}
}

console.log(`Pass Rate: ${(passCount / testSuites.length * 100).toFixed(1)}%`)
}

终期上线检查清单

  • 本地克隆并成功执行 npm run build,编译零错误;
  • .env 配置了有效的 Anthropic / OpenAI / Gemini 密钥,并加入 .gitignore;
  • 跑通基础终端交互,验证 Read(带 offset/limit)与 Edit 工具调用成功;
  • 编写了专属 AGENTS.md,确立行为契约与安全禁区;
  • 启用了 MEMORY.md 本地索引文件,支持跨会话记忆留存;
  • 编写了 Express / Webhook 监听接口,成功接收消息回传;
  • 搭建了至少 5 个自动化 Eval 用例,单次测试通过率可量化;
  • PM2 托管运行,具备崩溃自动重启与开机自启能力。

总结

构建自己的 [YourName]Claw,意味着你不再是单纯消费第三方黑盒工具的用户,而是真正掌握了大模型与物理系统交互的控制权。

下一篇,也是本系列的收官之作:Agent 方向面试与实习准备——如何把源码与项目转化为不可替代的求职竞争力。