Skip to content

ADR 0042:压低首轮 token——固定前缀瘦身与少走回合 ​

状态:Proposed

关联:建立在 ADR 0041(段组装器、场景段在系统提示词末尾、消息头)之上,0041 落地后开工。

背景 ​

2026-10-28 测试群一轮真实 trace(context_budget)里,单次模型调用约 15k 输入 token,其中每次都要带的固定部分约 8.9k:

部分token内容
工具 schema2.8k24 个常驻工具的完整参数定义(群聊里也含 grep / read_file / list_dir / bash 等开发工具)
bootstrap2.4kworkspace AGENTS.md(约 4k 字)、SOUL.md(约 2k 字)、TOOLS.md
系统规则1.6k安全、编排、工作流、风格
技能目录1.3k全部技能描述
工具索引1.1k69 个延迟工具逐条一行
输出 schema0.9k结构化回复 JSON schema

同一轮里,一次「踢人」请求走了 21 次模型调用(累计输入 358k):模型找不到合适工具,在群聊里反复 grep 插件源码;每用一个新工具还要 discover → load_tool → 调用三个来回,每个来回都重发完整前缀。

目标:降低首轮(含新会话第一句)的输入 token 与计费,同时不丢能力。

决策 ​

0. 原则 ​

  1. 常驻只放「知道有这个能力」所需的最少信息,细节按需取。
  2. 能力不丢的判据是评测:每一项改动都先跑 eval:agent / eval:chat 基线,改后对比;工具调用成功率与回答质量不下降,且既有任务(踢人、查成员、设提醒、公告、日程)仍能在调用上限内完成,否则不合入。
  3. 授权边界不变:按需加载、自动加载都只能取当前回合已授权投影里的工具(DeferredCapabilityPlan 的 ceiling),不扩大任何权限;工具自身的权限与审批照旧。

1. 跨会话逐字节稳定的前缀(首轮计费的最大杠杆) ​

  • 系统提示词的 system / environment / role_rules / platform 四类段,在同一实例、同一 provider/模型下跨会话、跨群逐字节相同;任何会话相关内容(会话规则、记忆路径、命令前缀等)只能进 scene 段或消息。
  • 在支持缓存断点的 provider 上,于 scene 段之前打断点(ADR 0041 §5 已给出切分与偏移,本 ADR 负责接到 packages/im/ai 的请求组装:wrapSystemForPromptCache 支持两段系统提示词)。
  • 效果:一个新群的第一句话,前面约 8k 也命中缓存,按缓存价计费。
  • 守护:测试断言两个不同会话(群 / 私聊 / 不同群)构建出的前四类段字节相同。

2. 常驻工具按场景分档 ​

场景常驻工具其余
群聊、普通私聊聊天必需:回复相关(表情回应、pick_sticker)、记忆检索、会话信息、discover、load_tool进工具索引,按需加载
主人私聊上述 + 开发工具(文件读写、grep、list_dir、bash 等,按 sandbox 策略)同上
  • 分档由宿主按回合的 origin / 角色决定,写成 deferredTools 的场景默认;alwaysLoadedTools 配置仍可显式追加。
  • 非主人回合里不常驻开发工具:它们仍在工具索引里,但只对有权限的回合可加载(现有权限不变),群聊里模型不会再拿 grep 去翻源码。

