我如何让 AI 助手学会自己进化技能?
一次关于架构设计的深度思考
缘起:被"复制粘贴"困住的工程师
最近在搭建 OpenClaw 的自进化系统,其中一个核心模块是让 AI 的 Skill(技能)能够自我学习和进化。
最初的实现方式是:在每个 Skill 脚本里埋一段"信号上报"代码。
比如 zenus-wechat-publishing 这个 Skill,每次执行完一个操作,就调用 emit_signal 把结果写入日志。当累积到一定次数,就触发"进化"逻辑——生成补丁、推送通知、用户确认、版本升级。
代码大概是这个样子:
defemit_signal(skill_name: str, signal_type: str, payload: dict = None):
"""把信号写入指定目录,更新计数,触发进化"""
# 写入信号日志
# 更新 Pattern 计数
# 达到阈值则创建触发标记
功能是跑通了,但我很快意识到一个问题:
如果明天我要开发一个新的 Skill(比如 zenus-email 邮件助手),它想接入自进化系统,就必须把这 ~80 行代码复制粘贴一遍。
这不是我想要的架构。
一次关键的自我追问
我在思考这个问题时,问了自己一个问题:
Skill 的定位到底是什么?
答案很快就清晰了——Skill 应该是通用技能模块,就像人类的技能库。技能库里的每一项技能都应该是拿来就能用、不附带任何"监控代码"的纯通用组件。
Skill 不应该知道: - 自己被谁调用 - 是否被纳入自进化系统 - 有没有人在观察自己的表现
这就好比: - 一把锤子不需要"上报"自己被用了多少次 - 一本菜谱不需要"埋点"记录读者的反馈 - 一个真正的技能模块,也不应该包含任何"业务之外"的特殊逻辑
这成了我后续所有设计的出发点。
方案对比:5 条路,哪条走得通?
明确了"Skill 零改动"的原则后,问题变成了:如果 Skill 不能主动上报,观察者怎么知道它执行了什么?
我列出了 5 种可能的方案:
方案 A:Shell History 审计 - 思路:监听 shell 历史记录 - 优点:零侵入、实时性高 - 缺点:依赖历史配置、无法判断执行结果
方案 B:会话上下文提取 - 思路:从对话历史中解析执行记录 - 优点:不改 Skill、能获取完整上下文 - 缺点:依赖会话 API、无法捕获后台执行
方案 C:统一执行入口(Wrapper) - 思路:所有 Skill 经过同一个代理 - 优点:完全可控 - 缺点:需改变调用习惯
方案 D:Skill 日志轮询 - 思路:Skill 写日志到固定位置,Observer 轮询 - 优点:改动相对小 - 缺点:仍有侵入、轮询有延迟
方案 E:平台层审计 - 思路:在 OpenClaw 底层统一记录所有调用 - 优点:对 Skill 完全透明 - 缺点:需平台支持,二次开发成本高
一个更清晰的思路
在讨论中,我的思路逐渐清晰:
自进化时同步执行,从上下文中提取调用 skill 的记录、是否成功等内容,然后对 skill 进行总结。成功和失败都是经验——成功告诉我们"怎么做才对",失败告诉我们"哪里容易出错"。
这意味着:观察从外部进行,Skill 零改动。
Observer 不需要 Skill 主动配合,它只需要"旁观"会话历史,从中提取自己需要的信息。
最终方案:Context Observer
┌─────────────────────────────────────────────────────────┐
│ Skill 通用技能库 │
│ skill_1 / skill_2 / skill_3 / ... │
│ 纯通用组件,无任何特殊逻辑 │
└─────────────────────────────────────────────────────────┘
↑
被调用执行
│
┌─────────────────────────────────────────────────────────┐
│ 外部观察层(Observer) │
│ 不侵入 Skill,从外部监听执行 │
│ - 从会话上下文提取执行记录 │
│ - 判断成功/失败/质量问题 │
│ - 累积 Pattern,触发进化 │
└─────────────────────────────────────────────────────────┘
关键设计原则: - Skill 不知道自己被观察,也不需要知道 - 观察者从外部"偷听"Skill 的执行 - 成功和失败都是经验,都能触发学习
实现细节:如何从会话中提取信息?
OpenClaw 的会话历史(transcript)是 JSONL 格式,每行是一个事件对象。当 AI 执行一条命令时,会留下这样的记录:
{"type":"message","message":{"role":"assistant","content":[
{"type":"toolCall","name":"exec","arguments":{"command":"zenus_wechat.py humanize /tmp/article.md"}}
]}}
{"type":"message","message":{"role":"toolResult","content":"...AI 痕迹已去除..."}}
Observer 只需要:
一、读取 transcript 文件
二、找到 toolCall 中包含 zenus_wechat.py 的命令
三、检查后续 toolResult 判断执行结果(成功/失败/质量问题)
4. 写入信号日志
defextract_skill_executions(conversation):
for msg in messages:
for tc in tool_calls:
if "zenus_wechat.py" in tc.command:
result = check_tool_result(next_msg)
yield {
"skill": "zenus-wechat-publishing",
"command": parse(tc.command),
"result": result # success / error / quality_issue
}
这就是"外部观察"的含义——Skill 不知道自己被记录,Observer 自己从会话历史中提取信息。
完整流程
Heartbeat 触发(每30分钟)
│
├── context_observer.py ← 从会话上下文提取信号
│ 读取 transcript 文件
│ 提取 tool_calls 中的 Skill 执行
│ 分析 tool_result 判断成功/失败
│ 写入 signals/
│ 达到阈值 → 创建 pending 触发
│
├── check_skill_evolution.py ← 检测 pending,执行 Generator
│
└── Inbox 提案 → 微信通知确认
│
└── 确认 → 更新 Skill 版本
这次设计的几点感悟
一、通用性 vs 侵入性的权衡
最初的"硬编码上报"方案虽然快,但代价是每个 Skill 都要复制同样的代码。真正的通用组件应该具备"即插即用"的特性,任何 Skill 接入自进化系统,都不需要修改自身代码。
二、外部观察优于内部上报
让 Observer 从外部提取信息,Skill 保持纯净。这不仅降低了耦合,也更符合"模块化"的设计哲学——每个模块只管自己的事,不掺和其他模块的业务逻辑。
三、成功和失败都是学习素材
传统认知里,我们只记录失败来"打补丁"。但成功的经验同样宝贵——它告诉我们"这样做是对的",值得固化到 Skill 的能力里。
4. 架构设计先于具体实现
如果一上来就写代码,大概率会陷入"先硬编码再重构"的循环。先花时间想清楚"Skill 的定位是什么""观察应该从哪一层进行",会让后续实现少走很多弯路。
结语
这就是我设计 Skill 自进化框架的过程:从"每个 Skill 埋代码"到"外部统一观察",本质上是一次从"侵入式"到"观察式"的架构演进。
核心洞察只有一句话:Skill 不知道自己被观察,也不需要知道。观察者从外部提取信息,Skill 保持纯净。
这个原则不仅适用于 Skill 自进化,应该也适用于很多其他场景。
本文是 Zenus 自进化系统开发过程的技术复盘,方案已经过实际验证。
夜雨聆风