前八节已经分别解释了 Skills 的内容、入口、交付和执行边界。现在回到 独立总图,把 B、C、D、F 接入已有 Agent 系统。

比较范围: 下面沿用 S06 的教学骨架,加入基于公开行为设计的 Skills 接口。颜色比较的是本次教学设计中的机制变化,不是 Claude Code 内部源码的版本差异;本地 S07 脚本是另一个更小的机制示例。

本节的可运行的教学轨迹脚本、文件位置与实验步骤见 S07.10 完整示例附录。

1. 先回顾 S06 的整体结构

上一轮整体流程:本章改造前的已有系统蓝色已有;橙色本轮将改造;灰色虚线细节在图 2 合并。上一轮整体流程:本章改造前蓝色已有;橙色本轮将改造;灰色虚线细节在图 2 合并。对话历史问题与工具结果请求模型SYSTEM + TOOLS判断响应是否 tool_use分发并执行handlers[name]回传结果按调用 ID 配对TOOLS基础定义 + task用户输入沿用 S04 入口本轮结束原有结束动作权限回调None / 拒绝说明UserPromptSubmit保留输入事件PreToolUse保留执行前事件日志回调保留观察行为注册回调拒绝:跳过执行与后置事件PostToolUse保留执行后事件Stop保留退出判断事件反馈追加到历史父工具结果回传 → 父模型继续SYSTEM已有委派提示工具实现read / write / edit / globTODO → S05历史索引,本例未含task(prompt)父循环同步等待子 Agent Loop独立运行 · 最多30轮返回文本最终文本 / 上限说明仅 task 进入这一分支task 返回再后置子配置与工具SUB_SYSTEM · 五个基础工具 · 无 task共享 WORKDIR原有 · 本轮展开新 messages初始仅子任务 promptTODO 的内部实现见 S05;本图保留 task 的委派、独立上下文、子循环与返回路径。
颜色与线型
  • 蓝色:已有且保留
  • 橙色:本轮将改造(仍是旧实现)
  • 紫色:真正新增(本图未用)
  • 红色虚线:本轮将移出(仍是旧流程)(本图未用)
  • 灰色实线框:存储/辅助节点(本图未用)
  • 灰色虚线框:细节合并,功能保留
  • 灰字:来源/补充说明
  • 灰色虚线:关联,不是执行回环
  • 实线箭头:流程/数据方向
  • 下划线章节:点击查看具体实现
图 1:沿用 S06 的节点和连线。橙色标出本轮扩展的请求上下文与调用入口;灰色虚线提示已讲过的子 Agent 细节将在下一张用 S06 索引简记,功能概念没有被删除。左右滑动查看全图。

这里保留的是已学过的循环关系:模型提出行动、程序处理调用、结果回到历史。S06 的子循环内部已经讲过,本节不再把它完整铺开。

2. 把 Skills 的三个接口接回去

图 2 · Skills 接回 Agent Loop(教学示意)目录进入模型请求;调用后正文进入上下文;执行仍沿用原工具循环。图 2 · Skills 接回 Agent Loop(教学示意)目录进入模型请求;调用后正文进入上下文;执行仍沿用原工具循环。对话历史含正文与工具结果请求模型上下文 + 技能目录判断响应是否 tool_use分发并执行handlers[name]回传结果按调用 ID 配对TOOLS原工具 + Skill 入口用户输入沿用 S04 入口本轮结束原有结束动作权限回调None / 拒绝说明UserPromptSubmit保留输入事件PreToolUse保留执行前事件日志回调保留观察行为注册回调拒绝:跳过执行与后置事件PostToolUse保留执行后事件Stop保留退出判断事件反馈追加到历史父工具结果回传 → 父模型继续SYSTEM已有 Agent 指令工具实现read / write / edit / globtask→S06TODO→S05已讲实现索引可见技能目录名称与描述 · B目录按发现规则更新Skill 调用用户 / 模型入口 · C准备正文参数与可选预处理 · D交付正文默认当前上下文 · D用户点名 / 模型调用在此汇合正文进入历史 → 后续模型请求可见紫色线说明内容流;工具结果仍按原协议回传。fork 分支及其调度单独见子 Agent 参考。
颜色与线型
  • 蓝色:已有且保留
  • 橙色:修改/职责迁移
  • 紫色:真正新增
  • 红色虚线:真实移除(本图未用)
  • 灰色实线框:存储/辅助节点(本图未用)
  • 灰色虚线框:细节合并,功能保留(本图未用)
  • 灰字:来源/补充说明
  • 灰色虚线:关联,不是执行回环
  • 实线箭头:流程/数据方向
  • 下划线章节:点击查看具体实现
图 2:紫色分支对应独立总图的 B、C、D。目录扩展请求上下文,调用准备正文,交付让后续模型请求看到新指令;灰色虚线表示入口关联。旧工具维持小框简记,正文要求的实际操作仍由原工具循环执行。左右滑动查看全图。
接入点来自独立机制图对原循环的影响
请求模型时的可见信息B 技能目录模型知道有哪些可用方法,不必先读全部正文
用户输入或模型调用的处理C 调用入口两种入口确定同一个目标 Skill,经过各自适用的调用检查
后续模型请求所用的上下文D 正文交付加入工作指令,模型可以据此继续行动
后续普通工具调用F 按需资源需要时读取参考文件或运行脚本,结果继续进入当前执行上下文

