SkillClaw技能集体进化框架深度代码解析
SkillClaw 技能集体进化框架深度代码解析
摘要
SkillClaw(arXiv:2604.08377)提出了一种让 LLM Agent 的技能在多用户环境中持续进化的框架,核心洞察是:当前 Agent 系统(如 OpenClaw)的技能在部署后基本静态,不同用户重复踩同样的坑、重复发现相似工作流。SkillClaw 把跨用户、跨时间的交互轨迹作为信号,通过"自主 evolver"自动识别模式并更新共享技能库,实现"一次改进,全系统受益"的累积式能力提升。
本文不重复论文摘要,而是直接对照项目源码,回答一篇针对该论文的评审所提出的若干关键疑问:evolver 的算法到底是什么?是否引入了外部验证器作为"认识论锚点"?更新频率和粒度如何控制?技能冲突如何仲裁?错误累积与隐私隔离如何处理?技能库规模增大后检索效率如何保障?
读完本文你会发现:代码基本印证了评审基于摘要的推测,并把其中几个"摘要未说明"的点坐实了——evolver 是 LLM 反思式自举而非 RL,默认路径无外部锚点,冲突仲裁极浅,无 PII 脱敏,检索无工程化保障。但版本化与发布门设计相对扎实。
项目地址:https://github.com/AMAP-ML/SkillClaw
论文:https://arxiv.org/abs/2604.08377
1. 整体架构:两个组件,两套引擎
SkillClaw 由两个解耦组件构成,二者只通过共享存储(Alibaba OSS / S3 / 本地文件系统)和统一的技能格式(SKILL.md 目录)交互。
graph LR
U[用户 + Agent
Hermes/Codex/Claude Code/OpenClaw] -->|OpenAI 兼容 API| P[Client Proxy
skillclaw/]
P -->|自动记录轨迹| S[(共享存储
OSS/S3/local)]
S -->|读 sessions| E[Evolve Server
evolve_server/]
E -->|写 evolved SKILL.md| S
S -->|pull/reload| P
P -->|注入技能 catalog| U
- Client Proxy(
skillclaw/):本地 API 代理(/v1/chat/completions、/v1/messages、/v1/responses),拦截 agent 请求、记录会话产物、管理本地技能库。这是单用户起步所需的全部。 - Evolve Server(
evolve_server/):可选服务,从共享存储读 session 数据,演化或创建技能再写回。它支持两种引擎:workflow(默认):固定 3 阶段 LLM 管线(Summarize → Aggregate → Execute)agent:OpenClaw 驱动的工作区,agent 直接编辑技能
部署模型是渐进的:先单机用客户端,需要自动演化时加一台 evolve server,团队共享时多个客户端指向同一共享存储 + 一台 server。客户端与服务端只在共享存储里相遇。
2. Evolver 算法剖析:LLM 反思式自举,不是 RL
这是评审最关心的一个问题——“摘要未说明 evolver 的具体算法(是基于 LLM 的反思?规则引擎?还是 RL?)”。代码给出的答案非常明确:本质是 LLM 反思式自举 + 薄层规则记账,没有任何 RL。
workflow 引擎的编排位于 evolve_server/engines/workflow.py:894 的 run_once(),每个"认知"步骤都是一次 OpenAI 兼容 API 调用(客户端 evolve_server/core/llm_client.py:19):
| 阶段 | 文件:行 | 机制 | 温度 |
|---|---|---|---|
| 轨迹构建 | summarizer.py:114 |
规则(字符串格式化) | — |
| 元数据抽取 | summarizer.py:349 |
规则(字典扫描,提取 read/modified skills、prm 分、tool 错误) | — |
| 摘要 | summarizer.py:390 |
LLM 调用(每 session 一次) | 0.2 |
| 会话裁判 | session_judge.py:362 |
LLM 调用,固定权重打分 | 0.1 |
| 聚合 | aggregation.py:16 |
规则(按技能名分桶) | — |
| 演化(决策+产出合一) | execution.py:366 |
单次 LLM 调用,同时决定动作并产出新技能 | 0.4 |
| 冲突合并 | execution.py:265 |
LLM 调用 | 0.3 |
| 技能验证器 | skill_verifier.py:154 |
LLM 调用(门禁) | 0.1 |
关键点:决策与产出在同一次 LLM 调用里完成(evolve_skill_from_sessions,execution.py:366),动作枚举只有四种(evolve_server/core/constants.py:35-41):
1 | CREATE = "create_skill" |
演化系统提示词反复强调"做针对性编辑,不要重写"“宁可 skip 也别投机性编辑”,并有一条很关键的反模式警告(execution.py:125):
如果技能里已有正确 API 信息而 agent 没用上,那是 agent 的问题不是技能的问题,不要删掉正确信息。
会话裁判的打分权重是硬编码的(session_judge.py:90-95):task_completion 0.55 / response_quality 0.30 / efficiency 0.05 / tool_usage 0.10,且 session_judge.py:137 会用固定权重重算 overall 而不信任模型自报的 overall——这是个小的确定性保护。
关于 RL——完全没有。 PRM/裁判打出的分数只是存进元数据、作为文本喂给后续 prompt,不存在任何梯度、参数更新或策略优化。准确表述是:LLM-as-judge 驱动的离线反思式技能编辑,规则只负责记账(SHA-256、版本号、slug 正则),不负责认知。
3. 认识论锚点之问:是 LLM 自举,还是引入了外部验证器?
这是评审最尖锐的问题,代码层面的答案是:默认配置下纯 LLM 自举,没有任何外部锚点。 系统里三层所谓"验证"都是 LLM 判 LLM:
graph TD
T[session 轨迹] --> SUM[LLM 摘要]
SUM --> EV[LLM 演化
决策+产出]
EV --> V1{skill_verifier?}
V1 -->|默认关| PUB1[直接发布]
V1 -->|opt-in 开| LLMJ[LLM-as-judge 门禁]
LLMJ --> PUB2[发布/拒绝]
EV --> V2{publish_mode?}
V2 -->|validated opt-in| REPLAY[重放 A/B
LLM 重答+PRM 判分]
REPLAY --> PUB3[≥baseline 才发布]
3.1 PRM Scorer
skillclaw/prm_scorer.py:116:让模型对每轮回答投票 +1/-1/0,多数表决(prm_m 路并行,默认 3)。纯 LLM judge,无 ground-truth、无程序化正确性校验。
3.2 Skill Verifier
evolve_server/pipeline/skill_verifier.py:154:又一个 LLM 调用,作为"发布门禁"。但 use_skill_verifier 默认 False(config.py:105)。开启后是"LLM 判 LLM 产物是否值得发布",唯一确定性的部分是 min_score=0.75 的数值阈值比较(skill_verifier.py:228)。verifier 提示词明确:“你的工作不是改进技能,只是判断候选是否安全且值得发布”,并要求 fail-closed——verifier 调用失败/JSON 非法时也判 reject(skill_verifier.py:197-220)。
3.3 重放 A/B 验证
这是最接近"锚点"的东西,但也是 opt-in(publish_mode="validated",默认 direct,config.py:107)。做法(skillclaw/validation_worker.py:170):
- 拿候选技能和当前技能分别注入 prompt,让 LLM 重答同一指令(
_run_replay_branch,validation_worker.py:142); - 用 PRM judge 给两个回答打分;
- 候选须
candidate_mean >= baseline_mean 且 >= 0.75才算赢(validation_worker.py:208-210)。
问题在于:重放的"环境"只是 LLM 重新回答原始 user 指令,不重跑 agent 的工具、不对照 ground-truth 输出校验程序化正确性。更糟的是,重放案例还刻意偏好"无工具调用"的纯文本轮次(workflow.py:514)——即恰恰是工具/事实正确性无法校验的那类轮次。重放 worker 只在opted-in 的空闲客户端上跑(validation_worker.py:80/94-103,要求 idle + sharing + group_id)。
3.4 结论:默认路径欠债
- 默认(
publish_mode="direct"+use_skill_verifier=False):LLM 演化技能 → 直接写共享库,零非 LLM 校验。纯 LLM 自举,且形成闭环:LLM 写技能 → 写进共享库 → 该 LLM 未来又读这些技能。 - 开 verifier:多一道 LLM-as-judge 门,仍无外部锚点。
- validated 模式:LLM 重放 A/B + LLM PRM 判分,最接近 grounding,但仍全 LLM 判、偏好无工具轮次。
按"信任被验证取代、需要异源锚点"的视角,SkillClaw 默认路径正是评审担心的"LLM 自举无锚点"形态。确定性逻辑只存在于记账层(SHA-256 去重 workflow.py:338、版本自增 skill_registry.py:123、slug 正则),都不校验语义正确性。agent 引擎的 “self-validation” 也只要求 agent 写一份 history/vN_evidence.md,是 prompt 约束不是代码约束——测试只断言 markdown 里含规定字符串。
顺带回答"是否需要用户显式反馈":不需要。proxy 自动记录每轮(prompt、响应、工具调用/结果/错误),按 user-turn modulo 自动上传 session(
api_server.py:3055),PRM 打分也是后台自动。摘要说的"limited interaction and feedback"成立——它从原始轨迹工作,不向用户索要 thumbs-up。
4. 更新频率与粒度控制:多层节流,但默认偏激进
频率是多层独立节流叠加,粒度由 LLM 每组自选。评审问"过于激进导致不稳定"——确实存在缺口。
4.1 触发与节流(多层 Gate)
| Gate | 文件:行 | 作用 | 默认 |
|---|---|---|---|
| A. user-turn 取模 | api_server.py:3104-3114 |
主防抖门,只有 final 轮计入计数 |
sharing_session_upload_interval=0(禁用) |
| B. 定时轮询 | workflow.py:1015-1023 |
server 周期 run_once |
interval_seconds=600s |
| C. 技能重载轮询 | api_server.py:2005-2028 |
proxy 拉取技能更新 | 30s + 10% 抖动 + 失败退避封顶 60s |
| D. 内容 SHA 去重 | workflow.py:338-352 |
内容未变直接 skip | — |
| E. fail-closed 验证器 + validated 门 | 见第 3 节 | 发布前质量门 | 均默认关 |
Gate A 是主防抖:设为 2 即每 2 个用户轮上传一次快照并触发 evolve(commit 064de4a);只有 final 轮计入,内部 tool_use 轮不推进(api_server.py:239-241),避免每步上传。上传成功后才 POST {evolve_server}/trigger,且若响应报 uploaded_skills > 0 则立即拉新技能并 reload(commit 0995fc9)。
4.2 缺口:无 cooldown、无 min-sessions
没有显式 cooldown 计时器,没有"至少 N 个 session 才演化"的阈值。run_once 会处理单个 drain 出来的 session;一个 skill group 可以只由一个 session 构成。唯一上游上限是 evolve_batch_size(默认 20,config.py:102),限制每组喂给 summarizer 的 session 数。
4.3 粒度与工作版/发布版
LLM 每组在四种动作里选(create/improve/optimize_desc/skip)。Nacos 后端有明确的工作版 vs 发布版区分(测试 test_nacos_working_version_push.py、test_nacos_skill_lifecycle.py 确认):
- 发布版 =
latest标签指向的版本;运行时 pull 只取发布版(nacos_skill_hub.py:529-536)。 - 工作版 = editing/reviewing 状态、未打
latest,对运行时客户端不可见。 nacos_publish_mode默认review(config.py:123):上传+submit 后停在uploaded_pending_review,要 Nacos 发布管线/人工 approve 才上线。direct才直接打latest。- 硬安全规则:同名 reviewing 工作版内容不同则抛 RuntimeError 拒绝覆盖(
nacos_skill_hub.py:439-443)。
4.4 回滚
- Nacos:保留所有版本 +
skillclaw skills publish NAME VERSION手动重指latest(cli.py:868-885)。 - 对象存储:版本历史(
skill_registry.py:133-149,history 封顶 20 条)+pull_skills前整目录备份、失败即从备份恢复(skill_hub.py:700-796,保留 3 份备份)。 - 注意:Nacos 后端的 pull 没有备份/恢复逻辑,安全性完全依赖"只拉 published 版本"。没有自动回滚已发布远端版本的命令。
5. 技能冲突解决:很浅,且只在同名哈希碰撞时触发
评审担心"不同用户经验推出矛盾更新如何仲裁"。代码给出的答案是:没有真正的仲裁机制,只有 LLM 自行裁量 + 哈希判断是否需要调 LLM。
- 演化端唯一的冲突检测是 byte 级 SHA-256 比较(
workflow.py:338-352_detect_conflict)。两个语义等价但措辞不同的技能不会被判重复;两个针对同名技能的矛盾编辑才进合并路径。 - 合并由 LLM
execute_merge完成(execution.py:265),提示词(execution.py:25-56)让 LLM"矛盾时偏好更具体/具体的指引"——仲裁完全交给 LLM 判断,无确定性规则、无投票、无 per-user 权重。 - 致命的 last-write-wins 兜底(
workflow.py:354-381_resolve_and_upload):若 LLM merge 失败,直接用 incoming 版本覆盖(“keeping incoming version”),无拒绝、无重试、无人工门。 - 聚合按技能名分桶(
aggregation.py:16-46),不做跨用户去重、不做嵌入聚类、不检测"两个不同名字的技能其实是一回事"。新技能创建时靠 prompt 让 LLM 起不撞名的名字,若还是撞了,代码悄悄把create_skill降级成improve_skill(execution.py:456-465)。 - 嵌入去重只在客户端注入时跑(
skill_manager.py:433-477),演化端从不调用——不能阻止重复/重叠技能被写进共享库。
6. 错误累积风险:最强护栏默认关闭
评审担心"早期用户的错误模式被固化进共享技能库"。代码证实:默认路径下一个候选技能只经过一次 evolve_skill_from_sessions LLM 调用就直写共享库。 三层 gate 里只有 session judge 默认开,且它只打分不阻断:
| Gate | 默认 | 机制 | 是否阻断发布 |
|---|---|---|---|
| Session judge | 开 | LLM 打 overall 分,固定权重重算 | 否,只作为 evidence 文本 |
| Skill verifier | 关 | LLM-as-judge 门禁,min_score=0.75 |
开启后阻断 |
| 重放 A/B 验证 | 关 | 候选须 ≥ baseline 且 ≥ 0.75 | 开启后阻断 |
effectiveness 统计(skill_manager.py:244-284)会回写 skill_stats.json 并在检索排序里降权低效技能、push 时可过滤(skill_hub.py:350-363),但这只是客户端 push 过滤,不删除共享库里已有的坏技能。
reject 后无自动重试——同一 evidence 被消费删除(workflow.py:946-952 处理成功即删 session key),候选不再有第二次机会。所以"错误累积 → 系统性偏差"的风险是真实存在的,不是摘要没说清——是默认没护栏。
7. 隐私与隔离:无 PII 脱敏
代码里没有任何 PII 清洗/脱敏。grep 'pii|redact|scrub|anonym|pseudonym|mask' 全仓零代码命中。
- 原始用户内容被原样上传共享存储(
api_server.py:3073-3083):完整turns(含prompt_text原始输入、response_text、tool_calls含参数、tool_results、tool_errors)verbatim 上传到{group_id}/sessions/{session_id}.json。唯一身份字段是user_alias——自填句柄,不是伪匿名哈希,两个都叫 “alice” 的人会撞名。 - 写技能的 LLM 看到真实(裁剪过的)用户内容:summarizer 把每字段裁到 ~400 字(
summarizer.py:29-33),产出 8-15 句 LLM 摘要,然后_trajectory+_summary经execution.py:302-349_build_session_evidence喂给演化 LLM。裁剪是主要(且非刻意的)抽象层,但不是隐私机制,trajectory 里仍含真实裁剪后的 prompt 和工具 I/O。 - 技能本身没护栏防泄密:技能 prompt 让 LLM"压缩环境信息(API endpoint、端口、payload 格式)",但没有指令清洗 PII,也没有后处理扫描 SKILL.md 是否泄露用户内容。若 LLM 把用户的 API key/邮箱/文件路径原样写进技能,没有任何环节能拦住。verifier 的 “safe to publish” 指发布质量,不是隐私。
- 隔离是 group 级、非 user 级:组内所有人能读所有人的 session 和技能,无 per-user ACL。唯一按 alias 隔离的是验证结果(
validation_store.py:77-78),防一人多投,但候选技能内容本身组内共享。 - 稍好一点的是
agent引擎:agent_workspace.py:152-167写 compact session(trajectory+summary,刻意排除原始 turns),这是唯一刻意的抽象边界——但默认workflow引擎不走这条路径。 - session 处理后从队列删除(
workflow.py:946-952),但若had_processing_error则保留重试;演化出的技能内容永久存于{group_id}/skills/。
评审"跨用户共享涉及隐私边界"在企业场景是真的踩雷——代码层面没有任何脱敏,靠的是"用户自觉别把敏感数据喂给 agent"。
8. 检索与规模:无工程化保障,RAG 是死代码
这是评审又一个被代码证伪的点。当前实现没有工程化检索,规模线性膨胀,且嵌入检索代码是死代码。
任务时 SkillClaw 不做任何检索/匹配:它把全部合格技能作为一个 XML catalog 注入 system prompt,让模型自己扫 <description> 决定读哪个 SKILL.md(skill_manager.py:650-669 build_injection_prompt → get_all_skills 全量)。匹配规则写在注入提示里(skill_manager.py:620-648):“扫 available_skills 的 description,至多读一个 SKILL.md”。
8.1 死代码证据
语义检索 retrieve()(skill_manager.py:483)和 _embedding_retrieve(line 455)在全仓没有任何调用方——grep '\.retrieve(' 只命中它自己的定义。SentenceTransformer 嵌入、余弦相似、0.9 去重、effectiveness 加权打分(_weighted_score line 428-431)都写好了,但 request path 根本不走它。config.skill_top_k=6、retrieval_mode 开关也是惰性的。
8.2 规模后果
| 关切 | 行为 |
|---|---|
| top-k 检索 | 配置存在(skill_top_k=6)但 retrieve() 从不被调用,惰性 |
| 嵌入/RAG | 已实现但不可达 |
| catalog 上限 | 单一软上限 max_skills_prompt_chars(默认 30KB,config.py:37) |
| 超限降级 | compact 模式丢掉 <description> 只留 name+location(skill_manager.py:592-618),模型只能按名字匹配 |
| 对话历史 | _truncate_messages(api_server.py:3181-3216)削到 max_context_tokens(默认 20K);catalog 在 system message 里永不被截断 |
| 排序 | 注入时无排序 |
| 缓存 | embedding 缓存只在未用的嵌入模式下生效;refresh_if_changed() 靠 generation 指纹避免重解析 |
具体后果:catalog 在 system message 里无界增长,超 30KB 后丢 description 降级到 name-only 匹配,再往后 _truncate_messages 会越来越多地吃对话历史来压总长,而技能 catalog 仍占着 system message 不让。无分页、无 top-k、无索引、无 RAG,reload 时整目录重扫重解析 O(N) 非增量。可检索性几乎全压在 description 这一个短文本字段上。
9. 总评与思考
| 维度 | 评价 |
|---|---|
| evolver 算法 | LLM 反思式自举,非 RL;决策与产出在同一次调用 |
| 认识论锚点 | 默认无外部锚点,重放验证是 LLM 判 LLM 且 opt-in |
| 更新频率/粒度 | 多层节流但默认偏激进;无 cooldown、无 min-sessions |
| 冲突解决 | 极浅,SHA-only 触发,LLM 裁量 + last-write-wins 兜底 |
| 错误累积 | 真实风险,最强护栏默认关,reject 无重试 |
| 隐私隔离 | 无 PII 脱敏,group 级非 user 级隔离 |
| 检索规模 | 无工程化保障,RAG 死代码,30KB 后降级 name-only |
| 版本化与发布门 | 扎实:工作版/发布版分离、review 模式默认、pull 备份恢复、SHA 去重 |
代码基本印证了评审基于摘要的推测,并坐实了几个"摘要未说明"的点。相对更扎实的设计集中在版本化与发布门——Nacos 工作版/发布版分离、review 模式默认、对象存储 pull 备份恢复、SHA 去重——这部分工程上能站住脚。
而从 Agent Infra 文章"验证债"的视角看,SkillClaw 最该追问的"认识论锚点"恰恰是它最薄弱的环节:默认配置下确实欠债。一个被污染的技能会被所有用户继承,而唯一的"异源锚点"候选(重放 A/B)本身仍是 LLM 判 LLM,且偏好无法校验工具正确性的纯文本轮次。
这给后续工作留下清晰方向:
- 引入真正的外部锚点:把技能里的代码/命令做可执行校验(编译、lint、dry-run),而非 LLM 自评。
- 冲突解决升级:从 SHA-only 升级到语义去重(激活已写的嵌入路径),引入投票/per-user 权重仲裁。
- 检索工程化:把
retrieve()接入注入路径,让 catalog 不再全量塞 system prompt。 - 隐私层:在 session 上传前加脱敏,在 SKILL.md 发布前扫描泄露。
- 默认开启发布门:让
validated/use_skill_verifier默认开,把"集体进化"的安全网做实。
SkillClaw 的"集体进化视角"理念是对的——把技能更新从单用户单会话扩展到多用户跨时间的生态层面,这在方向上区别于传统 RAG 和 DSPy。但理念落地到代码,目前的实现是"理念先行、护栏后置",留给安全/可靠性的债并不小。
附:关键文件索引
| 关注点 | 文件 | 行 |
|---|---|---|
| 编排主循环 | evolve_server/engines/workflow.py |
894 |
| 冲突检测(SHA only) | evolve_server/engines/workflow.py |
338-352 |
| 合并 + last-write-wins 兜底 | evolve_server/engines/workflow.py |
354-381 |
| verifier reject 路径(无重试) | evolve_server/engines/workflow.py |
766-791 |
| 重放验证 finalize 阈值 | evolve_server/engines/workflow.py |
596-616 |
| LLM 演化决策+产出 | evolve_server/pipeline/execution.py |
366 |
| LLM 合并提示词 | evolve_server/pipeline/execution.py |
25-56 |
| create→improve 降级 | evolve_server/pipeline/execution.py |
456-465 |
| 按技能名聚合 | evolve_server/pipeline/aggregation.py |
16-46 |
| skill verifier(默认关) | evolve_server/pipeline/skill_verifier.py |
154-241 |
| session judge(默认开) | evolve_server/pipeline/session_judge.py |
185-200, 362 |
| 配置默认值(verifier 关、direct 发布) | evolve_server/core/config.py |
104-111 |
| 动作枚举 | evolve_server/core/constants.py |
35-41 |
| OpenAI 兼容 LLM 客户端 | evolve_server/core/llm_client.py |
19 |
| 版本/SHA 记账 | evolve_server/core/skill_registry.py |
105-151 |
| PRM 多数表决 judge | skillclaw/prm_scorer.py |
116 |
| 重放 A/B(≥baseline 才赢) | skillclaw/validation_worker.py |
170-210 |
| 原始 session 上传(无脱敏) | skillclaw/api_server.py |
3073-3083 |
| user-turn modulo 节流 | skillclaw/api_server.py |
3104-3114 |
| 技能重载轮询 | skillclaw/api_server.py |
2005-2028 |
| 全量注入 + catalog | skillclaw/skill_manager.py |
650-669 |
| 死代码 retrieve() | skillclaw/skill_manager.py |
483 |
| 嵌入去重(仅注入时) | skillclaw/skill_manager.py |
433-477 |
| Nacos pull 只取发布版 | skillclaw/nacos_skill_hub.py |
529-536 |
| Nacos 工作版 vs 发布版 | skillclaw/nacos_skill_hub.py |
249-299 |
| pull 前备份/失败恢复 | skillclaw/skill_hub.py |
700-796 |








