在理解了 Agent Loop、工具并行调度、上下文压缩与多智能体隔离的底层原理后,最扎实的进阶方式就是亲自动手组装一个属于自己的独立 Coding Agent。
本节我们将借鉴 Pi 的分层架构与 OpenClaw 的长期记忆哲学,从克隆运行时、定制模型提供商、编写领域工具、接入外部记忆,一直到集成飞书/Slack 机器人并实现 PM2 生产守护,带你完成代号为 [YourName]Claw 的个人助手。
整体架构装配图
我们要构建的系统架构如下:
flowchart TD
subgraph 接入渠道
CLI[终端命令行 TUI]
FS[飞书 Webhook 机器人]
SK[Slack Bolt 机器人]
end
subgraph 核心服务进程: [YourName]Claw
API[Express HTTP / Event 网关]
MEM[(MEMORY.md 长期记忆持久化)]
PROMPT[AGENTS.md 系统人设与规范]
LOOP[Agent Loop 异步事件循环]
TOOLS[内置原子工具 + 领域定制工具]
LLM[统一多 Provider 模型适配层]
end
CLI --> LOOP
FS --> API
SK --> API
API --> LOOP
LOOP <--> MEM
PROMPT --> LOOP
LOOP <--> TOOLS
LOOP <--> LLM
第一步:克隆并编译 Pi 基础运行时
首先获取上游开源运行时并确认基础环境可用:
1 2 3 4 5 6 7 8 9 10 11 12
| git clone https://github.com/earendil-works/pi.git my-claw cd my-claw
npm install --ignore-scripts
npm run build
./pi-test.sh
|
第二步:配置 LLM 模型 Provider
在项目根目录创建 .env 文件(务必加入 .gitignore,切忌提交密钥):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ANTHROPIC_API_KEY="sk-ant-api03-xxx" LLM_PROVIDER="anthropic" LLM_MODEL="claude-3-7-sonnet-20250219"
|
第三步:定制系统人设与行为守则(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
| 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
| 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
| 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 }) })
app.post('/webhook/feishu', async (req, res) => { const body = req.body if (body.type === 'url_verification') { return res.json({ challenge: body.challenge }) }
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
| 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
| 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)}%`) }
|
终期上线检查清单
总结
构建自己的 [YourName]Claw,意味着你不再是单纯消费第三方黑盒工具的用户,而是真正掌握了大模型与物理系统交互的控制权。
下一篇,也是本系列的收官之作:Agent 方向面试与实习准备——如何把源码与项目转化为不可替代的求职竞争力。