# 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<NetaClawSkillEntity>;   // DB 同步用
  agentRepo: Repository<NetaClawAgentEntity>;   // 自动关联新 skill 用
  agentName: string;                             // 当前 Agent 名称
  linkedSkills: string[];           // Agent 已勾选的 skill 名称
  allowOptimize: boolean;
  allowCreateNew: boolean;
}

async function spawnSkillReview(ctx: SkillReviewContext): Promise<void> {
  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<void>;

// 获取 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 模块。