3. 发现即调用 ​

  • 模型直接按名字调用一个在已授权延迟目录里、但未加载的工具:运行时自动加载并执行(不再要求先 load_tool),在本会话后续回合保持已加载(受 maxLoadedPerSession 限制)。
  • discover 自动加载最匹配的至多 3 个工具,完整 schema 通过下一次模型请求的 tools 字段提供;结果正文只保留检索摘要与已加载名称,不再重复 schema(#1102)。模型可在同一回合直接调用,无需额外 load_tool。
  • 不在授权目录里的名字照旧报「未知工具」,不泄露其存在。
  • 每用一个新工具省 1–2 个来回。

4. 目录折叠 ​

  • 工具索引:按领域一行(如 群管理:踢人、禁言、头衔、公告…(7)),领域名来自工具所属 skill / 插件分组;细节用 discover。
  • 技能目录:每个技能只留名字 + 一句触发描述(≤ 40 字),完整描述在 load_skill / discover 时给出。
  • 守护:eval:agent 中「需要某延迟工具才能完成」的用例全部通过。

5. bootstrap 按场景注入 ​

  • AGENTS.md / SOUL.md / TOOLS.md 支持小节作用域标注:<!-- scope: master-private -->、<!-- scope: group --> 等,标注作用到下一个同级或更高级标题前;无标注 = 全场景(向后兼容)。
  • 宿主只注入与本回合场景匹配的小节;zhin trace 显示被跳过的小节标题。
  • 这些文件是用户 workspace 的内容:框架只提供机制和文档,不替用户改文件;create-zhin 模板可附带示例标注。

6. 紧凑输出 schema ​

  • 走 prompt 模式结构化输出的 provider(compat.structuredOutput: prompt,如 DeepSeek):完整 JSON Schema 换成「最小示例 + 字段规则」;原生结构化输出的 provider 不受影响(schema 走 API 参数)。
  • 守护:decodeStructuredReply 解析成功率在 eval:chat 上不下降。

7. 少走回合 ​

  • 按场景的迭代上限:群聊 6、私聊 10、主人私聊保持 15(maxIterations 的场景默认,modelHarness 仍可覆盖)。剩 1 次时注入「先用现有结论回复」。
  • 原地打转检测:同一工具族连续 3 次调用、结果无新信息(相同结果摘要、grep 无匹配、重复读同一文件)→ 注入一次提示要求换思路或直接回答;记 zhin trace 事件。
  • 并行调用:编排规则明确「互不依赖的查询放在同一条消息里并行发」;已有 spawn_task 并行规则合并表述,不增加净字数。

8. 度量 ​

  • 新增固定场景的基线测量:群聊首轮(新会话)、群聊第 N 轮、主人私聊带工具回合;记录名义输入 token、缓存读、调用次数。用 eval:chat + context_budget 生成前后对比表,写进 PR。
  • fixed-prompt-cost(#949)预算随本 ADR 下调到实测值 + 余量,防止回涨。

预期 ​

场景现在目标
群聊首轮单次调用(名义)~13–15k~6–7k
群聊首轮实际计费(含缓存)~13–15k~2–3k
带工具回合调用次数5–203–8

数字为估算,以 §8 实测为准。

实现分工(ADR 合入后多 agent 并行,最后一个 PR) ​

工作项内容主要位置
W1 稳定前缀 + 缓存断点§1prompt/segments、packages/im/ai 请求组装
W2 场景分档 + 发现即调用§2、§3deferred-capability-plan.ts、工具执行入口、discover
W3 目录折叠 + 紧凑 schema§4、§6工具索引 / 技能目录渲染、structured-reply
W4 bootstrap 作用域§5workspace prompt 解析、trace
W5 少走回合 + 度量§7、§8agent loop、exploration budget、evals

W2 与 W3 都碰工具目录渲染,分工时按文件划清;其余互不依赖。

待 Fin 确认的默认值 ​

  • 群聊 / 普通私聊常驻工具清单(§2)。
  • 迭代上限:群聊 6、私聊 10、主人私聊 15。
  • 打转阈值:同一工具族 3 次无新信息。
  • discover 自动加载工具的上限:3 个(仍受会话加载上限约束)。
  • 技能一句话描述上限 40 字。

非目标 ​

  • 换模型 / 换 provider 来省钱;改 provider 定价策略。
  • 删减安全规则或硬闸相关提示(只允许合并表述,不减语义)。
  • 改写用户 workspace 里的 AGENTS.md / SOUL.md 内容。

结果 ​

  • 首轮输入与计费显著下降,新会话也能吃到缓存。
  • 群聊里不再出现模型拿开发工具翻源码的长回合。
  • 能力以评测守护:工具仍可发现、可调用,权限与审批边界不变。