Skills 系统源码分析
源码路径:
src/agents/skills/
核心文件
| 文件 | 功能 |
|---|---|
agents/skills/workspace.ts | 核心:加载、过滤、格式化、限制逻辑 |
agents/skills/skill-contract.ts | formatSkillsForPrompt 全格式输出 |
agents/skills/agent-filter.ts | per-agent skillsLimits 解析 |
agents/skills/types.ts | SkillEntry / SkillSnapshot 类型定义 |
agents/system-prompt.ts | Skills section 注入到 system prompt |
config/types.skills.ts | SkillsLimitsConfig 类型定义 |
config/zod-schema.ts | 全局 skills.limits Zod schema |
config/zod-schema.agent-runtime.ts | per-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;优先级(从高到低)
agents.list[agentId].skillsLimits.maxSkillsPromptChars(per-agent override)skills.limits.maxSkillsPromptChars(全局配置)DEFAULT_MAX_SKILLS_PROMPT_CHARS= 18,000
配置示例
# 全局
skills:
limits:
maxSkillsPromptChars: 18000
# per-agent override
agents:
list:
- id: my-agent
skillsLimits:
maxSkillsPromptChars: 30000Compact 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
}关联文档
- tool-skills — Skills 系统用户文档
- tool-skills-config — Skills 配置 schema
- system-prompt — system prompt 构建