Skip to content

skill_create 缺少质检守门:skill-creator 可被静默跳过 / skill_create lacks a quality gate #146

@ZhouChaunge

Description

@ZhouChaunge

背景 / Background

DeepCopilot 的 skill 系统中,skill-creator 是一个元 skill(meta-skill),按设计它应当在每次创建新 skill 之前承担质检与优化职责:

  • 优化 description,提高未来触发命中率
  • 校验 SKILL.md body 结构是否符合 skill 规范
  • 对过长 / 未分层的 prompt 做精简
  • 必要时跑 evals 验证

skill_create 工具在实现层 没有任何前置约束,模型可以直接把用户的 raw prompt 塞进 body 参数落盘,整个 skill-creator 流程被静默跳过。

复现 / Reproduction

来自一次真实会话(session-2026-05-24T13-53-49-129Z.log):

  1. 用户让模型"帮我创建一个 skill"
  2. 模型直接调用 skill_create({ name, body: <用户的原始 prompt> })没有调用 skill_invoke({ name: "skill-creator" })
  3. 用户人工 review 才发现内容未经任何优化

模型事后自承认:

你给了我完整的 prompt → 我大脑里直接生成「内容已就绪,写入即可」→ 把 skill_create 当成了单纯的持久化工具 → 跳过了 skill-creator 的审查优化环节。本质上是我把「创建 skill」这个动作降格成了「写文件」,而省略了 skill 系统里 skill-creator 承担的是质检 + 优化的守门角色。

更糟的是,当用户追问"如何避免再发生"时,模型提出"在 .deepcopilot/hooks.json 里加一条 hook",但写下去的 JSON 用的是它自己编造的 schema

{
  "hooks": [{
    "trigger": { "on_tool": "skill_create", "timing": "before" },
    "action":  { "type": "invoke_skill", "skill": "skill-creator" }
  }]
}

而真实 schema(见 src/hooks.js)只支持 event + tool + run(shell 命令),根本没有 trigger / action / invoke_skill。这条 hook 在 loadHooks 里会因为 !hook.run 被直接 continue 跳过——等于没写。也就是说,"为了防止跳过守门"的修复,本身也是一次同款的"降格成写文件"事故。

根因 / Root cause

两层耦合的问题:

L1(主问题)skill_create 工具实现没有前置守门

  • src/tools/skill-tools.jsskill_create 接收 name + body 就直接写盘
  • 没有任何字段要求"必须是经过 skill-creator 处理过的产物"
  • system prompt / skill-creator SKILL.md 里也没有硬性规则强制模型先调用 skill-creator

L2(次问题)hooks 系统表达力不足

  • src/hooks.js 的 hook 只能跑 shell(hook.run
  • 无法表达"工具 A 调用前必须先调用工具/skill B"这种语义反射
  • 模型尝试用 hooks 兜底但被架构限制(同时也暴露了模型对 schema 的幻觉问题)

影响 / Impact

  • 质量风险:所有 skill_create 产物可能都没经过质检,长期累积污染 skill 库
  • 静默失败:模型不会主动报告它"跳过了 skill-creator",只有人工 review 能发现
  • 示范效应:其他元 skill 如果未来引入(例如 prompt-optimizeragent-reviewer)会面对同样的绕过风险

期望行为 / Expected behavior

调用 skill_create 时必须满足以下任一条件,否则工具应拒绝执行并返回明确错误:

  1. 当前 turn 之前已经调用过 skill_invoke({ name: "skill-creator" })
  2. skill_create 的 args 中显式带有 reviewed_by_skill_creator: true(或类似的校验字段,由 skill-creator 在其输出中产生)

被拒绝时的错误消息应包含:「请先调用 skill_invoke({ name: "skill-creator" }) 进行质检 / 优化后再创建 skill」。

候选方案 / Proposed solutions

方案 A:工具实现层硬守门(推荐,小改动、立即见效)

src/tools/skill-tools.jsskill_create 实现里加前置检查:

  • 通过 run/session 对象读取本轮 turn 的工具调用历史
  • 若历史中没有 skill_invoke 且 args 中没有 reviewed_by_skill_creator: true,直接返回工具错误
  • 错误文本要明确指引下一步动作(让模型自动纠正)

优点:确定性、不依赖模型自觉、改动小
缺点:把策略硬编码在工具里;如果未来还有别的元 skill 守门需求要重复实现

方案 B:在 skill-creator SKILL.md + system prompt 里加硬约束(软约束,作补充)

  • 在 skill-creator 的 SKILL.md 顶部加一段"任何 skill 创建必须经过本 skill"的强声明
  • 在系统 prompt(src/prompts/system.js)里追加规则:"before invoking skill_create, you MUST first invoke skill_invoke({ name: 'skill-creator' })"

优点:零代码成本
缺点:纯软约束,模型仍可能短路(本次事故就是绕过了"我应该……"的软约束)→ 建议 与方案 A 叠加使用,不要单独作为修复

方案 C:扩展 hooks 系统支持 invoke_tool / invoke_skill action(长期项)

扩展 src/hooks.js 的 schema:

{
  "hooks": [{
    "event": "before_tool",
    "tool":  "skill_create",
    "action": { "type": "invoke_tool", "name": "skill_invoke", "args": { "name": "skill-creator" } },
    "on_failure": "block"
  }]
}

这需要:

  • runHooks 能向调用方注入"前置工具调用"(不只是 shell output)
  • tool-executor 在 before_tool hook 返回"需要先调用 X"时,把控制权让回 agent loop
  • 新文档 + schema 迁移指引

优点:通用机制,未来所有"工具 A 前必须先调用 B"场景都能配置
缺点:架构改动较大,建议方案 A 落地后再开独立 issue 跟进

相关代码 / Related code

  • src/tools/skill-tools.jsskill_create 工具实现,守门应加在这里
  • src/hooks.js — 现有 hooks 系统(if (!hook.run) continue; 是限制点)
  • src/prompts/system.js — 软约束规则注入点
  • session-2026-05-24T13-53-49-129Z.log — 复现日志

验收标准 / Acceptance Criteria

  • 调用 skill_create 时,若同轮 turn 未先经过 skill-creator,工具返回明确错误而非写盘
  • 错误消息包含可执行的下一步建议
  • 新增单元测试覆盖"跳过 skill-creator → 被拒绝"和"正常路径 → 成功创建"两种情况
  • skill-creator 的 SKILL.md 更新,说明本守门约定
  • README / docs 同步说明该约束(双语)

备注

模型在本次事故中表现出的另一个 pattern——在不知道 schema 的情况下根据上下文猜测并写文件——本身也值得单独跟踪(可能需要在 system prompt 里加"未知 schema 必须先读源码确认"的元规则),但不在本 issue 范围内,建议另开。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions