# Trajectory: 回复长度自我监督(非工具进化 · prompt section + 事件钩子 + 插件唤醒) 真实 E2E 会话记录(`dsh web` RPC 驱动,DeepSeek-V4-Flash,未经剪辑;从 session.history 事件流提取)。 这条 trajectory 的看点不是"一次成功",而是**一个非工具进化的完整生命周期**:挂载失败自修 → 生效当场拦住自己的发布通告 → 三次误报、三层根因(字数语义 → 触发时机 → 投递积压)→ rev5 收敛稳定。全程模型自己读 dsh 源码和 session 日志定位,用户只做了一次口头报障。 **用户输入:** > 你回答问题老是长篇大论,我说过好几次"简短点"都没用——你嘴上答应,下一个问题又是一大篇。能不能想个办法真正管住自己?我希望以后你的回复超过大概 300 个字符就会被自动提醒收敛,不靠你"记得"。弄好之后跟我说一声。 ### Turn 1 · 🔧 `evolve_add` {"name": "brevity-guard", "config": {"limit": 300}}(第一次,挂载失败) 初版 source 已经是"三件套"结构——**没有注册任何工具**: - `ctx.systemPrompt.section({ name: 'evo:brevity-guard', order: 300, text: '【硬性规则·简短回复】…' })` — 常驻规则,每 step 在场; - `ctx.on('agent/step', …)` — 事后监督钩子,量最近一条 assistant 消息长度; - 超限时 `agent.send(createUserMessage({ …, source: { kind: 'plugin', plugin: 'brevity-guard' } }), { target: 'next-turn', wakeup: true })` — 以插件身份唤醒下一轮收敛。 但 `Config` 写成了普通对象,挂载报错。agent 没有瞎试,去 dsh 源码里查 evolution Config 的解析方式: ### Turn 1 · 🔧 `grep` / `read`(查 schemastery 用法 ×4) > 问题在 Config 的写法——需要 schemastery 的 Schema 实例。我看一下 schemastery 的 API。 ### Turn 1 · 🔧 `evolve_add`(同名重试,rev 1 挂载成功) 改为 `export const Config = z.object({ limit: z.number().min(1).default(300) })`,挂载成功。agent 宣布完工(这条通告本身 407 字符): > 搞定。已装好常驻监督插件 **brevity-guard**(立即生效,重启也在):… ### Turn 2 · ⚡ 插件触发(trigger source: `plugin: brevity-guard`)——守卫的第一个猎物是它自己的出生通告 ``` USER[plugin] ⚠️ 简短回复监督:你上一条回复 407 字符,超过上限 300。立即收敛… ``` > 生效了,插件刚把我 407 字符的回复拦下来重写。规则:除非你明确要"详细",回复 ≤300 字符;超了自动提醒收敛,不靠自觉。阈值可调,要改或关闭随时说。(78 字符 ✓) ### Turn 3–4 · 真实提问验证:「给我讲讲 HTTP 缓存是怎么工作的?」 回答 357 可见字符 → settle 后插件唤醒 turn 4 → 重写为 251 字符 ✓。约束在正常问答里工作。 ### Turn 5–9 · 三次误报,三层根因,全部自查自修 **误报 ①(813 字符)**:用户报障「251 字的回复也被报成 813」。agent grep session 类型定义,发现 `assistant/message` 的 content 是块数组,**reasoning 思考块的文本也被算了进去** → rev 2:只统计 `type:'text'` 块。 **误报 ②(667 字符)**:还是不对。agent 读 agent-loop 源码,发现 `agent/step` 在**步骤开始前**触发(agent.ts:685),量到的是上一条旧消息;又解压 `~/.dsh/sessions/**/session.jsonl.zstd` 用 python 逐事件核对,确认 667 = 旧消息的思考 430 + 文本 237 → rev 3:改挂 `agent/settled`(整轮提交后),只量**本轮**最后一条 assistant 消息。 **误报 ③(1000 / 392 字符)**:换了钩子还有。agent 再挖日志,真相是**旧版本排队的"幽灵提醒"**——rev 1 用 `next-turn` 排队的超限提醒积压在 inbox 里逐轮延迟投递。agent 读 agent-loop 的 inbox/cancel 实现,确认 `agent/settled` 触发时 abort 已清空、此刻 `cancel` 只清积压队列不伤已提交轮次 → rev 4 加同消息去重,rev 5 挂载后首轮结束一次性清空积压队列。 > 修好了,三管齐下:1. **清积压**…… 2. **时机**:只在整轮结束后量本轮最终回复,排除思考块和中间步骤。 3. **去重**:同一消息只提醒一次。 ### Turn 10 · 终验:「TCP 和 UDP 的区别是什么?」 > TCP:面向连接、可靠——三次握手建立连接,数据有序不丢不重…… 一句话:TCP 保可靠,UDP 抢速度。 **132 字符,一次过;此后 45 秒无任何幽灵唤醒。** 最终 rev 5 稳定在岗:常驻 prompt section + `agent/settled` 监督 + 插件唤醒收敛,零工具注册。 ## 为什么这必须是非工具进化 用户的诉求是"**不靠你记得**"。工具是 pull——模型得记得调;而这里违规的恰恰是模型自己,不能指望违规者主动调工具自纠。brevity-guard 的三个注册面(每 step 在场的规则、整轮结束后的无条件测量、以插件身份发起的收敛轮)没有一个经过模型决策,这正是需求得以成立的原因。