5.1. Skill 是可进化的执行单元而非静态函数
Skill 是可进化的执行单元而非静态函数
2026 年 3 月,我在一个客服 Agent 项目的第 4 次迭代中发现了问题。我们给 Agent 写了 23 个“处理函数”——退款、查单、改地址、发优惠券——每个函数逻辑清晰,单元测试全绿。但上线第二周,运营团队要求新增一个“部分退款”场景。我们修改了退款函数,加了一个 partial_ratio 参数,然后 11 个旧调用方坏了 3 个。那次回滚之后我意识到一个根本性错误:我们一直在用“写死代码”的方式构建一个应该“活”的系统。
Agent 需要的不是一个函数库。它需要的是 Skill:一个能描述自己、感知上下文、接受反馈并自我调整的执行单元。函数是“被调用”的,Skill 是“被激活”的——这两者之间的差异,是所有自进化 Agent 架构的第一道分水岭。
Skill 不是函数:一张对比表格
在 Hermes 的架构中,Skill 的定义是一个 YAML/JSON 文档,包含触发器、参数 schema、前置条件、执行指令和依赖声明。而传统函数是一个代码块,包含输入参数、逻辑体和返回值。以下是两者的系统性对比:
| 维度 | 传统函数 | Skill(可进化执行单元) | 作者的结论 |
|---|---|---|---|
| 定义方式 | 代码中的 def / function |
YAML/JSON 元数据文档 + 可选脚本 | Skill 是“声明”出来的,不是“编写”出来的 |
| 调用方式 | 代码显式调用 foo(args) |
触发器匹配:关键词、意图、时间或事件 | 调用方不需要知道 Skill 的存在,Agent 自动路由 |
| 状态管理 | 无状态(或依赖全局变量) | 执行上下文沙箱,含工作目录、环境变量、临时资源 | Skill 每次激活都是干净的执行环境 |
| 参数演化 | 修改函数签名 → 所有调用方编译/运行时报错 | 参数 schema 可扩展,旧调用方不受影响 | 进化不破坏兼容性 |
| 适用范围 | 开发者可见 | Agent 自身可见、可调用、可组合 | Skill 是 Agent 的“能力原子”,不是程序员的工具 |
| 生命周期 | 写完运行,删掉消失 | 草稿 → 激活 → 执行 → 评估 → 进化 → 归档 | Skill 像生物个体一样有生老病死 |
这张表格的最大启示在于最后一行:函数没有“退役”这个概念。你删掉它,或者留着它被遗忘。而 Skill 有明确的终态——当 Curator 后台组件判定一个 Skill 连续 30 天未被激活,或者其成功率低于阈值,它会自动进入“待归档”状态,等待人工确认后移入档案库。这是 2026 年 Hermes v0.13 中新增的 archive、prune 和 list-archived 操作所支撑的能力。
核心洞察:被动 → 主动 → 独立
要理解 Skill 的“可进化”本质,我们可以用一个三级跳跃来梳理:
第一级:被动执行(传统函数)
你定义了 calculate_refund(item_price, quantity),它等在那里。只有当你(程序员)在代码中写入调用语句,它才会执行。函数的主体性为零——它不知道什么时候该出现,不知道调用者的意图,不知道执行结果是否需要反馈。
第二级:主动适应(上下文感知的函数)
你给函数加上了一层薄薄的“意图壳”——比如一个装饰器声明了“当用户表达退款意愿时触发”。Agent 负责将用户输入与这个声明匹配,然后激活函数。这已经是进步了,但函数本体还是死的——参数错了,它报错;场景变了,它沉默。
第三级:独立生存(可进化 Skill)
Skill 不仅声明了“何时触发”,还携带了“执行后如何评估”“失败时如何降级”“从何处读取记忆作为上下文”。当一个“退款”Skill 被激活时,它能从 Memory 系统中拉取用户的历史订单、近期的情绪标记、上一次交互未解决的问题——这些都不是函数参数,而是运行时动态注入的上下文。执行结束后,Skill 的评估钩子将结果写回 Memory,供下一次激活时的“自己”使用。
经验框:Skill 的渐进式披露
Hermes 的 Skill 系统采用了“渐进式披露架构”。一个 Skill 不是一次性暴露所有能力,而是分层展示:触发器匹配后,先向 Agent 展示摘要(名称 + 一句话描述);Agent 确认调用意图后,再展示参数 schema 和执行前条件;执行遇到特定分支时,再展开子流程指令。
这种设计的核心价值在于:Skill 可以很复杂,但在 90% 的激活场景中,Agent 只需看到它的 10%。这让 Skill 像一个有经验的老员工——你不需要告诉它每一步怎么走,它知道什么时候该请示,什么时候该自己决定。
Skill 的元数据与描述模式
一个 Skill 的最小化定义如下(以 Hermes 的 YAML 格式为例):
id: order-refund
name: "订单退款处理"
version: "2.1.0"
triggers:
- keywords: ["退款", "退钱", "不要了", "取消订单"]
- intent: "refund_request"
- event: "order.cancelled.with_payment"
parameters:
order_id:
type: string
required: true
description: "需要退款的订单号"
reason:
type: string
required: false
default: "用户主动申请"
description: "退款原因,用于记录和统计分析"
dependencies:
- skill: payment-gateway
version: "^1.3.0"
- memory: user-order-history
execution:
entry: scripts/refund_executor.py
timeout: 30s
retry: 3
evaluation:
metric: "refund_success_rate"
threshold: 0.95
on_failure: "degrade_to_manual"
这里有三个设计细节值得展开:
-
触发器是开放的联合条件:
keywords是文本匹配,intent是 NLU 模型输出的意图标签,event是系统事件。三者是“或”关系——任意一个命中即激活。这保证了 Skill 的“可达性”不依赖单一信号源。 -
参数 schema 支持向前兼容:
reason字段不是必填,且有默认值。当未来版本的 Skill 增加refund_method参数时,可以不破坏 2.1.0 版本的调用方——Agent 看到缺失参数时会使用默认值或向用户追问,而不是报错中断。 -
依赖声明是版本感知的:
payment-gateway依赖声明了^1.3.0,意味着兼容 1.3.x 但不接受 2.0.0。这让 Skill 在依赖升级时能主动发出兼容性警告,而不是静默失败。
核心建议框:给参数留“呼吸空间”
在设计 Skill 的参数 schema 时,只把必须由调用方提供的字段设为
required: true。其他字段都应该是可选的、有默认值的。这不是为了让 Schema 看起来“宽容”,而是为了给未来版本留出添加新参数的空间——每一次参数扩展都不应该成为一次断裂性变更。
执行上下文与环境隔离
当一个 Skill 被激活时,系统不是直接在当前进程中调用一段代码,而是创建一个独立的执行上下文。这个上下文包含:
- 工作目录:一个临时目录,Skill 可以在其中读写文件,执行结束后可选择保留或清理
- 环境变量:系统级变量(如
AGENT_SESSION_ID)+ Skill 自定义变量(如REFUND_MAX_AMOUNT) - 权限边界:Skill 只能访问其声明的 API 和文件路径,超出范围的请求会被沙箱拦截
- 时间配额:由
timeout字段控制,超时后强制终止并记录
这四层隔离带来一个关键能力:同一个 Skill 可以安全地同时被多个对话线程激活。两个用户同时要求退款,会产生两个独立的执行上下文,它们不共享文件、不竞争锁、不污染彼此的状态。这是传统函数调用在并发场景下很难保证的事情——除非你小心翼翼地管理全局状态,而“小心翼翼”本身就是一个架构坏味。
进化事件与版本管理
Skill 的进化不是靠程序员手动修改配置文件来驱动的,而是靠 评估反馈闭环。每一次 Skill 执行完成后,系统会收集以下信号:
- 执行是否成功(成功率)
- 执行耗时(性能基线)
- 用户后续行为(满意/投诉/追问)
- 是否触发了降级策略
当某个指标连续低于阈值——比如退款 Skill 的成功率从 98% 掉到 91%——Curator 后台组件会生成一个 进化事件。这个事件不等同于“自动修改代码”,而是一个带上下文的提案:包含近期的 10 个失败案例、相关记忆片段、以及可能的改进方向(比如“增加支付网关超时重试次数”“补充对部分退款场景的覆盖”)。
开发者的工作是审阅这个提案,决策是修改定义、增加参数、还是标记为“已知限制”。如果修改了 Skill 定义,版本号会按 SemVer 规则升级:
- 修订号(1.2.x → 1.2.y):仅修改指令措辞或修復 bug,不影响行为契约
- 次版本号(1.x → 1.y):新增可选参数或新增触发器,向后兼容
- 主版本号(x → y):删除必填参数、修改返回值结构、或调整依赖合约
注意框:不要创建“无法退役”的 Skill
v0.13 引入的
archive和prune操作意味着 Skill 的生命终态已经被架构所确认。但实际工程中,很多团队的 Skill 一旦上线就再也没有清理过——因为“谁也不敢删”。建议从第一天起就给每个 Skill 设定一个观察期(通常是 30 天),观察期内成功率低于 50% 或者零激活记录的 Skill 自动进入待归档状态。一个从不回家的守卫不是警惕,而是迷路了。
适合谁 / 不适合谁
这一章最适合以下场景:
- 你正在设计一个需要快速迭代“能力单元”的 Agent 系统,每次修改都不应该成为断裂性变更
- 你的团队中有产品和运营人员参与“技能定义”,而不只是工程师在写代码
- 你希望 Agent 在运行一段时间后能“自动找出”哪些技能需要优化,而不是靠人工检查日志
这一章可能不适合以下场景:
- 你只是在做一个简单的 ChatGPT 包装器,所有能力都靠 System Prompt 控制
- 你的系统调用链路完全固定,没有“根据上下文动态选择能力”的需求
- 你暂时不需要考虑多技能协作、版本并存、或者长期运行后的技能退化
上一章我们讨论了从记忆到技能的跃迁是如何成为自进化 Agent 的关键路径——记忆提供了原材料,技能是原材料加工后的产物。本章我们深入了这个“产物”的内核:它有自己的元数据、执行上下文、生命周期和进化机制。它不是一段被调用的代码,而是一个在 Agent 生态中独立生存的能力单元。
但理解单个 Skill 的架构只是第一步。一个真正强大的 Agent 从不依赖孤立的技能——下一章,我们将走进 OpenClaw Skills 目录,这是一个拥有 1,800+ 个即装即用技能的社区生态。你会发现,你不需要从零设计每一个 Skill,而是可以像浏览 App Store 一样为 Agent 挑选、安装和组合能力。
Hermes Agent 系统设计与工程落地
关于 LearnKu