Skills 系统源码分析

源码路径:src/agents/skills/

核心文件

文件功能
agents/skills/workspace.ts核心:加载、过滤、格式化、限制逻辑
agents/skills/skill-contract.tsformatSkillsForPrompt 全格式输出
agents/skills/agent-filter.tsper-agent skillsLimits 解析
agents/skills/types.tsSkillEntry / SkillSnapshot 类型定义
agents/system-prompt.tsSkills section 注入到 system prompt
config/types.skills.tsSkillsLimitsConfig 类型定义
config/zod-schema.ts全局 skills.limits Zod schema
config/zod-schema.agent-runtime.tsper-agent skillsLimits Zod schema
agents/cli-runner/prepare.ts调用入口 resolveSkillsPromptForRun()

配置项:maxSkillsPromptChars

类型定义(config/types.skills.ts)

export type SkillsLimitsConfig = {
  maxCandidatesPerRoot?: number;       // 每个 root 最多扫描的候选目录数,默认 300
  maxSkillsLoadedPerSource?: number;   // 每个来源最多加载的 skill 数,默认 200
  maxSkillsInPrompt?: number;          // 最多注入 prompt 的 skill 数,默认 150
  maxSkillsPromptChars?: number;       // prompt 块最大字符数(触发 compact 的阈值),默认 18,000
  maxSkillFileBytes?: number;          // SKILL.md 文件最大字节数,默认 256,000
};

默认值(agents/skills/workspace.ts L138-142)

const DEFAULT_MAX_CANDIDATES_PER_ROOT      = 300;
const DEFAULT_MAX_SKILLS_LOADED_PER_SOURCE = 200;
const DEFAULT_MAX_SKILLS_IN_PROMPT         = 150;
const DEFAULT_MAX_SKILLS_PROMPT_CHARS      = 18_000;
const DEFAULT_MAX_SKILL_FILE_BYTES         = 256_000;

默认值(agents/skills/workspace.ts)

const DEFAULT_MAX_SKILLS_PROMPT_CHARS = 18_000;

优先级(从高到低)

  1. agents.list[agentId].skillsLimits.maxSkillsPromptChars(per-agent override)
  2. skills.limits.maxSkillsPromptChars(全局配置)
  3. DEFAULT_MAX_SKILLS_PROMPT_CHARS = 18,000

配置示例

# 全局
skills:
  limits:
    maxSkillsPromptChars: 18000
 
# per-agent override
agents:
  list:
    - id: my-agent
      skillsLimits:
        maxSkillsPromptChars: 30000

Compact Mode 触发逻辑

常量

const COMPACT_WARNING_OVERHEAD = 150;  // 为警告行预留的字符数

三个 Tier(applySkillsPromptLimits)

全格式长度 <= maxSkillsPromptChars
    → Tier 1: Full Format(含 description)

全格式超限,紧凑格式长度 <= maxSkillsPromptChars - 150
    → Tier 2: Compact,不截断(所有 skill 保留,仅去掉 description)

紧凑格式也超限
    → Tier 3: Compact + 二分查找最大可容纳数量,截断

核心函数(workspace.ts)

function applySkillsPromptLimits(params: {
  skills: Skill[];
  config?: OpenClawConfig;
  agentId?: string;
}): { skillsForPrompt: Skill[]; truncated: boolean; compact: boolean } {
  const limits = resolveSkillsLimits(params.config, params.agentId);
 
  const byCount = params.skills.slice(0, limits.maxSkillsInPrompt);
  let skillsForPrompt = byCount;
  let truncated = params.skills.length > byCount.length;
  let compact = false;
 
  const fitsFull = (skills: Skill[]) =>
    formatSkillsForPrompt(skills).length <= limits.maxSkillsPromptChars;
  const compactBudget = limits.maxSkillsPromptChars - COMPACT_WARNING_OVERHEAD;
  const fitsCompact = (skills: Skill[]) =>
    formatSkillsCompact(skills).length <= compactBudget;
 
  if (!fitsFull(skillsForPrompt)) {
    if (fitsCompact(skillsForPrompt)) {
      compact = true;                          // Tier 2
    } else {
      compact = true;
      // 二分查找最大可容纳数量
      let lo = 0, hi = skillsForPrompt.length;
      while (lo < hi) {
        const mid = Math.ceil((lo + hi) / 2);
        if (fitsCompact(skillsForPrompt.slice(0, mid))) lo = mid;
        else hi = mid - 1;
      }
      skillsForPrompt = skillsForPrompt.slice(0, lo);
      truncated = true;                        // Tier 3
    }
  }
  return { skillsForPrompt, truncated, compact };
}

警告文案

// Tier 3(截断 + compact)
`⚠️ Skills truncated: included ${skillsForPrompt.length} of ${resolvedSkills.length} (compact format, descriptions omitted). Run \`openclaw skills check\` to audit.`
 
// Tier 2(compact,不截断)
`⚠️ Skills catalog using compact format (descriptions omitted). Run \`openclaw skills check\` to audit.`

格式化函数

Full Format(skill-contract.ts)

export function formatSkillsForPrompt(skills: Skill[]): string {
  // 输出包含 <description> 标签
  // <skill>
  //   <name>...</name>
  //   <description>...</description>
  //   <location>...</location>
  // </skill>
}

Compact Format(workspace.ts)

export function formatSkillsCompact(skills: Skill[]): string {
  // 仅输出 <name> 和 <location>,省略 <description>
  // <skill>
  //   <name>...</name>
  //   <location>...</location>
  // </skill>
}

加载流程

来源优先级(低 → 高,后者覆盖前者)

openclaw-extra          (extraDirs + plugin skills)
openclaw-bundled        (bundled skills)
openclaw-managed        (managed skills)
agents-skills-personal  (~/.agents/skills)
agents-skills-project   (workspace/.agents/skills)
openclaw-workspace      (workspace/skills)

完整调用链

CLI Entry: resolveSkillsPromptForRun()          (cli-runner/prepare.ts)
  └─ buildWorkspaceSkillsPrompt()
       └─ resolveWorkspaceSkillPromptState()
            ├─ loadSkillEntries()               加载所有来源
            ├─ filterSkillEntries()             应用 skillFilter
            ├─ compactSkillPaths()              路径缩短(~/...)
            ├─ sort() by name                  按名称排序
            ├─ applySkillsPromptLimits()        应用字符限制 → 决定 tier
            │    ├─ resolveSkillsLimits()       读取配置 + 默认值
            │    ├─ formatSkillsForPrompt()     全格式测量
            │    └─ formatSkillsCompact()       紧凑格式测量
            └─ 拼接 truncationNote + prompt
  └─ 注入 buildAgentSystemPrompt()             (system-prompt.ts)

路径紧凑化

prompt 中的 skill 路径会被缩短为 ~/... 格式,减少 token 消耗:

function compactSkillPaths(skills: Skill[]): Skill[] {
  // /Users/xxx/.agents/skills/foo/SKILL.md → ~/.agents/skills/foo/SKILL.md
}

关联文档