4.9 KiB
Agent Note: 注入内容逐字投影,去除 XML 封套
Status: implemented
English | 中文
问题
两类注入的会话内容在渲染进模型 transcript(文本记录)时被包在 XML 封套里:steering/message 包成 <steering source="…">…</steering>,context/message 包成 <context source="…">…</context>(后者有一个 'raw' 退出选项可跳过封套)。这些封套意在告诉模型「这是注入内容,不是用户在说话」。
两个问题:
- 没有模型在这些标签上训练过。
<steering>和<context>是任何模型都未被教会去读的任意标记,因此这层框架只是徒增 token 而没有可靠效果,还可能起反作用——已录制的 transcript 显示,模型会把<steering>指令当成第三方元数据而拒绝服从,只回答原始提示词。 - 会话表层是承载框架的错误层次。 表层的职责是把持久日志投影为模型 transcript;决定内容如何措辞并不是它的事。想要特定框架的调用方可以在注入前自行格式化内容——唯一的重度生产方(
workspace-context)本就这样做,它自带完整的<system-reminder>框架,并用envelope: 'raw'退出<context>封套。剩下的标签机制(ContextEnvelope类型,以及贯穿InjectOptions、HookContext、context/message事件和 agent loop(智能体循环)的envelope字段)所服务的区分,本应归属调用方。
决策
注入的会话内容逐字投影,框架由调用方自行负责。deriveEventMessage 通过一个共享分支渲染 user/message、context/message 和 steering/message,都返回 { role: 'user', content: event.data.content };它们的内容块原样到达模型。context/message 的 source/meta 和 steering/message 的 turn 保留在持久事件日志中,但不渲染。
ContextEnvelope 类型和所有 envelope 字段都被移除——包括 SessionEventMap 中的 context/message、InjectOptions、HookContext,以及 dsh-agent-loop 中 inject()/additionalContexts 的相关管线。workspace-context 不再请求 'raw';它自带框架的内容渲染方式不变。renderTagged/renderContextEnvelope 辅助函数被删除。context/message.meta 仍携带持久的、对模型隐藏的 JSON 状态。
封套曾携带的 source 来源信息并未丢失——它仍保留在持久事件上;只是不再渲染进 transcript。
权衡的替代方案
- 保留
<context>封套,只对 steering(中途引导)去封套 —— 会为一个没有模型会读的框架位保留ContextEnvelope/envelope机制,并保留主要生产方本就退出的那种不一致。 - 仅对插件来源的内容保留 envelope 字段 —— 会按
source.kind把一条投影拆成两条,却没有观察到任何收益;插件引导 agent 时(钩子桥接器的轮次续行原因)同样希望指令被遵从,而不是被贴标签。 - 把去封套的逻辑移入适配器 —— 规范投影就是模型可见契约(「模型可见 ⟺ 已记录」);让各适配器在框架上各行其是,会使派生的 transcript 依赖于适配器。调用方确实想要的框架应放进调用方自己的内容里,而不是适配器。
结果
- 中途引导与注入的上下文以与普通用户提示词相同的权重到达模型。
- transcript 不再区分注入内容与用户消息;需要这一区分的消费方读取持久事件日志,其中事件类型、
source和meta完整保留。 hook-{cc,codex}-stop-continueACP(Agent Client Protocol)快照已重新录制:旧录制捕获的是模型把 steering 当作第三方元数据而拒绝服从,正是本次修复针对的失败模式。- 内容块词汇表 Agent Note 中关于带标签封套的条款已修订为指向本文。
推迟事项
workspace-context 已经自行为内容加框架:它把一个完整的 <system-reminder>…</system-reminder> 块作为消息内容发出,而不依赖表层封套。这种调用方自有的模式才是应保留的——表层逐字透传内容,任何框架都住在生产方自己的内容里。
曾经存在两条框架路径——调用方自行加框架(workspace-context 的 <system-reminder>),以及表层封套(deriveEventMessage 加上的 <context>/<steering>)。本次变更移除了后者,只留下调用方自有的框架。如果未来又需要带标签的框架,应由事件的 meta map(生产方附加、对模型隐藏的元数据字段)来统一它,交给专门的渲染器或适配器消费,而不是在 deriveEventMessage 中重新硬编码标签。生产方在 meta 中声明所需的框架,由一个渲染器统一施加;会话表层的投影始终保持逐字透传。