跳转至

SOP:req_shape 命令特征埋点(C2)+ 「重启不中断输出流」定死 + HANDOFF 过时表述修正

主题目录:task-side-metric-20261008/|执行时间:2026-10-08|类型:C 类变更(改 proxy.py + 全局规则 + 项目档案)


1. 背景

三件事同轮处理,前两件有共同病因——结论没有被实测校验,却长期当事实用:

  1. C2:收尾自查的「任务侧效率」行无机检。规则早已注明:现有日志(usage / req_shape / tool_timing)均不含命令文本(req_shape.shape 只有工具名),代理侧取不到「本轮是否跑部署器 / 远程批量」的客观判据 → 该口径「有机检之名、无验收之实」。要升机检,须先在 proxy.py 记命令特征。
  2. 「重启代理会打断在途流式回答」是错的。该结论写进了 HANDOFF §4 红线、规则《攒批重启》理由①、BASELINE.md §3 命令速查,但从未实测。用户 2026-10-08 实测推翻,并指出「影响好多次」。
  3. HANDOFF 两处过时表述:§3.2 首行仍写「写入前置 v4」(已是 v7);§3.1 过账行仍写「总账 1.1 待办型仅 1 项 #32」(#32 已关闭、1.1 已清空)。

2. 目标

  1. req_shape.jsonl 的 shape 新增 cmd 字段:{n, len, remote, deployer, heads},不落命令全文;执行步有值、非执行步为 null。
  2. 「重启不中断输出流」在 HANDOFF / 规则 / BASELINE 三处同步为正确结论,并留痕为已证伪 #14。
  3. HANDOFF 两处过时表述清零。

3. 前置条件

  • proxy.py 改动前必须备份 + 离线验证(HANDOFF §4 红线)。
  • ./venv/bin/python 可用(proxy.py 依赖 yaml);系统 python3 用于只读取数。
  • 重启代理:systemctl --user 托管(run.sh 与 systemd 互斥)。
  • 执行前检查: bash systemctl --user is-active token-proxy # 预期 active test -x ./venv/bin/python && echo venv-ok python3 -m py_compile proxy.py && echo compile-ok

4. 影响范围

项 内容
主机 本机 dell-R620(单机)
组件 proxy.py(重启即生效);全局规则 rule-agent-efficiency.md(每步重放);项目档案 HANDOFF.md / BASELINE.md
是否需停服 需重启代理;实测不会中断在途输出流(本次二次实证:重启后本会话继续正常)
日志影响 req_shape.jsonl 每行 shape 多一个 cmd 键(向后兼容:旧记录无该键,读取端须用 .get)
隐私 只落条数 / 长度 / 两个布尔 / 前 3 个首词,不落命令全文(有 U9 用例断言)

5. 分步操作

步骤 1:备份

TS=$(date +%Y%m%d-%H%M%S)
D=/home/rain/Documents/services/token_proxy/task-side-metric-20261008/backup
mkdir -p "$D/token_proxy/docs" "$D/trae-cn/user_rules"
cp -p docs/HANDOFF.md  "$D/token_proxy/docs/HANDOFF.md.bak.$TS"
cp -p docs/BASELINE.md "$D/token_proxy/docs/BASELINE.md.bak.$TS"
cp -p proxy.py         "$D/token_proxy/proxy.py.bak.$TS"
cp -p ~/.trae-cn/user_rules/rule-agent-efficiency.md "$D/trae-cn/user_rules/rule-agent-efficiency.md.bak.$TS"

步骤 2:改 proxy.py(C2)

三处:

  1. 常量区新增 EXEC_TOOLS / REMOTE_HINTS / DEPLOYER_HINTS / CMD_HEAD_LIMIT;
  2. 新增 _arguments_command_text() 与 _tool_command_features()(只输出特征,不输出原文);
  3. build_shape_fingerprint():默认值加 "cmd": None,在"从尾部向前找最近一次工具调用"处一并取 cmd。

