跳转至

SOP:收尾四项固定化 + 禁止提问窗 + miss 检测(2026-10-07)

1. 背景

用户(2026-10-07)提出三项诉求:

  1. 收尾要"真实执行时间":现有 selfcheck.py --since/--until 是跨轮窗口,会把用户阅读 / 回答 / 暂停的时间算进墙钟;要求改为纯执行时间,并把「合并步数 / 步数 / 时间墙 / 费用」无条件写进收尾, 不再沿用「无异常一句话」的旧形态。
  2. 不再弹提问窗:需要确认时用正文写出待确认项并结束本轮,等用户回复;不要调用 AskUserQuestion。
  3. miss 通知:单步 cache_miss_tokens ≥ 5000(大 miss 阈值 10000 的 1/2)每天都发生, 要求发现即在收尾通知并定位原因。(用户先写 500、随后澄清为 5000。)

2. 目标

目标 可验证状态
收尾固定四项 收尾答复必含「时间墙 / 本轮步数 / 本轮合并 / 本会话累计费用」
时间墙口径 取 turn_key(轮内首→末步),轮间用户等待不计入
一条命令取数 python3 cost_audit/wrapup.py 输出四项 + miss 检测
禁止提问窗 AskUserQuestion 被 merge-guard 判据 J 拦截(首次 deny、原样重发放行)
miss 检测 本轮单步 miss ≥5000 时列出并标注「轮首条 / 中段 / 是否伴随 tools_hash 变化」
guard 数据积累 强制拦截的 deny / allow_fp / remind 事件落 logs/guard_events.jsonl,收尾报「被拒次数」

3. 前置条件

  • 对 logs/usage.jsonl、logs/req_shape.jsonl 有读权限(本机 rain)。
  • ~/.trae-cn/hooks.json 中 merge-guard 的 matcher 为 *(已具备,本次未改 hooks.json)。
  • 口径依据来自 proxy.py 的 classify_request:conv_key=首条用户输入哈希、turn_key=末条用户输入哈希、 turn_seq=用户输入条数。

4. 影响范围

对象 类型 生效方式
~/.trae-cn/user_rules/rule-agent-efficiency.md 全局规则(进每轮 system prompt) 开新会话生效;本会话内即触发一次前缀失效(单条约 0.35 元)
merge-guard/merge-guard.py Hook 脚本 即刻生效(无需重启 Trae)
cost_audit/wrapup.py 新增只读脚本 即刻可用
  • 无停服;不影响代理 ds/glm/mi 与在跑作业。
  • 窗口:无需窗口期。

5. 分步操作

步骤 1:备份(先备份再改,备份即授权)

TS=$(date +%Y%m%d-%H%M%S)
cp -p ~/Documents/services/token_proxy/merge-guard/merge-guard.py \
      ~/Documents/services/token_proxy/wrapup-ask-guard-20261007/backup/merge-guard/merge-guard.py.bak.$TS
cp -p ~/.trae-cn/user_rules/rule-agent-efficiency.md \
      ~/.trae-cn/user_rules/backup/rule-agent-efficiency.md.bak.$TS
chmod 600 ~/.trae-cn/user_rules/backup/rule-agent-efficiency.md.bak.$TS

预期:两个 .bak 落盘,全局规则备份权限 600。失败判断:任一副本缺失 → 停止,不得继续。

步骤 2:merge-guard.py 增加判据 J(提问窗)

在判据列表、ENFORCE_MARKS、PROMPTS、DENY_PROMPTS 各加一项,并在 main() 的 F 判定之前加单次判定:

# J:提问窗(单次调用即可判,与序列无关)——环境约定:不用提问窗,改在正文提问并结束本轮
if hit is None and tool_name == "AskUserQuestion":
    hit = ("J", 1)

坑:DENY_PROMPTS[mark] % (n,) 对模板做了元组格式化,J 的模板必须含恰好一个 %d,否则抛 TypeError: not all arguments converted(被 except Exception: pass 静默吞掉,表现为"拦截无效")。

步骤 3:新增 cost_audit/wrapup.py

  • 读 usage.jsonl 尾行取 conv_key / turn_key;本轮 = 同 turn_key,本会话 = 同 conv_key。
  • 合并指标复用 step_profile.analyze()(import step_profile),不另立口径。
  • 坑:usage.jsonl 用 turn_key / conv_key,req_shape.jsonl 用 turn / conv,字段名不同,勿混。

步骤 4:改全局规则 rule-agent-efficiency.md

两处:① 「收尾自查」节改为固定四项;② 「停止判据」之后新增「交互(不弹提问窗)」节。

坑:该文件进每轮 system prompt,改完必须开新会话,否则在失效前缀上反复重放。 (2026-10-07 实测:改规则后单次 miss 仅 15,303 tok / 11.5%,下一步即恢复命中——因改动在 system prompt 靠后段,其前段仍命中;不是全量 miss。)

