Skill 不只是一个 SKILL.md:Agent 如何把研究方法变成可调用的能力
一个 Agent 已经能搜索网页、读取 PDF、调用 MCP、运行 Python,为什么还需要 Skill?
因为拥有工具,不等于掌握完成一类任务的方法。
假设用户问:“比较这两只指数基金的跟踪质量,再看看它们在我的组合里有没有重复暴露。”
搜索工具可以取得基金净值、基准与披露持仓;Python 可以计算主动收益波动和重叠比例。但工具不会替研究者决定:是否先核对份额、币种、分红口径和共同窗口?缺少私人组合授权时,是否应该停止计算个人重叠?两条序列无法对齐时,是否应承认指标不足?
在 ETF Agent 中,我们把这些可以复用的研究方法组织为 Skill,同时坚持一项分工:Skill 定义方法,模型选择并综合方法,Host 管实际能力和来源授权,确定性程序管计算,原 Runtime 管预算、执行和恢复。
这篇文章不仅讨论 Skill 文件结构,还沿一次研究解释:同一个 Skill 为什么在 Entry、Main 和 Child(Subagent)眼里不同;如何从目录变成实际读取、计算与交接;哪些行为可由回执确认,哪些不能靠目录或声明推断。
一、一个 Skill 到底应该包含什么?
Agent Skills 规范以 SKILL.md 说明方法,并允许附带 scripts/、references/ 和 assets/ 等资源。其重要价值是渐进加载:先给模型简短名称和描述,需要时再读取正文与支持文件,而不是每轮把所有方法塞进上下文。
不过,SKILL.md 只是入口。需要计算、统一口径和多种可单独调用的方法时,ETF Agent 的内部基金研究包采用这样的结构(为阅读省略部分子目录):
fund-research/
├── bundle.json
├── SKILL.md
├── skills/
│ ├── fund-research-planner/
│ ├── index-fund-profile/
│ ├── index-fund-tracking-quality/
│ └── portfolio-overlap/
├── scripts/
├── references/
├── schemas/
└── templates/截至本文核对的内部版本,Bundle 为 1.3.0,包含一个规划入口和十四种独立研究方法。它们的职责不同:
| 内容 | 管什么 | 不管什么 |
|---|---|---|
SKILL.md | 研究步骤、前提、证据与适用限制 | 数据授权、模型执行循环 |
bundle.json | 包身份、版本、方法和共享资源位置 | 额外调度与执行账本 |
| 子方法 | 跟踪质量、组合重叠等明确问题 | 必须创建一个独立 Agent |
references/、schemas/ | 参考知识、输入输出约定 | 自动证明来源可信 |
scripts/ | 对已提供的结构化数据做确定性处理 | 擅自获取私有数据、替代 Host |
templates/ | 结果的可复用呈现形式 | 宣称研究已经成功交付 |
用户只需要跟踪质量时可以直接选择这一方法;完整基金研究可以组合几个方法。子 Skill 不等于子 Agent,Bundle 也不是另一套任务调度器。
二、方法如何进入一次真正的研究?
有两条入口路径,但它们不是两份授权体系。
显式选择时,用户可提交已注册的 Skill ID,例如:
/index-fund-tracking-quality原入口解析真实 API 字段或 slash 命令,校验登记身份、版本和内容,然后把选定方法绑定到这次 Research。既然用户已明确选择,就不必再让 Entry 模型猜一次“他是不是想调用 Skill”。显式指引可经受信任的启动上下文进入模型请求,但仍受上层安全约束及原 Run 授权限制。
自然语言选择时,用户只是问“它们跟踪得怎样”。普通 Entry 主要判断 Direct 还是 Research;它不会提前塞进完整 Skill 目录,也不需要凭词表为问题强行绑方法。进入 Research 后,Main 可看到简短目录,根据任务读取方法正文或继续普通研究。按需读取的正文作为普通受控工具结果进入后续上下文,不能因此提升为系统权限。
按起点分组阅读关系与条件;完整图保留在下方。
- → 显式指定 Skill? (判断)
- 是 → 校验注册身份并绑定方法
- 否 → Entry 判断 Direct / Research
- → 原 Run 与 Host Tool Surface
- Research → Main 获得简短目录
- Direct → 正常直接回答
- → 按需读取方法,或直接研究
- → 原 Run 与 Host Tool Surface
- → 来源和工具已获准? (判断)
- 是 → 读取证据、必要时计算
- 否 → 保留未授权或缺失的部分
- → 综合有范围的结论
- → 综合有范围的结论
- → 原发布与交付机制
完整关系图 · 可放大查看细节
这里必须区分四个动作:发现方法、注入或读取指引、获得工具授权、实际消费方法。前两项让模型知道“可能怎样做”,第三项决定“允许做什么”,第四项才涉及真实工具调用、结果读取和研究综合。没有任何一步能自动证明下一步已经发生。
三、同一个 Skill,为什么在 Entry、Main 和 Subagent 中看到的内容不同?
沿开头的问题再走一遍。Main 需要公开数据比较两只基金,同时考虑把跟踪质量检查委派给一个 Child。此时“Skill 已经注册”不能被等同于“Main、Child 都能读、都能算”。
1. Entry、Main、Child 不是共享一份完整提示词
| 位置 | 实际可看到或取得什么 | 不能据此推出什么 |
|---|---|---|
| Entry | 普通入口判断 Direct/Research;显式指定则交给已校验的启动方法 | 普通入口并未预加载整个 Skill 正文 |
| Main | 经过 Host 投影的短目录:ID、名称、描述;可按需读取 | 目录出现不等于授权、采用或执行 |
| Child | 自己绑定的专家入口、任务输入,以及按本 Child 有效工具面筛选的 Skill 目录 | 不自动继承 Main 见过的全部 Skill |
| 自定义专家 | Profile 可带 preferredSkills 和 preloadedSkills | 偏好与预加载均不扩大工具、来源或预算 |
| 恢复后的请求 | 原已封存的指引、内容身份与实际回执;仍受上下文拟合 | 不能把当前最新版偷偷替换为旧请求的内容 |
具体来说,Child 目录由 Child 当前有效的 Tool Surface 决定。现行实现按下面的判断投影方法摘要:
- 有
read_skill_resource:可以看到其已绑定目录中可供读取的注册方法摘要。 - 没有读取工具、但有
run_skill_script:只展示有本地包根的可执行包方法摘要;不把纯远端方法伪装成可读。 - 两个工具都没有:不投影 Skill 目录。Child 仍可以在获准范围内完成不依赖这些方法的任务。
这套规则过滤的是可发现性,不是新的执行许可。即使列表里有某方法,真正读取时仍要检查有效资源身份、版本、作用域与原权限;真正执行脚本仍要经过原脚本工具的规则。另一方面,普通 Child 自己的专家包入口也有独立的冻结身份:agentId 是专家标识,不是 Skill 文件路径、更不是额外的 Tool grant。
2. 上下文投影决定“这一轮模型实际看到了什么”
Main 和符合条件的 Child 会把简短目录投影为带有 host-skill-catalog 标识的模型消息,目录项包含 skillId、名称和描述,必要时附上 omittedCount。这是一份本次请求可用的目录视图,不是把所有 Skill 指引变为系统 Prompt。
为了说明结构,可以将其简化为教学 JSON:
{
"kind": "host-skill-catalog",
"skills": [
{ "skillId": "index-fund-tracking-quality", "name": "Tracking Quality", "description": "Check comparable tracking data" }
],
"omittedCount": 2
}此处的两个省略项只是示意数字,不是系统真实目录的统计。实际投影逐项计算输入容量,装不下的摘要应保留确实发生了省略这一事实;如果连必要的目录包络都无法装入,则按原请求容量错误处理,不能假装模型看完了全目录。
随后模型可通过 read_skill_resource 读取合法方法正文。读取结果是可回查的工具结果,仍不代表已执行其中的计算,也不能从“下一轮对话提及 Skill 名称”反推曾成功读取。
3. 自定义专家的偏好、预加载和授权是三回事
一个自定义基金专家可以配置:
{
"preferredSkills": ["index-fund-tracking-quality"],
"preloadedSkills": ["index-fund-tracking-quality"]
}preferredSkills 只是对模型的软偏好,不会自动把正文塞入 Child,也不会隐藏未偏好的合法能力。而 preloadedSkills 明确请求在专家启动时读取并投影指引,但必须使用原注册资源、真实 Child grant、内容校验与上下文容量。失败就局部报告 unavailable,不能把未知路径拿来直接读,也不能假装加载成功。
若完整指引超出容量,当前投影以完整项为单位保留省略,避免截断一半再让模型误以为拿到了可用方法。拟合发生在 Child 的实际请求装配阶段;Main 在提交 spawn_subagent 时,不应该提前声称“Child 已成功预加载”。
自定义 Profile 可以改善专家的研究倾向,但不能靠“偏好 Skill”给专家追加私有持仓、外部数据服务或执行预算。
4. Child 用方法做了什么,最终仍须交回真实结果
同一问题可以按这样的路径委派,以下是机制示意,不是一次真实基金交易或模型运行记录:
按起点分组阅读关系与条件;完整图保留在下方。
- → Entry:Research
- → Main:方法目录与公开证据
- → 委派 Child:限定任务和原授权
- → Child 有读取工具? (判断)
- 是 → 按需读取对应 Skill 指引
- 否 → 不宣称已读;按现有能力研究
- → 所需数据与脚本可用? (判断)
- → 交回总结、引用和未解缺口
- 是 → 实际工具调用并检查结果
- 否 → 说明缺口,保留可用发现
- → 交回总结、引用和未解缺口
- → 交回总结、引用和未解缺口
- → Main 判断是否采纳与综合
- → 原发布机制交付
完整关系图 · 可放大查看细节
Child 提交 submit_subagent_result 时可以带摘要、证据引用、产物引用和缺口,但**“Child 交回了结果”不等于“Main 已在正文里真正使用它”**。Main 是否采纳哪一项发现,要对照其后续实际模型输入、引用和最终回答;任务关系提示、专家名称、声明使用了某方法,都不能替代这个事实。
这也是 Skill 与 Subagent 的合理组合:Skill 提供可复用的研究步骤,Child 提供独立上下文和模型工作段,Main 负责把已取得的研究贡献整合到用户问题。一个 Child 可用多个获准 Skill,一个 Skill 也可以由 Main 直接使用;不必制造“一个 Skill 对应一个 Agent”的固定拓扑。Subagent 自身的生命周期与多专家协作另有专题,这里只讲方法交接。
四、Skill 可以提出数据需求,但不能授予数据权限
基金跟踪质量通常需要基金净值和可比的基准总收益序列;个人组合重叠还需要实际配置权重及对应时点的底层持仓。
方法可以声明 research-scopes: [fund] 或 research-scopes: [portfolio_details],但它们表达研究领域依赖,不是用户授权。部分受审包还可声明 allowed-mcp-servers,表示方法可依赖的专用 MCP 服务上限;实际能否调用,仍由当前 Run 的授权、Host 政策、服务配置、只读限制、阶段与资源预算决定。
如果用户只允许公开基金资料,没有授权私人组合明细,Agent 可以研究公开费率、基准和已取得的收益序列;但无法计算这些基金在用户真实持仓中的重叠比例。它不能从历史闲聊、旧 RAG 摘要或想象的配置权重里补齐,也不能把未知重叠写成零。
用户要求“只用上传文件”时,同样不能因 Skill 声明了外部 MCP 就擅自联网。方法声明不能扩大 grant,grant 也不要求模型强制使用某方法。
五、从说明书到计算:Skill 如何真正产生结果?
金融研究中,部分判断必须由模型理解,部分计算则应交给可复算的程序。以指数基金为例:
- 跟踪差(Tracking Difference):某可比期间基金收益与基准收益之差。
- 跟踪误差(Tracking Error):逐期主动收益的波动,通常需要根据实际频率年化。
- 相对财富变化:基金与基准累计财富路径的相对变化。
三者不能相互替代,更不能把“跟踪误差”当成某一天基金收益率减去指数收益率。
基金包提供的受审脚本可先回答“方法需要哪些输入”:
{
"path": "scripts/run_module.py",
"args": ["--describe", "--module", "index-fund-tracking-quality"]
}这只读取输入约定,不自动下载行情、读取个人组合或执行跟踪质量计算。真正取得合规结构化数据后,脚本才对输入做确定性处理。较大数据可通过 stdinFromToolCallId 指向本 Run 已完成且对模型实际可读的工具结果:
{
"path": "scripts/run_module.py",
"args": ["--module", "index-fund-tracking-quality"],
"stdinFromToolCallId": "call_example"
}这里 call_example 仅是字段形态占位符,不是有效调用 ID。Host 按真实回执解析当前 Run 的已完成调用,把实际投影给模型的结果序列化后送入脚本 stdin,拒绝跨 Run、未完成、歧义或已被省略的引用。Host 私有原始捕获不会因此暴露。
包内脚本路径必须相对已批准的包并受路径约束;调用继续遵守原有期限、取消、资源和执行回执。受信任包的脚本运行器,不等于可运行任意不可信 Python 的安全沙箱。受管理 Child 专家包如果使用隔离 Analysis Sandbox,也不能因此推导所有 Skill 脚本都拥有相同隔离保障。
六、为什么计算必须带上方法、参数和来源身份?
假设输出只有 {"trackingError": 0.012}。下一轮无法判断是哪只基金、哪个份额、什么日期窗口、对应哪个基准版本,以及用日频还是月频和什么年化参数。
因此方法结果应保留对象、份额、时期(requested、used、as-of)、输入引用、来源、参数身份和实际结果状态。基金研究包用这组上下文关联方法计算;外部 Host 仍独立确认真实来源和准入,包内声明不能伪造合法引用。
例如基金份额或基准版本变更,不能复用旧计算;参数从每年 252 期改成 12 期,也不能只改结果标签。方法内容更新时,只有名称相同更不足以说明是同一种执行:必须绑定注册版本、内容哈希以及已经实际采用的指引。
恢复时也一样。旧 Run 已封存的方法不能静默替换成当前最新版。另一方面,方法内容身份与 Run 的授权身份是两条独立约束:重新加载方法不会自动重授权限;Skill 被停用也不意味着已合法发布的历史正文必须随之抹除。
七、一个方法失败,整份研究是否必须失败?
继续最初的基金比较,可以遇到三种不同结果。
基金身份、费用等公开资料已经核对,但缺少可比的基准总收益序列。这时可以交付产品事实,却不能用价格指数代替总收益序列补出正式跟踪误差。
用户未授权组合明细,个人重叠率只能标记不可用,不能报零。脚本输入格式错误也必须保留真实失败,不能把空输出当成零收益或成功结果。
以下是教学化结果结构,并非实际生产接口:
{
"research": "两只指定指数基金的比较",
"findings": {
"fundProfile": { "status": "available" },
"trackingQuality": {
"status": "insufficient",
"missing": ["可比的基准总收益序列"]
},
"portfolioOverlap": {
"status": "unavailable",
"reason": "未获私人组合明细授权"
}
}
}一项方法失败,不应抹掉邻近有效发现。Child 可交回“已确认事实 + 未解决缺口”,Main 再决定哪些值得放进报告。方法读取完成、脚本执行成功、研究结论有证据、Main 实际采纳和文件交付成功是不同环节;它们复用原有回执,不另外制造“所有子 Skill 必须成功”的发布门。
八、Skill 版本治理,管的不是 Markdown 文件名
个人脚本中修改一份 Markdown 可能很简单,但在存在包安装、可恢复任务与私有来源的系统里,修改方法前提、脚本、允许的依赖或输出解释,都会影响后续执行。
ETF Agent 区分 draft、active、disabled、superseded 等生命周期状态,注册、受审激活与实际执行分开处理。即使名称和版本号未变,内容哈希改变也不能沿用旧内容的批准结论。
| 行为 | 它确认的事实 | 它不证明什么 |
|---|---|---|
| 注册 | 一份方法以哪个身份、版本存在 | 已激活或已授权 |
| 激活 | 某候选经过现有审查可供合法选用 | 用户数据可读 |
| 投影或读取 | 某次请求实际看到目录或正文 | 模型已执行方法 |
| 执行与交接 | 实际工具结果与 Child 回执可定位 | Main 一定采纳、用户一定收到 |
禁用主要阻止不合格的新使用;历史回读与尚未执行动作的恢复,则由原版本、权限、回执和发布规则分别约束。治理的目标是使方法变化可识别,而不是新增第二套 Runtime。
九、公开实现能复现到哪一步?
前述基金 Bundle、私人组合准入与受审 Python 链属于内部系统,不等于公开的 Open ETF Agent 已具备相同功能。
不过,在公开 v0.2 的一个固定源码版本里,compare-guides Skill 指导 Agent 比较两份合成软件指南:读取双方真实版本,分清免费/企业条件,核对数值与单位,再保存可回读的来源报告。可以阅读其 固定 SKILL.md 和 目录实现。
按该固定版本的实际引擎约束,使用 Node.js 26.11.1(或兼容的 26.x):
git clone https://github.com/hpoenixf/open-etf-agent.git
cd open-etf-agent
git checkout a79e89bf709d4a2245b5a8652882a591ba467519
npm ci --ignore-scripts --no-audit --no-fund
npm run demo -- --case skill-mcp这是离线合成示例,不需要真实基金数据或模型密钥。它可以帮助观察“方法发现 → 正文读取 → Host 来源获取 → 报告 → 运行记录”的交接,不证明前面介绍的私有 Bundle、Child 预加载或基金计算已在公开版发布。
十、什么时候用 Skill,什么时候写普通代码?
固定输入、确定公式、稳定结果的任务,直接编写普通函数往往更简单。若任务形式多变、需要领域判断,但存在可复用的检查步骤和证据前提,Skill 才真正有价值。
对基金研究,这包括先核产品份额、基准和期间,区分跟踪差/误差,处理未知穿透暴露,或者在证据不足时给出正确范围的结论。对专家协作,这意味着Child 不是方法的所有者,方法也不是执行权限:专家可以拥有独立上下文,同时按自己的可用工具面发现和组合方法,交回结果由 Main 实际消费。
可以把判断收束为四句话:
- 需要模型理解、选择和综合的步骤,放在 Skill。
- 确定性计算放在受控代码;公开数据、私人来源和外部动作仍经原工具。
- 工具授权、版本绑定、预算、恢复与发布留给原 Host/Runtime。
- 目录、指引、工具调用、Child 回执和最终采纳分别可观察,不互相冒充。
Skill 合同可检查,不代表模型总能选对方法;脚本可复算,不代表输入金融资料必然可靠。这两项仍要单独评估。
结语
Agent 已经有工具,为什么仍需要 Skill?
工具回答“能调用什么”,Skill 帮模型回答“面对这类问题,应当怎样组织研究”。只有进一步分清谁看到了方法、谁获准执行、谁实际读了正文、谁产出结果、谁在最终研究中采纳,它才不只是高级 Prompt 包装。
ETF Agent 想建立的不是拥有隐式权限的 Skill 系统,而是一套可以持续增加研究方法、却不重复建设执行权威的能力体系。新方法只定义前提、步骤、输入和结果;授权、计算、回执和交付仍由原来真正负责它们的机制承担。
延伸阅读
- Agent Skills Specification:文件结构与渐进加载。
- Model / Runtime / MCP / Skill 的分工:四种责任的最小心智模型。
- Subagent 全景:Child 的独立上下文、生命周期和结果交接。
- 自定义专家,不应该只是一个 Prompt 输入框:Profile、冻结与恢复。
- Extension Host:从功能机到智能机:扩展包的注册与生命周期。
- Open ETF Agent:公开最小实现与源码。欢迎在 GitHub 查看和 Star。