Files
deepseek-harness/.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.md
T
Tianyi Cui 4987261d55 feat(spill): bound the durable copy of Code Mode sub-dispatch results
New tools/code-dispatch-log waterfall (run via registry.shapeDispatchLog,
contained — a throwing listener falls back to the unshaped content) lets
listeners reshape the tool/code-dispatch event's content before the
bridge appends it. dsh-spill-policy registers a second arm sharing the
model-facing arm's exact replacement pipeline (same maxInlineBytes cap,
preview + locator, within-cap invariant, best-effort fallbacks), with
artifacts labeled dispatch under the sub-call id. The program's value is
untouched; read sub-calls ARE bounded (a log copy is not model context,
and read produces the biggest logs). Resolves the tools README's
uncapped-dispatch-log Known Limitation.
2026-07-26 09:01:03 +08:00

3.6 KiB

Agent Note: Spilling the durable copy of Code Mode sub-dispatch results

Status: implemented

English | 中文

Scope: the fourth PR of the Code Mode UI stack — bounding the tool/code-dispatch event's content with the existing spill machinery. The host foundation note accepted the unbounded log deliberately and named this PR as the payoff point; the live-parallel note settled the event pair this shaping hooks into.

Problem

Since the full-content dispatch logging landed, a run_code program that reads a large file wrote the complete rendered text into the session log — uncapped and outside spill policy, while native results were bounded to maxInlineBytes before logging. The asymmetry was backwards: sub-calls (built for bulk data work) were precisely the calls most likely to carry huge results, and the JSONL grew by megabytes per such turn.

Decision

A log-shaping waterfall on the registry, and the spill policy as its first listener.

  • Seam: tools/code-dispatch-log — a scope-filtered waterfall the bridge runs (via registry.shapeDispatchLog, contained: a throwing listener falls back to the unshaped content) over each settled sub-dispatch before appending tool/code-dispatch. The payload (CodeDispatchLog) carries the outer execution, the hoisted agent routing key, the sub-call identity, and the default content. Only the durable copy is shapeable — the program already received the complete value across the worker boundary, and the model sees neither.
  • Policy: dsh-spill-policy registers a second arm on the new seam sharing the exact replacement pipeline of its model-facing arm (same maxInlineBytes cap, same preview + locator + within-cap invariant, same best-effort fallbacks), with the artifact labeled dispatch under the sub-call id. UIs and replay read the full text through the spill artifact exactly as they do for spilled native results, so the native-parity rendering story survives bounding.
  • One deliberate asymmetry: the model-facing arm skips read (the read → spill → read again loop); the dispatch-log arm bounds read sub-calls too — a log copy is not model context, so the loop cannot happen, and read is precisely the tool that produces huge logs.

Alternatives considered

Bound inside the bridge with a plain cap (no spill). Rejected: truncation without a locator loses data replay/UIs may need, and re-introduces the "truncated summary" degraded render path the stack removed.

Spill inside the bridge directly (call ctx.spillStore from code-mode.ts). Rejected: the registry would grow a hard dependency on the spill capability; the waterfall keeps the policy where every other spill decision lives, composable and disable-able (omitted maxInlineBytes still means a true no-op).

Reuse tools/post-execute for nested calls instead of a new event. Rejected: post-execute shapes the PROGRAM-facing result (nested calls deliberately skip it so programs get complete data); the durable copy needs its own decision point after the program has its value.

Consequences

The session log is bounded again for Code Mode turns — the README's Known Limitations entry about uncapped dispatch logging is resolved and now points here. Old logs with oversized dispatch content still replay (the event shape is unchanged; only future appends shrink). The web UI renders spilled sub-call output as the preview + locator text through the identical native path, no special casing.