步骤 5:guard 拦截事件落盘(logs/guard_events.jsonl)

在 merge-guard.py 与 glob-grep-guard.py 各加 _log_event(),在三个分支调用: deny(拦截)/ allow_fp(被拦后原样重发 → 该次拦截不成立,即误报判据)/ remind(退化为提醒)。 wrapup.py 增 guard_counts(),按「本轮首步 ts → 现在」统计并输出一行。

坑:① 必须 append-only 且异常静默——日志写不进去绝不能阻断工具调用; ② 该文件与 usage.jsonl 同属不可再生,勿删;③ 事件时间戳与 LLM 请求不同源,故统计用时间窗近似。

步骤 6:取消「改规则须开新会话」提醒 + 落点自检改按需输出(2026-10-07 追加)

动因:用户实测质疑「改 rule 的 miss 不可避免、当场就生效,新开对话省不掉」——结论成立。

  • posttooluse-guard/skill-edit-guard.py:_rule_warning() 停用(docstring 已标),改为 _log_rule_change() 落 logs/guard_events.jsonl(action="rule_change")——不再向上下文注入任何文本。
  • 全局规则 rule-agent-efficiency.md:落点自检由「无问题也固定输出一句」改为有结论才写。
  • 同步更正:docs/HANDOFF.md 红线、docs/BASELINE.md §2.1(新增已证伪结论 #13)、 skills/ops-execution-efficiency/SKILL.md 红线 #8。

证据(2026-10-07 复测更正):Trae 在同一会话内回注 "This user rule has been updated" → 改动同会话即生效。 早期把「改规则后 miss 15,303」读作前缀失效是误判——那是新增内容的 miss(该步 prompt 同时增长 15,715 tok)。

步骤 7:各类改动的 miss 实测与 Hook 注入的最终形态(2026-10-07)

改动对象 实测结果
skill description(加一个逗号) 下一步 miss 299 tok / 0.1% —— 无前缀失效
user_rules 各步 0.1~1.6%,无前缀失效
hook 脚本 / skill 正文 / 项目文档 同上
hooks.json 不进上下文;须重启 Trae 才生效

结论:Trae 对更新后的规则 / 技能索引按增量注入,不替换 system prompt → 不打断前缀。 旧结论「改 description → 全量重建 36.5 万 tok」存疑(2026-10-01 那次无对照,疑为其它原因)。

Hook 注入的最终形态(用户 2026-10-07 决策:保留"重启"这条,其余按实测定): - hooks.json 改动 → 保留一行注入(「须重启 Trae」是真事实,与 miss 无关); - user_rules / skill description / commands → 只落盘、不注入。

步骤 8:收尾改表格 + miss 阈值上调 + 落点自检改按容量触发 + guard_report(2026-10-07 追加)

  • cost_audit/wrapup.py:五项输出改 Markdown 表格;miss 检测只看中段步(轮首不计)、 阈值 5000 → 20000。依据:历史 16,063 个中段步 miss 分布 P50=743 / P90=4,271 / P95=7,664 / P99=21,349 —— 5000 会命中 7.6%(过频),20000 命中约 1.0%。
  • 新增 cost_audit/guard_report.py:按 hook × 判据 聚合 guard_events.jsonl, 给 误报率 = 原样重放 ÷ (拦截 + 原样重放),供"是否放宽某类强制规则"取数。
  • 全局规则:落点自检触发条件由「改过 HANDOFF」改为「HANDOFF ≥10 KB 或 ≥5 轮」 (用户纠正:HANDOFF 是「当前状态」,有工作推进就会改它,改一次就自检不切题;要的是「该落没落」的提醒)。

实测:改后 wrapup.py 输出表格正常;guard_report.py 输出 skill-edit-guard/R × 6、merge-guard/C remind × 1。

同日再调(用户第 4 轮反馈):① 表格转置(项横排、一行值,省空间);②「合并」拆成 合并(已合并步数 + 占比 + 主要工具 TOP3,全自动)与未合并(组/步 + 语义类分列作原因线索, 是否真依赖需人工判读——req_shape 不记参数,机器判不了);③ miss 检测无命中则整段不输出; ④ HANDOFF 未达 10 KB / 未满 5 轮时不提落点自检。

同日再调(用户第 5 轮反馈):输出去单位、去解释(时间墙只留数字、步数不带"步"); 「合并」改为工具简称分列且只列非零(改7跑2,映射:增=Write / 删=DeleteFile / 改=SearchReplace / 查=Read·Glob·Grep·LS·SearchCodebase·WebSearch·WebFetch / 跑=RunCommand / 其它=编排·计划·等待·交互·展示); 「费用」改为 命中%/未命中%/输出%/思考占输出%,步数;「guard」改为 拒/放/提 三个数。

同日再调(用户第 6 轮反馈):① 费用列补回金额:本轮金额/本会话金额(元、3 位小数); ② 新增规则条款「备份 / 台账 / SOP 同步状态默认不写,仅在未备份或未同步时才提醒」 (此前每轮末尾那句「备份…均已同步」是我自发写的,非任何规则或 Hook 要求)。

同日再调(用户第 7 轮反馈;其它项目复现):落点自检仍被输出 → 根因是规则内两条条款自相矛盾: 「未触发不输出」(落点自检节)与「稳定层同步(条件必答)」——后者无条件触发,把前者顶掉。 改为共用触发条件(HANDOFF ≥10 KB 或 ≥5 轮),并明确「未触发时两者完全不提,而非提一句未触发」。

6. 验证

cd ~/Documents/services/token_proxy
python3 -c "import ast;[ast.parse(open(f,encoding='utf-8').read()) for f in ['merge-guard/merge-guard.py','cost_audit/wrapup.py']];print('syntax OK')"
echo '{"session_id":"tJ1","tool_name":"AskUserQuestion","tool_input":{}}' | python3 merge-guard/merge-guard.py   # 预期 deny JSON
echo '{"session_id":"tJ1","tool_name":"AskUserQuestion","tool_input":{}}' | python3 merge-guard/merge-guard.py   # 预期空(指纹放行)
python3 cost_audit/wrapup.py    # 预期四行:时间墙 / 本轮步数+合并 / 本会话累计 / miss 检测

实测结果(2026-10-07):syntax OK;J 首次 deny、原样重发空、普通 Read 空; wrapup.py 输出「时间墙 0.7 分钟 | 本轮 3 步 | 未合并块 0 组/0 步、已合并率 100% | 本会话累计 ¥0.3607 (命中 7% / 未命中 39% / 输出 55%)| miss 检测 1 步:01:20:53 miss=15303(中段)」。

交叉验证:该 3 步与 usage.jsonl 中 turn 1a305368d4ef 的记录条数一致;命中的中段 miss 15303 对应本次改了全局规则之后的首次请求(规则进 system prompt → 前缀失效),归因成立。

7. 回滚

备份 原路径 回滚方式
wrapup-ask-guard-20261007/backup/merge-guard/merge-guard.py.bak.20261007-011928 merge-guard/merge-guard.py 覆盖回原文件;判据 J 随之消失
~/.trae-cn/user_rules/backup/rule-agent-efficiency.md.bak.20261007-011928 ~/.trae-cn/user_rules/rule-agent-efficiency.md 覆盖回原规则,开新会话生效
cp -p wrapup-ask-guard-20261007/backup/merge-guard/merge-guard.py.bak.20261007-011928 merge-guard/merge-guard.py
cp -p ~/.trae-cn/user_rules/backup/rule-agent-efficiency.md.bak.20261007-011928 ~/.trae-cn/user_rules/rule-agent-efficiency.md

新增文件 cost_audit/wrapup.py 无备份(全新),回滚时直接删除即可(删除前确认无其它消费者)。

回滚后验证:AskUserQuestion 不再被拦(离线用例返回空)、规则文件恢复原文。

触发回滚的判据:① 判据 J 误拦导致任务被打断且原样重发无法继续;② wrapup.py 报错或数字与 usage.jsonl 对不上;③ 规则改动引发意外行为。任一达标即回滚。

8. 风险说明

风险 缓解
判据 J 误拦(用户显式要求弹窗) 指纹放行:原样重发即通过;每会话另有 DENY_MAX=3 上限,超出后退化为一次提醒
wrapup.py 与 --since/--until 口径混淆 脚本内注明:时间墙/步数用 turn_key,费用用 conv_key;--since/--until 仅作覆盖开关
依赖 proxy.py 的 turn_key 语义 已核对 classify_request 注释与 2026-10-07 实测数据(turn 内 turn_key 稳定、跨 turn 变化)
改全局规则 → 前缀失效约 0.35 元/次 收尾后立即开新会话,不在失效前缀上继续跑
阈值 5000 过于灵敏 / 过于迟钝 阈值在 rule-agent-efficiency.md 与 wrapup.py --miss-threshold 各一处,观察后调整

附录:交付物清单

文件 说明
cost_audit/wrapup.py 收尾五项取数(新增)
logs/guard_events.jsonl guard 拦截/放行事件流水(append-only,勿删)
glob-grep-guard/glob-grep-guard.py 同步加 _log_event()(改)
merge-guard/merge-guard.py 增判据 J(改)
~/.trae-cn/user_rules/rule-agent-efficiency.md 收尾四项 + 不弹窗(改)
wrapup-ask-guard-20261007/backup/ 备份 + README.md 台账

来源:/home/rain/Documents/services/token_proxy/wrapup-ask-guard-20261007/SOP.md | 最后更新:2026-10-07 04:43(由 build/sync.py 同步,请勿在站点侧修改)