递归进化:让 Agent 成为自己的程序员 (Voyager 范式)
What(本文讲什么)
“让 agent 自己写技能”不是一句浪漫口号,而是一条工程管线:在任务现场发现缺口,生成一段可执行代码,把它放进隔离环境里验证,通过后版本化入库,并在未来被检索与复用。
Voyager 论文把这件事拆成三个关键组件:自动 curriculum、不断增长的 skill library(可执行代码)、以及结合环境反馈与执行错误的迭代自我改进机制。我们会沿着这个拆法,把“自写技能”落到可上线的系统设计。
Problem(要解决的工程问题)
当你的 agent 需要长时间运行并覆盖更多任务域时,它会不断遇到“工具缺口”:
- 缺一个解析器: 日志格式/私有协议/特殊文件结构。
- 缺一个修复器: 某类错误的最小 patch 生成与验证。
- 缺一个裁剪器: 上下文装配、去噪、摘要结构化。
你当然可以把这些都手写进工具箱,但这会带来两个现实问题:
- 需求到代码的交付延迟: agent 越强,场景越多,手写跟不上。
- 体系碎片化: 临时脚本越来越多,缺少统一的验证、版本化、审计和回滚。
因此“自写技能”的真正工程目标是:缩短能力迭代周期,同时把风险收口到可控边界(隔离、权限、审计),避免变成供应链事故制造机。
Principle(技能的生命周期:生成不难,治理才难)
把技能当成一个可发布工件(artifact),它至少要经历下面几个阶段:
- 需求化(Requirement): 明确输入输出契约与边界条件。
- 合成(Synthesis): 生成代码,但代码只是候选。
- 验证(Verification): 单元测试 + 沙箱运行,失败则迭代修复。
- 存证(Provenance): 记录来源、hash、依赖、测试结果、生成时间与作者(agent id)。
- 发布(Release): 进入 skill registry,并可被检索。
- 灰度(Shadow/Canary): 先在影子环境跑真实任务回放,避免直接上生产。
- 退役(Retire): 低命中、低质量或高风险技能要被下线或替换。
这条生命周期的意义是:让“自写技能”可审计、可回滚、可复盘,而不是把代码扔进一个文件夹就算完成。
Usage(怎么用:一条可上线的 Skill Genesis Pipeline)
1) 技能契约:先定义“什么算一个技能”
一个能复用的技能必须是“边界清晰、依赖可控”的。建议最小契约包含:
name: 稳定命名(带版本)。input_schema: 输入字段与类型(可用 JSON Schema)。output_schema: 输出字段与类型。side_effects: 是否允许写文件/联网/执行命令(默认禁止)。timeout_ms: 最大运行时间(超时)。idempotency: 幂等 key 策略(对有副作用的技能必须有)。
这样 runtime 才能在调用前做权限与隔离检查。
2) 产物结构:代码、测试、元数据要一起入库
不要只存代码。至少需要三件东西:
skill.py: 技能实现。test_skill.py: 单测(可在沙箱内跑)。skill.json: 元数据(schema、hash、依赖、来源、审计字段)。
示例(元数据):
{
"name": "parse_private_log_v1",
"version": "1.0.0",
"input_schema": {"type": "object", "properties": {"text": {"type": "string"}}, "required": ["text"]},
"output_schema": {"type": "object", "properties": {"items": {"type": "array"}}, "required": ["items"]},
"side_effects": "none",
"timeout_ms": 2000,
"created_at": "2026-04-21T00:00:00Z",
"created_by": "agent:skill-genesis",
"source": {
"task_id": "task-xxx",
"trace_id": "trace-yyy",
"dossier": ".agents/runs/.../research/articles/03-self-writing-skills-agent.md"
},
"sha256": "..."
}
这份元数据的价值是审计与复盘:当技能造成事故时,你能追到它从哪里来、为什么被允许发布。
3) 沙箱验证:把副作用隔离当成第一性原则
自写技能最危险的地方不是“写错逻辑”,而是“写出副作用”。因此验证环境必须把能力收口:
- 文件系统只读,或仅允许写入
/tmp。 - 默认禁止网络(除非技能明确声明需要联网)。
- 禁止执行任意命令,只允许调用白名单工具。
- 强制超时,避免死循环把 CPU 烧干(超时)。
验证通过不代表可以上生产,但验证失败一定不能入库。
4) 迭代修复:把错误当作训练信号,但要有退出条件
Voyager 的迭代 prompting 机制强调利用执行错误与反馈来改进程序,但工程上必须有退出机制:
- 最大重试次数(重试): 防止 token 风暴。
- 失败原因标签: 语法错误/断言失败/超时/权限拒绝。
- 回滚与隔离: 不允许失败技能污染技能库。
5) 检索与注入:不要“隐式随便注入”
技能库越大,误召回越致命。你需要:
- 语义检索 + 结构过滤: 先按 schema/side_effects 过滤,再按向量相似度排序。
- 召回数量上限: 只注入 top-k 接口,不注入实现细节。
- 调用前二次校验: schema 校验、权限检查、超时限制、审计字段补齐。
这一步本质上是“工具治理管线”的延伸:技能就是一种工具,必须进入同一套治理。
Design(设计取舍:为什么要把技能“产品化”)
你可能会想:让 agent 直接生成脚本并执行不是更快吗?
快,但不可控。把技能做成可发布工件,换来的能力是:
- 可审计: 每个技能都有来源与 trace id(审计、观测)。
- 可回滚: 技能版本化,坏了可以回滚(回滚)。
- 可复用: 技能复用降低 token 开销与不确定性。
- 可隔离: 技能声明 side_effects,runtime 才能按风险分级隔离(隔离、权限)。
这是一条“把不可控的生成,转成可控的软件供应链”的路线。
Pitfall(递归进化的真实危险)
- 错误堆叠: v1 有 bug,v2 依赖 v1,最终形成“逻辑反噬”。
- 供应链污染: skill 依赖可被投毒(例如
pip install拉到恶意包),必须在隔离环境执行并锁定依赖。 - 权限升级: 技能一旦能写文件/联网/执行命令,就会变成“越权通道”(权限)。
- 重试风暴: 失败后无脑重试会把成本与延迟放大(重试、超时、降级)。
- 无审计: 没有 provenance,事故发生时你无法追责与复盘(审计、观测)。
Debug(如何排查“自写技能系统”)
优先排查顺序建议是:
- 验证是否真的隔离: 文件系统是否只读?网络是否默认拒绝?工具是否白名单?
- 查看失败原因分布: 是断言失败居多,还是超时居多?这决定你修“逻辑”还是修“资源限制”。
- 回放失败样本: 用同一输入在沙箱回放,确保可复现。
- 检查检索误召回: 是否把不相关技能注入导致模型走偏?
- 检查幂等策略: 有副作用的技能是否可能被重放导致重复提交(幂等)?
Source(资料来源)
- Voyager: https://arxiv.org/abs/2305.16291
- AutoSkill: https://arxiv.org/abs/2603.01145
- LifelongAgentBench: https://arxiv.org/abs/2505.11942
- 协作视角补充(collab-voyager/MindForge): https://arxiv.org/abs/2411.12977
上线门禁(建议强制执行)
自写技能最容易“看起来能跑”,但实际上已经把风险引入系统。建议至少强制这些门禁:
- 必须有测试:
- 单测覆盖关键边界(空输入、异常输入、极大输入)
- 测试必须在隔离环境跑通(隔离)
- 必须可审计:
- 记录创建者、创建时间、来源 trace、hash(审计、观测)
- 必须可回滚:
- 技能版本化,回滚等同切版本(回滚)
- 必须有限制:
- 超时上限(超时)
- 最大重试(重试)
- side_effects 默认 none;需要写/网必须显式申请(权限)
- 必须有退役机制:
- 命中率低/失败率高/耗时异常时自动下线(降级)
这些门禁的目的不是“慢”,而是防止你把技能系统变成一条新的供应链漏洞路径。
一条最小“技能发布流程”(可落地)
- 生成候选技能(code + test + metadata)
- 沙箱执行测试(超时、资源限制)
- 影子回放验证(shadow / replay)
- 发布到 registry(只发布接口与元数据,不默认注入实现)
- 线上灰度(按任务类型/风险等级逐步放量)
- 监控与退役(失败原因分布、超时比例、重试次数)
当你把这条流程跑通后,“自写技能”才从概念变成系统能力。
常见反模式(看到就要停)
- 直接执行生成的代码:
- 模型刚生成的代码没有经过测试与隔离,直接执行等同把不可信输入直连系统调用(隔离、权限)。
- 允许技能自由联网:
- 一旦开放网络,技能就可能变成数据外传通道,且很难审计(权限、审计)。
- 允许技能自由写工作区:
- 这会把“技能生成”变成“仓库破坏器”,回滚代价巨大(回滚)。
- 无限制重试:
- 失败后无限重试,会把系统拖进 token 与 CPU 的双重风暴(重试、超时、资源释放)。
一个实用的“失败原因标签”(便于聚合)
建议从第一天就把失败原因标准化,否则后续无法做可观测性分析:
syntax_errortest_assertion_failedtimeoutpermission_denieddependency_resolution_failednon_deterministic_outputside_effect_detected
有了标签,你才能做两件事:
- 针对性修复(例如 timeout 多,就先优化资源限制与算法复杂度)。
- 针对性降级(例如 permission_denied 多,就先把技能声明与权限申请流程补齐)。