Skip to content
Zegging's Tech Blog
Go back

opencode Skill 功能实现 - 渐进式披露

Opencode Skills 架构剖析:从实现细节到上下文虚拟化

1. 概述

在 Opencode 项目中,Skill (技能) 不仅仅是一份份静态的 Markdown 文档,它是一套精密的动态上下文管理机制。本文将从代码实现层面由浅入深,探讨 opencode 如何通过“渐进式发现”原则,实现对 LLM 有限 Context 的极致优化。

2. 基础实现:SKILL 的发现与注册

Opencode 不会将所有技能直接硬编码进系统提示词,而是采用文件系统扫描的方式进行动态发现。

2.1 扫描机制

位于 packages/opencode/src/skill/skill.ts 的核心逻辑负责在启动时扫描以下路径:

  1. 全局目录~/.claude/skills~/.agents/skills
  2. 项目目录:沿着项目根目录向上查找 .claude / .agents
  3. 配置目录.opencode/skill 以及配置文件中指定的路径。

这种设计确保了技能的可移植性和层级覆盖(项目级覆盖全局级)。

// packages/opencode/src/skill/skill.ts 核心扫描逻辑
const addSkill = async (match: string) => {
  // 解析 Markdown Frontmatter
  const md = await ConfigMarkdown.parse(match);
  // ... 校验与去重 ...
  skills[parsed.data.name] = {
    name: parsed.data.name,
    description: parsed.data.description,
    location: match, // 记录物理路径,暂不加载全部内容
    content: md.content,
  };
};

3. 核心机制:渐进式披露 (Progressive Disclosure)

这是 Skill 架构中最精彩的部分。系统不会在对话开始时将几万字的技能文档扔给 LLM,而是利用 Tool Definition 构建了一个轻量级的“索引表”。

3.1 索引表的构建

packages/opencode/src/tool/skill.ts 中,SkillTool 的定义至关重要。

代码实现细节:

// packages/opencode/src/tool/skill.ts

export const SkillTool = Tool.define("skill", async (ctx) => {
  const skills = await Skill.all()

  // 1. 权限过滤:确保 Agent 只能看到它有权访问的技能
  const accessibleSkills = ...

  // 2. 构建描述符(Descriptor):这是渐进式披露的关键
  // 拼出一段给 LLM 的提示,说明“当识别任务匹配时再调用工具加载完整 skill”
  // 并列出 <available_skills> 清单(仅包含名称、描述、位置)。
  const description = [
      "Load a specialized skill that provides domain-specific instructions and workflows.",
      "When you recognize that a task matches one of the available skills listed below, use this tool to load the full skill instructions.",
      "<available_skills>",
      ...accessibleSkills.flatMap((skill) => [
        `  <skill>`,
        `    <name>${skill.name}</name>`,
        `    <description>${skill.description}</description>`, // 仅暴露描述,隐藏具体内容
        `    <location>${pathToFileURL(skill.location).href}</location>`,
        `  </skill>`,
      ]),
      "</available_skills>",
    ].join("\n")

  return { description, parameters, ... }
})

设计意图: LLM 此时只持有“指针”(Skill Name)和“摘要”(Description),Context 占用极小。

3.2 按需加载 (Page-In)

当 LLM 决定调用 skill 工具时,才会触发真正的加载逻辑。

// packages/opencode/src/tool/skill.ts

async execute(params: z.infer<typeof parameters>, ctx) {
  // 1. 获取完整内容
  const skill = await Skill.get(params.name)

  // 2. 权限校验
  await ctx.ask({ permission: "skill", patterns: [params.name], ... })

  // 3. 文件采样:对 skill 目录进行采样,在提示中展示有限示例
  // 帮助 LLM 理解目录结构,同时避免 Context 爆炸
  const files = await iife(async () => {
    // ... Ripgrep 扫描前 10 个文件 ...
  })

  // 4. 注入完整上下文
  return {
    output: [
      `<skill_content name="${skill.name}">`,
      `# Skill: ${skill.name}`,
      skill.content.trim(), // 此刻,庞大的知识库才真正进入 Context
      "<skill_files>",
      files,
      "</skill_files>",
      "</skill_content>",
    ].join("\n"),
  }
}

4. 数据流闭环

Skill 的内容通过以下链路最终影响模型行为:

  1. Tool Call: LLM 发起工具调用请求。
  2. Tool Execution: SkillTool.execute 返回包含 <skill_content> 的文本。
  3. Message Serialization:
    • 结果被封装为 Message 的 tool part。
    • packages/opencode/src/session/message-v2.ts 中的 toModelMessages 方法将其序列化为模型可读的 tool_result
  4. Context Injection:
    • SessionPrompt 将包含 Skill 结果的消息历史发送给 LLM。
    • LLM 在下一轮推理中,“看到”了刚刚加载的系统级指令。

5. 顶层抽象:上下文虚拟化 (Context Virtualization)

透过代码细节,我们可以看到 Opencode Skill 机制的本质是对 LLM 稀缺注意力资源昂贵上下文窗口的一种极致优化。

5.1 提示词的分页机制 (Prompt Paging)

这与操作系统的虚拟内存机制有着异曲同工之妙:

  • 压缩态 (Page Out): 默认状态下,System Prompt 不含任何领域知识,仅保留一份索引表(Skill List)。
  • 缺页中断 (Page Fault): 当 LLM 的“指令指针”运行到特定领域(如“需要编写 Rust 代码”)时,触发工具调用。
  • 展开态 (Page In): 系统将磁盘上的 SKILL.md 加载到当前的 Context Window 中。

5.2 复杂度的降维

这种设计将 Token 消耗从 O(N)(N为所有技能的总长度)降低到了 O(1)(仅加载当前任务需要的技能)。

5.3 动态系统提示 (Dynamic System Prompting)

传统开发中,System Prompt 往往在对话开始前就写死。Opencode 通过 Skill 实现了后置注入 System Prompt。虽然技术载体是 tool_result,但在语义上,它动态地重塑了 LLM 在当前 Session 中的行为准则、工作流和注意事项。


总结: Opencode 的 Skill 架构不仅是一个插件系统,更是一套注意力管理系统。它允许 Agent 在保持极简初始状态的前提下,通过工具化的检索机制,拥有理论上无限的技能库,并确保注意力始终聚焦在当前最相关的上下文上,有效减少了模型幻觉与上下文被稀释的风险。

继续阅读: Skills 的演进与终局


Share this post on:

Previous Post
SKILL 与 MCP 的区别与应用
Next Post
SKILL 的演进与终局