# Skill 自进化系统设计 > 日期: 2026-04-13 > 模块: netaclaw/skill-evolution > 状态: 设计阶段 > 依赖: P0 长期记忆系统(共享 background review 模式) ## 1. 目标 为 NetaClaw Agent 添加 Skill 自进化能力:Agent 在对话中积累经验后,后台 review agent 自动提炼可复用的方法论为 Skill,或更新已有 Skill。同时提供 `skill_manage` 工具允许 Agent 在对话中主动创建/编辑 Skill。所有 Agent 创建的 Skill 经过安全扫描后才写入文件系统。 ## 2. 核心决策 | 决策项 | 选择 | 理由 | |--------|------|------| | Review 触发方式 | 迭代计数(默认每 10 轮工具调用) | Hermes 验证过的模式,平衡频率和成本 | | Review Agent 模型 | 复用主 Agent 同模型 | 保证提炼质量 | | 安全校验 | 完整正则扫描(~84 条规则) | Agent 创建内容需要严格审查 | | Skill 存储 | 文件系统 + DB 元数据 | 与现有架构一致 | | Review 执行方式 | 异步不阻塞主对话 | 不影响用户体验 | ## 3. 系统架构 ``` [Agent 对话循环 (attempt.ts)] ↓ toolCallCount 累计 >= N [chat.ts 检测触发条件] ↓ 异步(不阻塞响应) [spawnSkillReview()] → 独立 runAgent() 调用 ↓ review agent 拥有 skill_manage + skill_list 工具 [skill_manage 工具] ↓ 写入前 [SkillGuard 安全扫描] ↓ 通过 → 原子写入 [文件系统 skills/{name}/SKILL.md] + [DB netaclaw_skill] ↓ 写入后 [SkillLoader.reloadSkill()] → 热更新内存缓存 ↓ 下次对话 [system prompt 自动包含新 skill] ``` 关键约束: - Review agent 是一次性的:spawn → 分析 → 写入 → 结束 - Review agent 禁用自身的 nudge interval(防止递归 review) - Review agent 最多 8 轮工具调用 - Skill 写入是原子的:先写临时文件,扫描通过后 rename - Review agent 不产生用户可见输出 ## 4. Skill Manager 工具 ### 4.1 skill_manage 工具 ```typescript // tools/builtin/skill_manage.ts interface SkillManageParams { action: 'create' | 'edit' | 'patch' | 'delete' | 'write_file' | 'remove_file'; name: string; // skill 名称 content?: string; // SKILL.md 完整内容(create/edit) old_string?: string; // patch: 要替换的文本 new_string?: string; // patch: 替换后的文本 replace_all?: boolean; // patch: 替换所有匹配 category?: string; // 可选分类目录 file_path?: string; // 支持文件路径(write_file/remove_file) file_content?: string; // 支持文件内容(write_file) } ``` | action | 用途 | 必需参数 | |--------|------|---------| | create | 创建新 skill | name, content | | edit | 完整重写 SKILL.md | name, content | | patch | 局部替换 | name, old_string, new_string | | delete | 删除 skill 目录 | name | | write_file | 写入支持文件 | name, file_path, file_content | | remove_file | 删除支持文件 | name, file_path | ### 4.2 skill_list 工具 ```typescript // 只读工具,返回已有 skill 列表 interface SkillListParams { category?: string; // 可选:按分类过滤 } // 返回: [{ name, description, category }] ``` ### 4.3 验证规则 | 规则 | 限制 | |------|------| | name | 最长 64 字符,`/^[a-z0-9][a-z0-9._-]*$/` | | category | 单层目录,同 name 命名规则 | | SKILL.md content | 最大 100,000 字符 | | 支持文件 | 最大 1MB/文件 | | 支持文件目录 | 仅 `references/`, `templates/`, `scripts/`, `assets/` | | frontmatter | 必须包含 `name` 和 `description` 字段 | | description | 最长 1024 字符 | | 总文件数 | 每个 skill 最多 50 个文件 | | 总大小 | 每个 skill 最大 1MB | ### 4.4 原子写入流程 ``` 1. 验证参数 → 失败则返回错误 2. 写入临时文件 skills/{name}/.tmp_SKILL.md 3. 调用 SkillGuard.scan() 扫描临时文件 4. 如果 verdict === 'dangerous' → 删除临时文件,返回错误 5. rename 临时文件 → SKILL.md 6. 同步 DB 元数据(netaclaw_skill 表) 7. 调用 SkillLoader.reloadSkill(name) 热更新缓存 8. 清除 skill prompt 缓存 ``` ## 5. Skill Guard 安全扫描 ### 5.1 威胁模式分类 参考 Hermes skills_guard.py 的 84 条正则规则,按类别组织: | 类别 | 规则数 | severity | 示例模式 | |------|--------|----------|---------| | 数据泄露 (exfiltration) | 18 | critical | `process\.env`, `\.ssh/`, `credentials`, DNS exfil | | 提示注入 (prompt_injection) | 16 | critical | `ignore.*instructions`, `you are now`, `system prompt` | | 破坏性操作 (destructive) | 8 | critical | `rm\s+-rf\s+/`, `mkfs`, `dd\s+if=`, `chmod\s+777` | | 持久化 (persistence) | 10 | high | `crontab`, `authorized_keys`, `systemd`, `sudoers` | | 网络 (network) | 9 | high | reverse shells, tunnels, hardcoded IPs | | 混淆 (obfuscation) | 14 | high | `eval\(`, `Buffer\.from.*base64`, hex encoding | | 代码执行 (execution) | 6 | high | `child_process`, `exec\(`, `spawn\(` | | 路径穿越 (path_traversal) | 5 | high | `\.\.\/`, `/etc/passwd`, `/proc/` | | 加密挖矿 (crypto_mining) | 2 | critical | `stratum+tcp`, `xmrig` | | 供应链 (supply_chain) | 6 | critical | `curl.*\|.*bash`, unpinned deps | | 权限提升 (privilege_escalation) | 5 | high | `sudo`, `setuid`, `NOPASSWD` | | 凭证暴露 (credential_exposure) | 5 | critical | hardcoded secrets, private keys | ### 5.2 扫描接口 ```typescript // skill_evolution/guard.ts interface ScanFinding { category: string; severity: 'critical' | 'high' | 'medium'; pattern: string; match: string; file: string; line: number; } interface ScanResult { verdict: 'safe' | 'caution' | 'dangerous'; findings: ScanFinding[]; summary: string; } function scanSkill(skillPath: string): ScanResult; ``` ### 5.3 判定逻辑 ``` findings 中有 critical → verdict = 'dangerous' findings 中有 high → verdict = 'caution' 无 findings → verdict = 'safe' ``` Agent 创建的 skill 策略: - safe → 允许 - caution → 允许(记录日志) - dangerous → 阻止,回滚,返回错误信息给 agent ### 5.4 二进制文件拦截 禁止的文件扩展名:`.exe`, `.dll`, `.so`, `.dylib`, `.bin`, `.dat`, `.com`, `.msi`, `.dmg`, `.app`, `.deb`, `.rpm` ## 6. Background Review 机制 ### 6.1 触发条件 在 `controller/chat.ts` 中,`runAgent()` 返回后: ```typescript // 从 Agent 配置读取 const evoConfig = agentEntity?.config?.skillEvolution; const skillEvolutionEnabled = evoConfig?.enabled ?? false; const nudgeInterval = evoConfig?.nudgeInterval ?? 10; // 累计工具调用计数(跨同一会话的多次请求) const totalCalls = (sessionMeta.toolCallsSinceReview ?? 0) + result.toolCallCount; const shouldReview = ( skillEvolutionEnabled && totalCalls >= nudgeInterval && result.toolCallCount > 0 ); if (shouldReview) { sessionMeta.toolCallsSinceReview = 0; // 异步触发,不阻塞响应 spawnSkillReview({ conversationHistory: history, parentAgentConfig: agentConfig, skillLoader: this.skillLoader, linkedSkills: agentEntity?.skills ?? [], allowOptimize: evoConfig?.allowOptimizeLinked ?? true, allowCreateNew: evoConfig?.allowCreateNew ?? true, }).catch(err => logger.warn('Skill review failed:', err)); } else { sessionMeta.toolCallsSinceReview = totalCalls; } ``` ### 6.2 Review Agent 配置 ```typescript // skill_evolution/review.ts interface SkillReviewContext { conversationHistory: LLMMessage[]; parentAgentConfig: AgentConfig; skillLoader: SkillLoaderService; skillRepo: Repository; // DB 同步用 agentRepo: Repository; // 自动关联新 skill 用 agentName: string; // 当前 Agent 名称 linkedSkills: string[]; // Agent 已勾选的 skill 名称 allowOptimize: boolean; allowCreateNew: boolean; } async function spawnSkillReview(ctx: SkillReviewContext): Promise { const reviewPrompt = buildSkillReviewPrompt( ctx.linkedSkills, ctx.allowOptimize, ctx.allowCreateNew, ); const reviewConfig: AgentConfig = { ...ctx.parentAgentConfig, name: `${ctx.parentAgentConfig.name}_skill_reviewer`, systemPrompt: SKILL_REVIEW_SYSTEM_PROMPT, maxToolRounds: 8, }; // skill_manage 工具接收 SkillWriter 实例(非 SkillLoader) const writer = new SkillWriter(ctx.skillLoader.getSkillsDir(), ctx.skillRepo, ctx.skillLoader); const tools = [ createSkillManageTool(writer, { allowedEditSkills: ctx.allowOptimize ? ctx.linkedSkills : [], allowCreateNew: ctx.allowCreateNew, }), createSkillListTool(ctx.skillLoader), ]; await runAgent({ agentConfig: reviewConfig, tools, userMessage: reviewPrompt, history: ctx.conversationHistory, }); } ``` ### 6.3 Review Prompt Review prompt 是动态生成的,根据 Agent 的 skillEvolution 配置和已勾选 skill 列表拼接: ```typescript function buildSkillReviewPrompt( linkedSkills: string[], // Agent 已勾选的 skill 名称列表 allowOptimize: boolean, allowCreateNew: boolean, ): string { let prompt = '回顾上面的对话,考虑是否需要保存或更新 skill。\n\n'; prompt += '关注:\n'; prompt += '1. 是否使用了非显而易见的方法来完成任务?\n'; prompt += '2. 是否经历了试错或因实际发现而改变了方案?\n'; prompt += '3. 用户是否期望或希望不同的方法或结果?\n\n'; if (allowOptimize && linkedSkills.length > 0) { prompt += `当前 Agent 已关联以下 skill:${linkedSkills.join(', ')}\n`; prompt += '请优先检查这些已有 skill 是否可以根据本次对话的经验进行优化(用 patch 更新)。\n\n'; } if (allowCreateNew) { prompt += '如果发现了全新的可复用方法论且没有相关 skill,请创建新 skill。\n\n'; } prompt += '操作指南:\n'; prompt += '- 先用 skill_list 查看已有 skill,避免重复创建\n'; prompt += '- 优化已有 skill 时优先用 patch(局部替换),避免 edit 全量重写\n'; prompt += '- Skill 内容应聚焦于可复用的方法论,而非具体的业务数据\n'; prompt += '- 如果没有值得保存的内容,直接说"无需保存"并停止\n'; return prompt; } ### 6.4 Review System Prompt ``` 你是一个 Skill 提炼专家。你的任务是分析对话历史,提取可复用的方法论并保存为 Skill。 Skill 格式要求: - SKILL.md 必须包含 YAML frontmatter(name + description) - 正文用 Markdown,包含:触发条件、工作流程、规则约束 - name 使用小写字母、数字、连字符(如 nginx-deploy-config) - description 一句话描述 skill 的用途 你只有 8 轮工具调用机会,请高效行动。 ``` ## 7. Agent 配置扩展 ### 7.1 AgentEntity.config 扩展 ```typescript interface AgentConfig { // ...现有字段 skillEvolution?: { enabled: boolean; nudgeInterval?: number; // 触发 review 的工具调用间隔,默认 10 allowOptimizeLinked?: boolean; // 是否允许优化已勾选的 skill,默认 true allowCreateNew?: boolean; // 是否允许创建全新 skill,默认 true }; } ``` ### 7.2 Skill 进化范围 Agent 的 skill 进化有两种模式,均可在 Agent 编辑页面独立开关: **模式 1:优化已勾选 Skill(allowOptimizeLinked)** - Agent 配置中已关联的 skill(`agent.skills[]` 列表)可以被 review agent 通过 patch/edit 更新 - 适用场景:电商运营 Agent 反复执行"上架商品"skill,在实践中发现更好的标题写法或定价策略,自动优化该 skill - Review agent 在分析对话时,优先检查已勾选 skill 是否有改进空间 **模式 2:创建全新 Skill(allowCreateNew)** - Review agent 可以发现对话中的新方法论并创建全新 skill - 新创建的 skill 自动关联到当前 Agent(加入 `agent.skills[]`) - 适用场景:投流 Agent 在实践中摸索出一套新的出价策略,提炼为独立 skill ### 7.3 多 Agent 协作场景考虑 电商运营系统中多个 Agent 各司其职(上架、下架、投流、产品管理等),skill 进化需要考虑: - **Skill 共享**:一个 Agent 优化的 skill 如果被其他 Agent 也勾选了,优化会自动生效(因为 skill 存在文件系统,所有 Agent 共享同一份) - **写入冲突**:多个 Agent 同时优化同一个 skill 时,用文件锁(lockfile)保证原子性,后写入的 patch 基于最新内容 - **进化隔离**:如果某个 Agent 的场景特殊,不希望它的优化影响其他 Agent,可以关闭 `allowOptimizeLinked`,只允许 `allowCreateNew`(新 skill 只关联到自己) ### 7.4 Agent 编辑页面配置 在 Agent 编辑页面新增"Skill 进化"配置区域: - 总开关:启用/禁用 Skill 进化 - 子开关:允许优化已勾选 Skill(默认开) - 子开关:允许创建新 Skill(默认开) - 数字输入:Review 触发间隔(高级选项,默认 10) ### 7.5 SkillLoader 扩展 新增方法: ```typescript // 热更新单个 skill(skill_manage 写入后调用) async reloadSkill(name: string): Promise; // 获取 skill 列表摘要(给 review agent 用) getSkillSummaries(): { name: string; description: string; category?: string }[]; ``` ## 8. 新增文件清单 ``` src/modules/netaclaw/ ├── skill_evolution/ │ ├── guard.ts # SkillGuard 安全扫描(正则模式匹配) │ ├── guard_patterns.ts # 威胁模式定义(84 条规则) │ ├── review.ts # spawnSkillReview() 后台 review 逻辑 │ └── skill_writer.ts # 原子写入 + 验证 + DB 同步 ├── tools/builtin/ │ ├── skill_manage.ts # skill_manage 工具 │ └── skill_list.ts # skill_list 工具 ``` ## 9. 修改文件清单 | 文件 | 修改内容 | |------|---------| | `controller/chat.ts` | runAgent 返回后检测触发条件,异步调用 spawnSkillReview,通过 sessionRepo 直接更新 metadata | | `service/skill_loader.ts` | 新增 reloadSkill()、getSkillSummaries()、getSkillsDir() 方法 | ## 10. 依赖 无新增外部依赖。安全扫描用纯正则实现,skill 文件读写用 Node.js fs 模块。