坑:arguments 是 JSON 字符串且可能不是合法 JSON(工具调用被截断等)——解析失败必须返回空串、按"无命令"处理,不得抛错(U8 用例锁死)。 坑:heads 要切 ; / && / || / | / 换行,且去重 + 上限 3(U10 用例锁死)。 隐私:函数内可取原文,但返回值只含聚合量;U9 断言特征 JSON 里不含原文子串。

步骤 3:改全局规则(重启结论)

rule-agent-efficiency.md 的《同一共享服务的多处改动》行:理由由两条改为一条,并显式标注旧理由①已证伪:

理由只剩明确成立的一条:重启清掉内存态(thinking_stages / 缓存探测快照)→ 该会话的空闲保护缺席一次。 旧理由「重启打断在飞的流式请求、客户端须重发」已于 2026-10-08 由用户实测推翻——重启不会中断输出流,禁止再用它当理由。

步骤 4:改项目档案

  • HANDOFF.md §4 红线:「重启代理会打断在途流式回答」→「重启代理不会中断在途输出流(2026-10-08 用户实测,推翻旧结论)」;
  • HANDOFF.md §3.2 删 v4 过时行;§3.1 过账行改为「1.1 待办型已清空」;
  • BASELINE.md:§3 命令速查注释同步;§2.1「已证伪结论」表追加 #14(留痕,不得静默删);§5 修订记录加一行。

步骤 5:离线验证

./venv/bin/python task-side-metric-20261008/offline_verify_cmd_features.py   # 15 项(含 6 个反例)
python3 -m py_compile proxy.py

步骤 6:重启代理

systemctl --user restart token-proxy && echo "[高危] 已重启 token-proxy"
sleep 3
python3 -c "import urllib.request;print(urllib.request.urlopen('http://127.0.0.1:8788/healthz', timeout=5).status)"

实测记录:本次重启后本会话未中断,命令正常返回、对话继续 → 与"不中断输出流"结论一致(第二次实证)。

6. 验证

# 判据 命令 期望
V1 纯函数正反例 ./venv/bin/python task-side-metric-20261008/offline_verify_cmd_features.py 末行 全部通过(15 项)
V2 语法 python3 -m py_compile proxy.py rc=0
V3 服务健康 见步骤 6 的 healthz 200
V4 真机落字段 python3 -c "import json;lines=open('logs/req_shape.jsonl').read().splitlines();s=json.loads(lines[-1])['shape'];print('cmd' in s, list(s.keys()))" True 且 keys 含 cmd
V5 执行步有值 / 非执行步为 null 见下 RunCommand 步 cmd 非空;SearchReplace/Read 步为 null

V5 取数(也是「任务侧效率」的只读消费端):

python3 -c "
import json
lines=open('logs/req_shape.jsonl').read().splitlines()
for line in lines[-5:]:
    s=(json.loads(line).get('shape') or {})
    print('tool=', s.get('tool_names'), '| cmd=', json.dumps(s.get('cmd'), ensure_ascii=False))
"

实测结果(重启前后同工具对比,直接证明新代码生效):

tool_names= ['RunCommand'] | cmd= null                                            ← 重启前(旧代码)
tool_names= ['RunCommand'] | cmd= {"n": 1, "len": 316, "remote": 0, "deployer": 0, "heads": ["echo","systemctl","sleep"]}   ← 重启后
tool_names= ['SearchReplace'] | cmd= null                                         ← 非执行步

幂等复验:V1 连跑两遍,两遍 全部通过。

7. 回滚

TS=20261008-153344
D=/home/rain/Documents/services/token_proxy/task-side-metric-20261008/backup
cp -p "$D/token_proxy/proxy.py.bak.$TS"  /home/rain/Documents/services/token_proxy/proxy.py
cp -p "$D/token_proxy/docs/HANDOFF.md.bak.$TS"  /home/rain/Documents/services/token_proxy/docs/HANDOFF.md
cp -p "$D/token_proxy/docs/BASELINE.md.bak.$TS" /home/rain/Documents/services/token_proxy/docs/BASELINE.md
cp -p "$D/trae-cn/user_rules/rule-agent-efficiency.md.bak.$TS" ~/.trae-cn/user_rules/rule-agent-efficiency.md
systemctl --user restart token-proxy && echo "[高危] 已回滚并重启"

