SOP:PostToolUse 守卫 skills 命中面收窄(2026-10-02)¶
1. 背景¶
posttooluse-guard/skill-edit-guard.py 在写操作命中 ~/.trae-cn/skills/ 任意文件时,
统一提醒「改动 skill / 全局规则会使上游前缀缓存失效(约 0.35 元/次),请开新会话」。
但既有实测结论(measured-baselines.md 第十二节)与该提醒矛盾:只有 SKILL.md 的
frontmatter description 进每轮 system prompt 的技能索引,skill 正文与 references/
是「按需加载」,改它们不进每轮前缀、不触发缓存重建。旧实现属错误归因,会诱导一次
无谓的新会话(重注入 HANDOFF + 重建上下文同样花钱)。本轮先复测确认,再收窄判据。
2. 目标¶
- 以同会话逐步
cache_miss复测「改references/是否触发前缀重建」,给出可复现证据; - 守卫改为:skills 下仅当「文件名为
SKILL.md且改动文本含description:」时提醒;references/、skill 正文(非 SKILL.md)一律静默; - 14 项离线回归全过,且不破坏既有四类提醒(
user_rules/commands/hooks.json分档文案)。
3. 前置条件¶
- 只读取证工具:
cost_audit/selfcheck.py、logs/usage.jsonl(只经脚本聚合,不读原文); - 守卫脚本可写;
hooks.json不需改动(脚本改动即时生效,改hooks.json才须重启 Trae); - 复测窗口须覆盖整个任务(
selfcheck --since/--until带秒)。
4. 影响范围¶
- 涉及组件:
posttooluse-guard/skill-edit-guard.py(层 3 守卫)一处;不涉及代理服务、不动hooks.json; - 无需停服、无窗口期约束;
token-proxy服务与在跑作业不受影响; - 影响面仅限「写操作后的提醒文案与触发条件」;漏报的最坏后果是少一次提醒,不影响任何功能路径。
5. 分步操作¶
实现体:posttooluse-guard/skill-edit-guard.py(改动落在该文件,下列序列供阅读与复现)。
步骤 1:复测「改 references/ 是否触发前缀重建」¶
在本会话内对 ~/.trae-cn/skills/ops-execution-efficiency/references/measured-baselines.md
做一次真实 SearchReplace 编辑,然后逐步看 cache_hit_tokens / cache_miss_tokens。
坑:miss 单值不能直接判「重建」——读入一个大文件、写一次报告,都会让下一步的
miss 接近该内容大小(新内容首次入上下文,一次性未命中)。判据是 hit 是否回落:
前缀重建表现为 hit 骤降 + miss ≈ prompt 全量(如 364,998 那条)。
python3 - <<'PY'
import json
rows = [json.loads(l) for l in open('logs/usage.jsonl') if l.strip()]
conv = rows[-1]['conv_key']
for r in [x for x in rows if x.get('conv_key') == conv]:
print(r['ts'], 'hit=%s miss=%s out=%s' % (r['cache_hit_tokens'], r['cache_miss_tokens'], r['completion_tokens']))
PY
预期:本会话 hit 从 6,784 单调递增到 127,104、无回落;改动 references/ 的那一步
miss=1,584,跨用户轮次后的首个请求 miss=2,339——均在 1~2 K 量级,不构成重建。
步骤 2:备份原脚本¶
高危动作前先留原文件,位置与命名见全局规则;本主题台账 backup/README.md。
# 高危:这是覆盖前唯一的回滚点
TS=$(date +%Y%m%d-%H%M%S)
mkdir -p hook-skill-desc-20261002/backup/posttooluse-guard
cp -p posttooluse-guard/skill-edit-guard.py \
"hook-skill-desc-20261002/backup/posttooluse-guard/skill-edit-guard.py.bak.$TS"
ls -l hook-skill-desc-20261002/backup/posttooluse-guard/
预期:列出 1 个 .bak.<时间戳> 文件,大小与原脚本一致(12,456 B)。
步骤 3:改判据(四处)¶
- 常量:删
WATCHED_DIRS,新增SKILL_FILE = "SKILL.md"、DESCRIPTION_RE、CONTENT_KEYS;SKILLS_DIR/RULES_DIR/COMMANDS_DIR保留; - 新函数
_touches_description(tool_input):任一文本字段出现行首description:即判为 「可能改了 frontmatter」; - 命中收集:
user_rules/commands/hooks.json照旧;skills 下改为basename == SKILL_FILE and _touches_description(tool_input)才计入; - 文案:
prefix_hits一句改为「改动全局规则或 skill 的 frontmatter(description 等, 二者均进每轮 system prompt)…」。
坑:Write 整篇写一份 SKILL.md 时必然含 description: 行,即使值未变也会命中——
单次 PostToolUse 事件取不到旧值,无法比对,此为该判据的已知局限(见第 8 章)。
# 查看实际改动(与备份逐行比对)
diff -u hook-skill-desc-20261002/backup/posttooluse-guard/skill-edit-guard.py.bak.20261002-140208 \
posttooluse-guard/skill-edit-guard.py
python3 -m py_compile posttooluse-guard/skill-edit-guard.py && echo "compile OK"
预期:diff 只出现上述四处;compile OK。
步骤 4:跑离线用例¶
python3 hook-skill-desc-20261002/verify_guard_cases.py
预期:14 项全 PASS,末尾打印「失败 0 项」。
6. 验证¶
| # | 用例 | 预期 | 实测(2026-10-02 14:04) |
|---|---|---|---|
| V1 | 规则改动(user_rules) |
提醒含「开新会话」 | 通过 |
| V2 | 命令改动(commands) |
提醒含「无需为此开新会话」 | 通过 |
| V3 | hooks.json 改动 |
提醒含「重启 Trae」 | 通过 |
| V4 | 只读工具(Read) |
静默 | 通过 |
| V5 | 备份文件(*.bak.<ts>) |
静默 | 通过 |
| V6 | 非监视路径(/tmp/*) |
静默 | 通过 |
| V7 | references/ 改动 |
静默(本次新增判据) | 通过 |
| V8 | skill 正文改动(SKILL.md 非 frontmatter) |
静默(本次新增) | 通过 |
| V9 | skill 目录下非 SKILL.md |
静默 | 通过 |
| V10 | SKILL.md 的 description: 改动 |
提醒 | 通过 |
| V11 | 整篇 Write SKILL.md |
提醒(可忽略,见第 8 章) | 通过 |
| V12 | 非对象 JSON([]) |
退出码 0、stderr 空、静默 | 通过 |
| V13 | 空 stdin | 同上 | 通过 |
| V14 | 同会话去重(第二次) | 静默 | 通过 |
全部用例同时满足两条红线:退出码 0、stderr 为空。
实测命令:python3 hook-skill-desc-20261002/verify_guard_cases.py。
待补的真机证据:本次收窄的「正向」对照(改 SKILL.md 的 description → 下一轮
全量重建)沿用 2026-10-01 的旧实测,本轮未重跑;在新会话里做一次即可闭环。
7. 回滚¶
- 备份位置:
hook-skill-desc-20261002/backup/posttooluse-guard/skill-edit-guard.py.bak.20261002-140208, 台账见hook-skill-desc-20261002/backup/README.md(同时登记docs/HANDOFF.md的备份); - 回滚触发条件(量化):① 用例回归出现 FAIL;② 新判据导致该提醒时静默(线上漏报,
例如改
user_rules未提醒);③ 脚本出现非 0 退出码或 stderr 非空。
# 高危:覆盖现存脚本;脚本即时生效,无需重启 Trae
cp -p hook-skill-desc-20261002/backup/posttooluse-guard/skill-edit-guard.py.bak.20261002-140208 \
posttooluse-guard/skill-edit-guard.py
python3 -m py_compile posttooluse-guard/skill-edit-guard.py && echo "rollback OK"
回滚后验证:重跑第 6 章用例表——V1~V6、V10~V14 应仍全过,V7~V9 应回到「提醒」(旧行为)。
8. 风险说明¶
| 风险 | 说明与缓解 |
|---|---|
判据基于文本关键词 description: |
若某 skill 的 frontmatter 写法异常(无 description: 行),改它会漏报;缓解:漏报代价是一次提醒,不影响功能;hooks.json / user_rules 路径不受影响 |
整篇 Write SKILL.md 误报 |
必然含 description: 行,即使值未变也提醒(取不到旧值比对);缓解:文案与 docstring 已写明「此时可忽略」,且整篇重写 SKILL.md 属低频动作 |
改 references/ 时文本恰好含 description: 字样 |
会被判为 frontmatter 改动而提醒;缓解:路径在 references/ 下时不看内容(代码先判文件名是否为 SKILL.md),实际不受影响 |
| 真机正向对照未重跑 | 「改 description → 全量重建」沿用旧实测;缓解:已登记为 HANDOFF 3.2 待观察项,新会话补一次即可 |
| 改动触发当前会话前缀重建 | 本脚本不在 hooks.json、也不进每轮前缀,改它不需要开新会话(与改规则不同) |
来源:
/home/rain/Documents/services/token_proxy/hook-skill-desc-20261002/SOP.md| 最后更新:2026-10-02 14:05(由build/sync.py同步,请勿在站点侧修改)