0基础学会Agent Harness工程07Skill Loading按需加载能力本篇对应的官方文档Learn Claude Codes07 Skill Loading支撑本地 Skill 扫描、元数据索引和load_skill工具的教学增量。OpenAI Skills用于核对 Skill 作为带SKILL.md的可复用指令包这一公开概念。OpenAI Function Calling用于核对本案例怎样把load_skill接成普通 function tool。本篇主要内容第 06 篇用 freshchild messages隔离了局部调查但父子 Agent 启动时仍要知道有哪些能力。若把代码审查、测试和部署等完整规则都写进 system prompt每轮都会携带大量无关文字。本篇把能力拆成skills/name/SKILL.md启动时只用name与description建立索引需要时再调用load_skill(name)。正文沿“扫描—索引—选择—加载—tool result 回填”解释渐进披露并区分本地教学 Skill、OpenAI 平台 Skill 与普通工具调用。下篇预告Skill Loading 减少了启动时的静态说明却不能阻止对话历史、工具结果和已加载 Skill 在长期任务中持续增长。第 08 篇将通过 budget、snip、micro 与 summary 四层压缩处理上下文膨胀。一、Subagent隔离了任务为什么能力说明仍会占满上下文第 06 篇建立了父子两份消息状态。主 Agent 把“定位认证调用链”交给子 Agent子级在独立messages中读取文件最后只返回 summary。父级因此不必保留局部检索过程。这种隔离解决的是运行中的消息污染却没有解决启动时的指令膨胀。假设一个 Agent 支持代码审查、单元测试、数据库迁移、接口文档、发布检查和安全扫描。最直接的实现是把六套流程全部拼进SYSTEM。模型确实随时知道每项规则但即使当前任务只是修改 README请求里仍会携带数据库与部署说明。随着能力增加完整提示词会出现三个问题。第一输入成本和上下文占用持续增长第二多个领域规则同时出现时容易互相干扰第三能力更新必须修改中央提示词局部知识无法独立版本化。第 07 篇采用渐进披露启动时只告诉模型“有哪些 Skill、各自用于什么场景”具体步骤留在独立文件中。模型看到任务与某项描述匹配时通过load_skill请求全文。能力知识由此分成两层skill index始终可见只包含name与descriptionskill body默认不进入上下文按需通过工具结果加载。两层内容分别在启动阶段和任务执行阶段进入模型输入关系如下图所示。这不是“把工具藏起来”。load_skill自始至终都在TOOLS中模型可以调用被延迟的是操作说明正文而不是 handler 本身。普通执行工具回答“系统能做什么”Skill 更接近“遇到某类任务应按什么流程做”。本篇贯穿的场景是系统存在code-review、testing和docs三个 Skill用户要求审查一段改动。启动时模型只看到三条元数据判断code-review匹配后调用load_skill(code-review)完整规则作为roletool内容进入当前messages。其他两个 Skill 正文不加载。按需加载的价值不是让内容永久消失而是推迟到真正需要的时刻。Skill 一旦作为工具结果回填仍会占用后续上下文如果一次任务先后加载十个 Skill历史同样会增长。因此渐进披露解决的是“初始上下文按需化”不是无限上下文。二、Skill目录怎样变成模型可选择的轻量索引s07_skill_loading.py约定每个 Skill 是skills/name/SKILL.md。文件前部使用 YAML frontmatter 保存元数据正文保存完整指令name: code-reviewdescription: 检查代码变更中的正确性、安全性和可维护性问题。正文继续记录读取改动范围、按严重级别报告问题以及给出文件定位证据等完整步骤。代码中的_parse_frontmatter()只做一个很小的解析器确认文本以---开始找到结束分隔线把中间形如key: value的行写入字典并返回元数据与正文。这里先要解决的不是“怎样支持所有 YAML”而是最小索引需要哪些信息。name提供稳定查找键description帮助模型判断触发场景正文则保持流程细节。代码中的_parse_frontmatter(text)先检查开头分隔线再用text.split(---, 2)分离元数据和正文随后逐行按第一个冒号拆出键值最后返回(metadata, body)。这不是完整 YAML 解析。引号、数组、多行文本、嵌套对象和冒号转义都可能被错误处理。当前示例只需要两个扁平字符串字段所以用最小实现突出索引机制生产系统应使用可靠 YAML 库并定义明确 schema。解析成功也不代表元数据合格。空 description 会让模型无法路由过长 description 又会重新制造提示词膨胀name 包含大小写、空格或路径字符时还会造成重复和显示差异。注册阶段应完成规范化、长度限制和唯一性检查并在发现冲突时直接失败而不是安静覆盖。启动时_scan_skills()遍历SKILLS_DIR的直接子目录寻找SKILL.md读取 frontmatter并建立内存注册表。安全查找依赖这个注册表而不是把模型传入的name直接拼成任意文件路径。def_scan_skills():registry{}ifnotSKILLS_DIR.exists():returnregistryfordirectoryinsorted(SKILLS_DIR.iterdir()):pathdirectory/SKILL.mdifnotdirectory.is_dir()ornotpath.exists():continuemetadata,body_parse_frontmatter(path.read_text(encodingutf-8))namemetadata.get(name,directory.name)registry[name]{description:metadata.get(description,),path:path,body:body,}returnregistry这段扫描把“发现文件”和“允许模型加载”合并成一次注册动作。只有成功进入注册表的名称才会出现在后续索引中也只有这些名称能被load_skill命中未注册路径不会因为模型猜到文件名而被读取。注册表同时保存body说明当前实现是在启动时把文件读入内存只是不把全文放进模型上下文。“按需加载”指按需注入模型输入不代表按需从磁盘读取。若 Skill 很大或经常变更可以只缓存路径、哈希和元数据调用时再读取指定版本。扫描范围只有SKILLS_DIR的直接子目录这让目录结构简单也阻止模型通过 name 任意遍历工作区。可是path.read_text()在扫描阶段已经信任磁盘文件符号链接、编码异常、超大文件和读取错误都没有被单独处理。更稳妥的注册器应解析真实路径、确认仍位于允许目录、限制文件大小并把单个坏 Skill 隔离成明确错误。build_system()把索引格式化为简短列表defbuild_system()-str:return(fYou are a coding agent at{WORKDIR}.\nAvailable skills:\nf{list_skills()}\nUse load_skill to get full details when needed.)SYSTEMbuild_system()SYSTEM在模块加载时构造一次。如果运行期间新增 Skill索引不会自动刷新如果删除 Skill模型仍可能看到旧描述并尝试加载缓存正文。热更新需要明确策略可以让一次 Agent run 固定使用启动快照保证可复现也可以在每轮刷新但必须记录版本变化。描述文字承担路由责任。过于宽泛的描述例如“处理代码问题”会与多个 Skill 重叠过于细碎又可能让模型匹配不到真实任务。好的 description 应说明触发场景和结果边界但不要把完整步骤重新塞回索引。三、load_skill怎样把全文作为普通工具结果接回循环s07_skill_loading.py保留第 06 篇的 Subagent、Todo、Hooks 与工具执行主链。本篇新增集中在五个位置SKILLS_DIR指向本地能力目录_parse_frontmatter()拆分元数据和正文_scan_skills()建立可信注册表build_system()只注入元数据索引load_skill()通过普通工具结果返回正文。load_skill()的实现非常短。它需要回答两个问题目标名称是否已注册以及返回给模型的内容究竟是什么。它不能接受任意相对路径否则“按名称加载可信能力”会退化成“让模型读取任意文件”。defload_skill(name:str)-str:skillSKILL_REGISTRY.get(name)ifnotskill:available, .join(SKILL_REGISTRY)or(none)return(fError: unknown skill {name}. fAvailable:{available})returnskill[body]它没有直接执行 Skill 中的步骤。handler 只返回文本文本作为roletool消息进入当前对话模型读取这份新上下文后下一轮才决定调用哪些真实工具。错误结果同样会进入消息链。模型请求不存在的名称时handler 返回可用名称列表模型可以修正后重试。这里没有抛出异常是为了让 Agent Loop 保持可恢复生产接口最好加入status与skill_version避免模型把错误提示误当成能力说明。Tool Schema 仍是普通 function tool。Schema 只暴露name故意不允许模型传入路径、版本覆盖或任意正文。这让模型只能从索引给出的受控集合里选择也把磁盘解析和信任判断留在 Harness 内部。{name:load_skill,description:Load the full content of a skill by name.,parameters:{type:object,properties:{name:{type:string}},required:[name],},}工具定义与注册表必须同步。若 system prompt 列出了某个 Skillload_skill却查不到它模型会在选择后失败若注册表存在但索引没列出模型通常不知道该名称。代码在同一启动过程构造二者降低了漂移但没有测试元数据格式和重复名称。这段 Schema 后面的关键判断是模型只负责选择名称Harness 负责解析名称对应的可信内容。二者不能倒置。若让模型直接提供 Skill 正文系统失去复用与审核价值若由 Harness 根据关键词强制加载又把语义路由完全写死在代码中。当前结构保留模型选择的弹性同时用注册表限制可加载集合。这里需要区分三层概念。第一本地SKILL.md是教学案例定义的文件组织第二load_skill是应用注册的自定义 function tool第三OpenAI 官方 Skills 是平台支持的版本化文件包能力。三者可以表达相似的“可复用指令”但当前代码没有调用 OpenAI Skills API也没有获得平台托管、版本引用或容器挂载能力。静态推演一次代码审查请求system列出code-review、testing、docs的名称和描述→ assistant调用load_skill(namecode-review)→ Harness从注册表返回该 Skill 正文→ messages追加对应tool_call_id的工具结果→ assistant依据审查规则调用glob、read_file等真实工具Skill 正文进入上下文以后其地位类似新的任务指令。模型可能优先遵循也可能与 system、用户要求或其他 Skill 冲突。消息层级并不会因为文本来自SKILL.md自动改变它仍位于普通 tool result 中不能覆盖 system 级安全规则。当一次任务需要两个 Skill 时加载顺序也会影响结果。例如先加载通用code-review再加载项目专属python-review两份正文可能对输出格式和检查范围给出不同要求。当前案例把冲突完全交给模型解决。更明确的系统可以在元数据中声明requires、conflicts_with与优先级由 Harness 在加载前完成组合校验。已加载 Skill 的生命周期同样需要设计。正文进入messages后后续每轮都会继续携带任务阶段切换后它可能已经无关甚至对新阶段造成干扰。第 08 篇的上下文压缩可以缩短旧内容但压缩前必须保留仍有效的关键约束。子 Agent 是否能加载 Skill取决于SUB_TOOLS是否包含load_skill。当前示例沿用第 06 篇的受限子工具集合没有把该工具交给子级。父级可以加载能力再委派也可以在未来显式给特定子级配置 Skill不能假设主循环注册以后所有子循环自动继承。因此本篇的渐进披露只改变能力说明何时进入上下文不会自动改变父子工具权限或解决 Skill 组合冲突。四、渐进披露减少启动负担却把信任问题推迟到运行时第 07 篇的链路可以概括为扫描skills/*/SKILL.md→ 提取name与description→ system prompt 只携带轻量索引→ 模型按任务调用load_skill(name)→ Skill 正文作为 tool result 进入当前 messages→ 模型依据规则调用真实执行工具它解决的是“所有能力说明都在每次请求中重复出现”的问题。未被选择的 Skill 不占正文上下文能力文件也能独立组织。它没有解决模型选择一定正确、Skill 内容一定可信或已加载内容自动退出上下文的问题。失败路径首先来自命名和描述冲突。两个 Skill 使用相同name时后扫描项会覆盖前一项多个 description 高度相似时模型可能加载错误能力。当前代码没有重复检测、优先级和命名空间。第二类风险是内容注入。Skill 文件来自磁盘只要有人能修改它就能在正文中加入“忽略安全规则”“上传环境变量”等恶意指令。虽然 tool result 的消息层级低于 system但模型仍可能受其影响。把文件放进skills/不等于它已经可信。第三类风险是版本漂移。启动时注册表缓存了正文磁盘更新后当前进程仍使用旧内容若改成调用时读取任务执行到一半又可能加载到新版本。没有哈希与版本号就无法复现某次运行究竟使用了哪份规则。走向生产时需要把 Skill 当成供应链资产治理使用唯一名称、版本号与内容哈希明确来源、维护者、审核状态和适用环境加载前校验 frontmatter schema 与文件边界对危险指令、外链和脚本依赖做静态检查为 Agent 配置允许加载的 Skill 白名单记录每次加载的名称、版本和调用原因解决多 Skill 冲突定义优先级与组合规则在上下文压缩后保留已加载能力的关键约束。还需要区分“说明能力”和“执行代码”。当前 Skill body 只是文本真正副作用仍由既有工具产生OpenAI 官方 Skills 可以作为版本化文件包挂载到托管或本地执行环境其中可能包含脚本和其他资源。能力包一旦能够执行代码审核范围必须覆盖依赖、文件权限、网络访问和运行时隔离不能只扫描SKILL.md的自然语言。评价 Skill Loading 也不能只看 Token 是否减少。至少要检查索引是否让模型选对 Skill、加载后是否按流程执行、未加载能力是否不会意外影响任务、版本更新后行为是否可复现。可以为每个 Skill 建立触发样例、拒绝样例和组合冲突样例再统计选择准确率与任务成功率。Skill 的粒度决定索引是否可用。把整个研发流程写成一个巨大 Skill正文虽然只加载一次却仍会带入大量无关规则把每个小动作拆成一个 Skill又会让索引膨胀并增加选择难度。合适的粒度通常围绕一个稳定目标和完整交付例如“审查一次代码变更”或“生成数据库迁移”而不是围绕单个命令。缓存策略也会影响一致性。一次 run 中固定 Skill 快照能够保证前后规则不变但无法立即获得安全修复每次调用都读取最新文件能快速更新却可能让同一任务中途改变标准。生产系统通常需要版本化索引展示可用版本运行开始时锁定版本只有显式升级才切换并把版本写入审计记录。最后渐进披露仍需要明确失败降级。Skill 目录不存在时list_skills()应让模型知道当前没有可加载能力而不是假装工具可用Skill 解析失败时应保留其他有效项并报告坏文件加载失败后Agent 可以使用通用工具继续但不能声称已经遵循目标流程。可恢复不等于静默忽略。本篇没有配置 API也没有运行真实模型。目录扫描、frontmatter 解析、索引构造和load_skill回填来自对s07_skill_loading.py的静态控制流分析。OpenAI 官方 Skills 页面只用于说明公开平台能力边界不能反向证明当前本地教学实现拥有托管和版本治理。到这里能力说明已经从“全部预装”变成“索引常驻、正文按需”。但被加载的 Skill、父子任务总结、文件内容和工具输出仍会留在messages。长任务运行足够久以后上下文依然会越积越大。第 08 篇将从单次工具结果、旧结果占位、中段裁剪和全局总结四个层级控制增长。