回滚后验证:shape 不再含 cmd 键;HANDOFF §4 红线恢复旧措辞。 注意:req_shape.jsonl 是不可再生日志,回滚不动它——已写入的 cmd 字段保留(新字段向后兼容,无读取端会因此报错)。

8. 风险说明

风险 缓解
改 proxy.py 影响流式转发 改动只在 build_shape_fingerprint 内加一个纯聚合字段,不碰任何转发 / 流式路径;离线 15 项 + 真机 healthz 200 双证
解析 arguments 抛错导致请求失败 全路径 try/except + 结构判定,异常一律返回空串/None(U8 三类反例锁死)
落盘体积膨胀 每步仅增 ~60 字节(5 个聚合字段);实测单条 cmd JSON 约 60~80 字节
泄露命令内容 只落条数 / 长度 / 两个布尔 / 前 3 首词;U9 断言不含原文;heads 也只在首词层面(如 curl,不含 URL)
规则改动打碎前缀缓存 已知代价(常驻规则每步重放);本次为必要修正,改后本会话即生效

9. 追加变更(同日下午):cmd 指标接入 wrapup.py

C2 只做了埋点,消费端仍缺 → 收尾自查的「任务侧效率」依然靠自判。本节把消费端接上(明细见 docs/SOP.md 第五十九节):

项 内容
新增 cost_audit/wrapup.py:task_side_stats()(汇总 cmd)+ should_report_task_side()(机检触发)
输出 仅当本轮有远端 / 部署器命令时追加:命令步 / 命令调用 / 远端步 / 部署器步 / 重复 heads;纯本地轮不输出
规则 《任务侧效率》节表头改为机检可得五字段;旧「条件触发(自判)」废除;「上限须知」同步
备份 wrapup.py.bak.20261008-153826、rule-agent-efficiency.md.bak.20261008-153826
验证 用例 13 项 ×2 全通过;反例端到端(纯本地轮 → 不输出)+ 正例端到端(造 3 行 remote=1 → 输出且数值正确)

仍不可机检(如实):SSH 建连数(无连接数埋点)、精确「可省重复」(cmd 只存首词)。

9.1 首次真机使用即暴露的判据缺陷(已修)

现象:接入后第一次跑收尾自查,本轮明明只有本地命令,却报「部署器步 = 1」。

根因:DEPLOYER_HINTS 原用朴素子串 "deployer" in text 匹配。命中的那条命令是我构造测试数据的 heredoc,内含字面量 "deployer": 0——命令里"提到"关键词 ≠ "跑了"该类命令。

修复:新增 _hint_hit(),改为按 token / 路径段边界匹配(空白切 token、/ 切段,任一段以关键词开头才算命中);REMOTE_HINTS 同步去掉尾空格(与段匹配冲突,曾致 3 项用例失败)。

对照证据(真机,同形态命令、修复前后):

时间 命令形态 旧代码 新代码
15:39:47 heredoc 内含 "deployer": 0 deployer=1 ❌ 误报 —
15:42:36 python3 -c "print({'deployer': 0, 'remote': 1})" — deployer=0, remote=0 ✅

用例补强:U12 / U13(字面量不得命中)+ U14 / U15 / U15b(路径段与 token 命中)。

方法论:这正是「判据要过反例」的价值——离线用例第一版没覆盖"命令里提到关键词"这一形态,靠真机首次使用才暴露。

迭代快照:proxy.py.bak.20261008-154154(含 cmd 但未落 _hint_hit 的中间态)。回滚点统一为 proxy.py.bak.20261008-153344(原始无 cmd 版)。

9.2 收尾自查「费用」口径改为当轮(2026-10-08 用户要求)

