4.5 KiB
Agent Note: 停止将 token 流镜像为 agent 事件
Status: implemented
English | 中文
问题
agent loop(智能体循环)将模型的每个 token delta 同时记录为持久的 assistant/chunk 会话事件,并发射一个携带相同数据的并行实时 agent/stream-chunk Cordis 事件。在 packages/core/agent-loop/src/loop.ts 中,二者仅相隔一行:
const chunkEvent = session.append('assistant/chunk', { turn, step, chunk })
chunkSeqs.push(chunkEvent.seq)
ctx.emit('agent/stream-chunk', agent, turn, step, chunk) // ← the mirror
- 持久事件:
assistant/chunk: { turn, step, chunk }。 - 实时发射:
agent/stream-chunk(agent, turn, step, chunk)——相同的StreamChunk,相同的turn/step。
实时发射相比会话事件唯一多出的东西是实时的 Agent 句柄,而唯一的消费方直接丢弃了它(其处理函数签名为 (_agent, _turn, _step, chunk))。
这与移除边界镜像为轮次/步骤边界消除的重复相同:消费者面对同一持久事实的两个事实来源,每次变更都必须同时触及两者。该 Agent Note(agent 决策记录)没有把 chunk 流一并纳入,而是推迟处理(“assistant/chunk 持久化仍承载关键约束,所以以后可以将 chunk 流作为镜像评估,但那是一项独立决策”)。本 Agent Note 就是那项独立决策。
推迟所依赖的前提已经明确:chunk 持久化是权威的,且将保留。停止持久化 chunk、仅保留瞬态实时流事件的提案已被否决——高保真回放、部分失败的流以及快照回放都依赖持久化的 assistant/chunk 序列。因此 session/event 上的 assistant/chunk 是持久的、承重的 token 流,而 agent/stream-chunk 是它的纯冗余镜像。
决策
从 agent 事件分类体系中移除 agent/stream-chunk。token 流通过 session/event 以 assistant/chunk 的形式读取——持久化与回放已经使用的正是同一个序列。session/event 是唯一的实时 transcript(文本记录)流(assistant chunk、轮次/步骤边界、工具活动、todo)。
消费方。 唯一重要的生产消费方——ACP(Agent Client Protocol)桥接(dsh-acp,面向编辑器的真实流式输出接口)——已经从 session/event 渲染 assistant/chunk,从未使用 agent/stream-chunk,因此不受影响。stdio UI(dsh-ui-stdio,一个一次性的测试 REPL)是唯一的实时消费方;它在边界迁移时已经有了 session/event 监听器,因此其 chunk 渲染被折叠进该监听器作为 assistant/chunk 分支。合并为一个监听器还消除了一个潜在隐患:inReasoning dim-SGR 标志此前在两个独立监听器(agent/stream-chunk 和 session/event)之间共享,chunk 与边界在该标志上竞争时没有确定的顺序;单一监听器按追加顺序处理,使交错变为确定性的。
范围
移除:agent/stream-chunk。
未触及:
assistant/chunk(持久 session 事件)——权威 token 流,原样保留。本 Agent Note 移除的是实时镜像,而非持久化(移除持久化的提案已单独遭到拒绝——见上文)。agent/steering——本决策未触及(它是控制信号,不是 token 流)。其持久孪生事件是steering/message,镜像发射由其自身的后续 Agent Note 移除:移除agent/steering镜像发射。agent/status、agent/error、agent/created/agent/disposed、agent/queued、agent/session-start——生命周期/控制事件,不是 transcript 数据,也没有持久副本。
曾考虑的替代方案
移除持久化、仅保留瞬态实时流——反向裁剪,已被单独否决:高保真回放、部分失败的流以及快照回放都依赖持久化的 assistant/chunk 序列。在此前提确定后,实时发射才是配对中冗余的那一半。
后果
插件不能再从 Agent 优先事件观察 token 增量。它需要订阅 session/event、过滤 assistant/chunk,并在需要时通过 ctx.agents.get(session.id) 直接查找对应的实时 handle。没有生产消费者需要在 chunk 时刻取得实时 Agent;这与移除边界镜像所作的取舍相同,均可接受。