SOP:收尾四项固定化 + 禁止提问窗 + miss 检测(2026-10-07)¶
1. 背景¶
用户(2026-10-07)提出三项诉求:
- 收尾要"真实执行时间":现有
selfcheck.py --since/--until是跨轮窗口,会把用户阅读 / 回答 / 暂停的时间算进墙钟;要求改为纯执行时间,并把「合并步数 / 步数 / 时间墙 / 费用」无条件写进收尾, 不再沿用「无异常一句话」的旧形态。 - 不再弹提问窗:需要确认时用正文写出待确认项并结束本轮,等用户回复;不要调用
AskUserQuestion。 - 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同步,请勿在站点侧修改)