诉求(原话):「我不想要总计的命中、未命中输出、输出思考占比,我想要的就当轮任务的」。

改法:wrapup.py 费用格——金额仍给「本轮 / 本会话」两个数(对照仍有价值), 但四个百分数(命中 / 未命中 / 输出 / 思考占输出比)一律取本轮;规则《收尾自查》表与说明行同步。

坑(量纲):这四个百分数是金额占比(不是 token 占比),故本轮需按峰谷逐条取价算三段金额; 首版改动曾把 token 数(cur_hit)传给 cur_share,量纲错、会得出无意义百分数,已修正并加注释。

验证(异源对照,完全一致):

来源 结果
wrapup.py 输出 0.279/2.748 | 52%/10%/38%/70%,129
独立复算(自写脚本按 IDLE_SECONDS=900 切本轮) 步=11 hit=3,605,632 miss=14,048 out=13,333 reason=9,371 思考占输出=70%
手验金额占比 命中 0.1442 / 未命中 0.0281 / 输出 0.1067 = 0.279 → 52% / 10% / 38% ✓

幂等:两遍输出一致。备份:wrapup.py.bak.20261008-154745。

9.3 「命中 / 未命中」改为输入侧独立 100%(2026-10-08 用户要求,紧接 9.2)

诉求(原话):「我要的是命中/未命中 这个合起来是 100%,这个是输入侧的,不需要你完全统计…… 你现在的统计是将输出侧的也加进来算了」。

根因(9.2 埋下的):9.2 只把口径从「本会话」改到「本轮」,分母没动——仍是总成本(命中+未命中+输出), 于是命中+未命中+输出 = 100%,输出被折进了「输入侧」的 100% 里。用户看到的 53%/9%/38%/63% 中, 38% 是输出占总成本的比,与前两项不同分母,所以看起来「命中+未命中 ≠ 100%」。

改法(wrapup.py 费用格): - 命中% / 未命中% 分母收窄为输入侧金额(cur_cost_hit + cur_cost_miss)→ 两项合计 100%; - 去掉原「输出%」(不再折进同一分母); - 保留「思考占输出比」(输出侧 reasoning_tokens / completion_tokens,与输入侧不同分母)。 - 规则《收尾自查》四处同步:费用行、形态示例行、形态说明行、B 组异常判据 +「输入侧占比 + 未命中归因」行。

验证(异源复算 + 幂等,完全一致):同刻同数据 —— wrapup.py 输出 0.194/0.352 | 42%/58%/69%,15; 独立脚本按同一 active_tail 复算 命中42% 未命中58% 合计100% 思考占输出69%,逐项一致;两遍输出一致。42+58=100 ✓。

备份:wrapup.py.bak.20261008-155628(同批含规则 / SOP)。

10. 追加变更(当晚):HANDOFF 落点核验从答复撤出、改挂写前拦截

动因(2026-10-08 用户裁定):原「每轮带出」把落点核验打进给用户的收尾答复,用户原话: 「我想的是你自己闭环,不需要我参与,这是写给你看的」——该行既不占用户版面、也不该由用户裁定; 同时不能因此留下漏洞(此前用户已纠过:旧触发点挂在「本轮动 HANDOFF」上,越线时靠「不写」即可绕过核验)。

改法: 1. cost_audit/wrapup.py:删除每轮输出段;随之删除唯二调用点所在的 handoff_path() / handoff_status() / 常量 HANDOFF_MAX_UP / 未使用的 from pathlib import Path(净减 1 285 B)。 2. handoff-landing-guard/handoff-landing-guard.py:新增 _landing_hint() —— 在判定要拦时用 importlib 加载 handoff_precheck 并 scan(),把「该落候选 N 行 / 合计 B」并进 deny 理由。异常 / 取不到 → 返回空串 (宁漏不误,绝不因此放行或崩溃)。 3. skill writing-handoff:验收行两处改为「落点结论不进答复」。

验证:

