4.3 KiB
Agent Note: 将 Code Mode 子分发结果的持久化副本纳入 spill 机制
Status: implemented
English | 中文
范围:Code Mode UI 堆叠 PR(Pull Request)链的第四个 PR,即用既有的 spill 机制为
tool/code-dispatch事件的内容施加边界。宿主侧基础 Agent Note当初有意接受了不设上限的日志,并指明本 PR 就是兑现点;实时并行 Agent Note敲定了本次整形所挂接的事件对。
问题
自携带完整内容的分发日志落地以来,读取大文件的 run_code 程序过去会把完整的渲染文本写进会话日志,不设上限、位于 spill 策略之外;而原生结果在记录之前就已被限制在 maxInlineBytes 以内。这种不对称的方向完全反了:子调用(本就为批量数据工作而设计)恰恰是最可能携带巨大结果的调用,而每个这样的轮次都会让 JSONL 增长数 MB。
决策
在注册表上增设一个日志整形 waterfall(瀑布式事件),spill 策略作为其第一个监听器。
- seam:
tools/code-dispatch-log,一个按作用域过滤的 waterfall,由桥接层在追加tool/code-dispatch之前对每个已结算的子分发运行(经由注册表的私有shapeDispatchLog调用器——作为能力闭包经RunCodeBridgeOptions交给桥接层;waterfall 才是公开 seam,调用器绝不扩大服务接口。故障被兜住:监听器抛出异常时回退到未整形的内容,并用可处理任意抛出值的错误格式化,确保恶意抛出值无法逃出兜底)。载荷(CodeDispatchLog)携带外层执行、提升出来的agent路由键、子调用标识与默认内容——即原生tool/result所载的渲染后结果投影(程序本身收到的是结构化value)。可整形的只有持久副本;模型两者都看不到。整形作为被跟踪的旁路工作在程序路径之外运行,但有界:待处理日志任务超过maxParallelSubCalls时有序提交通道会暂停,因此慢速 spill 后端会对整个 run 施加背压,而不是无限累积待完成 I/O;run 结算仍会在开放轮次内排空全部任务。 - 策略:
dsh-spill-policy在新 seam 上注册第二个分支,与其面向模型的分支共用一模一样的替换流水线(同样的maxInlineBytes上限、同样的预览 + 定位符 + 不超上限不变式、同样的尽力而为回退),产物以dispatch为标签,记在子调用 id 名下。UI 与回放通过 spill 产物读取全文,方式与读取被 spill 的原生结果完全相同,因此与原生同等保真的渲染在施加边界之后依然成立。 - 一处有意的不对称:面向模型的分支跳过
read(避免read → spill → read again循环);分发日志分支则连read子调用也施加边界:日志副本不是模型上下文,该循环因此不可能发生,而read恰恰是会产生巨大日志的那个工具。
曾考虑的替代方案
在桥接层内部用普通上限施加边界(不做 spill)。 否决:没有定位符的截断会丢失回放与 UI 可能需要的数据,还会重新引入本堆叠 PR 链已经移除的「截断摘要」降级渲染路径。
直接在桥接层内做 spill(从 code-mode.ts 调用 ctx.spillStore)。 否决:注册表会因此对 spill 能力产生硬依赖;waterfall 则把策略留在所有其他 spill 决策所在的地方,既可组合也可禁用(省略 maxInlineBytes 依然意味着真正的 no-op)。
让嵌套调用复用 tools/post-execute,而不是新增一个事件。 否决:post-execute 整形的是面向程序的那份结果(嵌套调用有意跳过它,好让程序拿到完整数据);持久副本需要一个属于自己的决策点,位于程序取得其值之后。
后果
对 Code Mode 轮次而言,会话日志重新有了边界:README 中关于分发日志不设上限的 「已知限制」条目已经解决,现在指向本篇。携带超大分发内容的旧日志仍可回放(事件形状未变;只有今后的追加才会变小)。Web UI 经由与原生完全相同的路径,把被 spill 的子调用输出渲染为预览 + 定位符文本,没有任何特殊处理。