这里的紫色箭头表达内容流,不能把它读成绕过工具权限、Hook 或 API 消息配对的捷径。模型发起的工具调用仍需收到匹配结果;用户直接点名则由命令入口处理,不需要伪造一次模型工具调用。

图 2 先展示默认 inline 路径。需要 context: fork 时,正文交付到子上下文,再按对应配置返回结果;目标与等待方式见子 Agent 参考。这一选择不要求把所有 Skill 都改成 Subagent。

3. 用伪代码追踪同一份内容

下面是本专题的教学设计,不是官方内部函数。它只展开默认 inline 路径;消息角色、Skill 工具协议、去重以及临时权限的具体实现由运行环境决定。

先把正文准备写成一个有明确输入、输出的过程:

def prepare_inline_skill(name, arguments, caller, session):
    skill = registry.resolve(name)
    check_invocation_policy(skill, caller, session)

    body = read_skill_body(skill)
    body = substitute_arguments(body, arguments)

    # 仅在正文包含动态命令时执行;命令也要经过适用的权限检查。
    body = prepare_optional_dynamic_context(body, session)
    return PreparedInstructions(skill=skill, content=body)

准备正文不等于执行正文中的自然语言步骤。例如“查看 git diff”这句普通指令,还要等模型接收到它后请求工具。动态命令的预处理路径则已在渲染参考中单独讨论。

用户点名时,调用入口已经确定:

request = parse_user_input(user_input)

if request.is_direct_skill:
    instructions = prepare_inline_skill(
        request.skill_name, request.arguments, caller="user", session=session
    )
    session.deliver_direct_skill(request, instructions)
else:
    session.append_user_message(user_input)

模型自行选择时,调用发生在原来的工具处理阶段:

while True:
    request_context = assemble_context(
        session=session,
        skill_descriptions=registry.visible_descriptions(session),
    )
    response = ask_llm(request_context, enabled_tools(session))
    session.append_assistant_message(response)

    calls = extract_tool_calls(response)
    if not calls:
        if continue_from_stop_hook(session):
            continue
        break

    results = []
    instruction_deliveries = []

    for call in calls:
        if is_skill_invocation(call):
            blocked = check_tool_call_with_pre_hooks(call, session)
            if blocked:
                results.append(tool_error_result(call.id, blocked))
                continue
            try:
                instructions = prepare_inline_skill(
                    call.skill_name, call.arguments,
                    caller="model", session=session,
                )
            except SkillInvocationError as error:
                result = tool_error_result(call.id, error)
                run_post_tool_hooks(call, result)
                results.append(result)
                continue
            # 本例返回匹配原调用的加载确认,正文单独交付一次。
            result = skill_loaded_ack(call.id, call.skill_name)
            run_post_tool_hooks(call, result)
            results.append(result)
            instruction_deliveries.append(instructions)
        else:
            results.append(execute_with_existing_hooks(call))

    # 先完成这一批调用的结果,再开始下一次模型请求。
    session.complete_tool_turn(results)
    session.deliver_skill_instructions(instruction_deliveries)

这里将“匹配调用结果”和“交付指令”分开命名,便于检查接口;本例的确认结果不含完整正文。如果某个平台直接在工具结果中承载正文,就不要再重复交付。消息封装不表示官方一定有两个同名步骤;权限与 Hook 的具体策略按权限参考接在应用边界上。调用失败返回错误并停止本次装载,不能按“正文已经交付”继续。

目录来自发现阶段的当前状态,并不意味着每次循环都重新扫描磁盘。调用后的正文会成为后续可见信息,普通参考文件和脚本仍按任务需要取用。官方内容生命周期

4. 本地教学代码怎样对应

当前本地 s07_skill_loading/code.py(ce8f9f1)提供了更具体、范围更小的实现:

教学设计中的职责本地代码
发现并建立技能索引SkillLoader.scan()
生成名称与描述目录SkillLoader.catalog(),加入 build_system_prompt()
按名字取得完整内容SkillLoader.load(name)
模型发起加载load_skill 工具与分发表
内容回到模型原循环构造 tool_result 并追加消息

它没有实现本专题讨论的所有现代分支,例如用户命令路由、动态渲染、现代调用控制或 fork。也不能将其未注册 task 理解成 Skills 原理排斥子 Agent。

因此,我读教学代码时追踪“目录与全文如何分开交给模型”;读现代文档时再补充实际产品的入口、上下文和执行配置。两份材料回答的层次不同。

我现在怎样判断自己理解了

拿一个具体 Skill,能说清下面四件事:

  1. 调用前模型知道哪些信息?
  2. 这次由谁发起,正文交到哪个上下文?
  3. 哪些指令由模型落实,哪些内容由应用预处理?
  4. 工具结果、最终报告和后续上下文分别怎样继续?

Skills 改变了模型能够按需获得的方法与指令;这些内容最终仍要通过一个执行上下文和它的 Agent Loop 发挥作用。

上一节:完整案例与评估 · 下一节:完整示例附录