项 命令 结果
既有离线用例 python3 task-side-metric-20261008/offline_verify_handoff_gate.py 11 通过 / 0 失败
正例(探针 20.6 KB + 净增 400 B) printf '{…}' \| python3 handoff-landing-guard/handoff-landing-guard.py deny,理由含「落点核验:候选 360 行 / 合计 20700 B」
幂等复验(换 session 重跑) 同上 输出逐字一致
坏样本(脚本复制到 /tmp,同目录无 cost_audit) 同上 仍 deny,理由不含「落点核验」→ 静默降级、不误放行
死代码 / 语法 grep -n 'handoff_status\|handoff_path\|HANDOFF_MAX_UP\|pathlib' cost_audit/wrapup.py;py_compile 无命中;编译通过
答复侧 python3 cost_audit/wrapup.py 输出不再有「HANDOFF 落点」行

残留(已知):_landing_hint 每次被拦都会重扫一遍 HANDOFF(~9 KB,量级可忽略),未做缓存。 「该写却完全没写」这一情形无机器兜底(Trae 无「最终答复」事件)。

备份:TS 20261008-222232(两个脚本)。台账:task-side-metric-20261008/backup/README.md。

补充(同日 22:34):第三种 —— 写时顺带报轮次(用户裁定「加第三种」)

reason is None(放行)分支新增 _offer_ctx():输出 allow + additionalContext,内容 「【HANDOFF 写前】距上次更新已 N 轮 | 当前 X KB | 落点核验:候选 K 行」;越线时 _turn_txt() 并入 deny 理由。 新增 _epoch() / _turn_txt()(倒读 req_shape.jsonl 尾部 256 KB、遇 ts <= mtime 即停, 按不同 turn 值计数)/ _emit_allow_ctx();去重键 ctx_key = 轮次文案 + 水位档(512 B), 写在既有 state/<session> 里。

用例 期望 实测
健康水位 8 KB + 净增 400 B allow + additionalContext 含轮次 ✅ 「距上次更新已 36 轮 | 当前 8.0 KB | 候选 0 行」
同 session 第二次同场景 空输出(去重) ✅ 输出为空
偏高 9.5 KB + 净增 deny + 含轮次 ✅ 「当前 9.5 KB 已在偏高水位…距上次更新已 36 轮」
坏样本:脚本隔离到 /tmp(无 logs/、无 cost_audit/) 仍放行、不崩 ✅ 文案自动省去轮次与候选,只剩「当前 8.0 KB」
既有 11 项用例 全过 ✅ 11 / 0

边界:轮次是提示值(turn 客户端生成、可能复用),不参与任何拦截判定;「该写却完全没写」仍无机器兜底。

11. 追加变更(当晚 23:11):拦截额度 999 → 3(每个操作拦三次就放行)

动因:用户拿另一会话(6ac59a3fc1804934)的截图来问「是不是硬拦没放行导致任务没完成」。 查底层日志(logs/guard_events.jsonl):该会话 22:47–22:59 被拦 45 次、同一操作被拦 4 遍, 最后一次工具调用停在 22:59:27 → 任务确实被拦停。根因是上午「上强度」把 DENY_MAX 改成 999 (实质无上限),且 deny_repeat 分支不读该额度。

改动:DENY_MAX 999 → 3;denied list → dict(存每指纹已拦次数,旧格式兼容读作 1); deny_repeat 加额度判定,满 3 次即 allow_fp 放行。

验证:连发 6 次同操作 → deny → deny_repeat → deny_repeat → 放行 ×3(日志一致); offline_verify_strict_guards.py 31/0;verify_allowfp.py 5/0; offline_verify_guard_i.py 5/1(改前改后一致,既有失败)。

备份:TS 20261008-231109。明细:docs/SOP.md 第七十五节。


来源:/home/rain/Documents/services/token_proxy/task-side-metric-20261008/SOP.md | 最后更新:2026-10-08 23:12(由 build/sync.py 同步,请勿在站点侧修改)