Repository navigation
Bound Workbench producer / completed-envelope render history end to end #681
Description
Priority and scope
P2 — memory and response-size reliability. Follow-up to #666 and #600. Coordinate with production streaming #686, but keep streaming latency and retained-history bounds as separate acceptance criteria.
Audited commit: b435f7b9179271cbff81d3e40d14ee342cbd65dd.
Origin: owner-requested deep-dive, 2026-09-06. Expanded with the complete producer/final/history findings.
Evidence level: source inspection establishes that the replay limit does not bound all retained invocation data. The reviewer has not reproduced an out-of-memory failure or claimed that every renderer has no other internal limit.
Verified gap
The 256-render-event bound applies to one live replay buffer, not the whole invocation lifecycle:
- route-invocation-production.ts:
renderCompiled()pushes render events into an array and returns that collection. - route-invocation-child.ts: the completed child result carries its event collection through IPC.
- route-invocation-service.ts: successful invocations retain
events: child.events; completed history and the final stream envelope retain that result.#publishStream()only evicts live render messages.
The final envelope can therefore restore the history that replay eviction discarded. Limiting the number of completed invocations does not establish a bound on retained events or bytes inside each invocation.
Required behavior
Use one documented retention policy across producer collection, child IPC, live replay, completed invocations, history reads, final stream envelopes, and browser replay. Keep the latest/final document separately from disposable intermediate history. Preserve outcome, correlation, and an explicit truncation indication.
Address retained bytes as well as event counts. Handle oversized final documents separately and explicitly; do not silently truncate valid results or introduce an unjustified universal plugin-result limit merely to cap intermediate history.
Acceptance
- Produce substantially more than 256 events through an actual compiled child, not only a mocked
renderChildcallback. - Assert producer-retained events and completed
invocation.eventsremain bounded. - Assert GET/read/history and final-stream responses do not rehydrate the full discarded history.
- Preserve a single truthful truncation indication and the correct final document/outcome/correlation.
- Exercise retained-byte behavior with large intermediate snapshots, not only many tiny events.
- Reconnect after eviction and verify a coherent latest document or an explicit replay limitation.
- Cancel after the shell event has been evicted and after a long progress sequence; retain intelligible partial evidence and correct cancellation status.
- Error, cancellation, and completion paths release disposable producer/IPC buffers and preserve process-tree cleanup.
- Preserve existing live replay behavior; a replay-only test is not sufficient proof of end-to-end bounds.
Test owners
packages/agent-bundle/tests/route-invocation-service.test.tspackages/agent-bundle/tests/route-invocation-dev-server.test.tspackages/workbench/tests/invocation-model.test.tspackages/workbench/tests/audiobook-curator.acceptance.e2e.test.ts
Non-goals
No durable trace database, remote telemetry, new orchestration layer, or changes to plugin business behavior. Executable selection is #680; production Flight delivery is #686.
Activity
ScriptedAlchemy commentedon Sep 6, 2026
Coordination with #686 (streaming) — #681 owns retention, #686 owns Flight delivery.
Retention policy lands in one place:
RENDER_EVENT_RETENTION+retainRenderEventinpackages/agent-bundle/src/dev/routes/route-invocation-result.ts(re-exported fromcontracts/invocations.tsfor the Workbench). Newest 256 render events, ≤ 1 MiB of JSON, oldest-first eviction, newest event never evicted. Applied by the render child (renderCompiled), the child's IPC reply, the service's live-replay window, the completed envelope (events+evictedEvents), and the Workbench live buffer. The latest document is tracked outside the window so cancellation keeps it after the shell was evicted.Ask for #686: when the MCP/event worker starts forwarding Flight chunks, keep feeding
publishRender/the dispatcher one event at a time and do not add a second buffer — the service already bounds what it retains, and#streaminroute-invocation-routes.tsnow paces the replay by socket drain (the live-consumer queue limits still apply to live messages). Slow-consumer policy for live delivery stays #686's call; retention is settled here.ScriptedAlchemy commentedon Sep 6, 2026
Coordination from #686 / #718: the generated MCP/event Flight workers now post
chunk/end/errorinstead of one bufferedcomplete. On the Workbench side nothing new is retained — chunks feed the existing dispatcher and events still go throughpublishRenderone at a time, soretainRenderEvent(#699) bounds them unchanged whichever PR lands first (no overlapping hunks inroute-invocation-production.ts; #718 only dropscompletefrom the worker message set and adds a streamcancelhook). Slow live consumers stay bounded bycreateFlightDemandand the#streamdrain pacing; no new queue.ScriptedAlchemy commentedon Sep 6, 2026
Duplicate resolved: #699 closed, #715 survives as the single retention policy (
route-invocation-render-history.ts: newest 256 events and ≤ 2 MiB serialized, pinned newest event and newest document-bearing event; applied to the child/IPC producer, service replay, completed/cancelled/failed envelopes, GET/read, history,final, and the Workbench live window). #699's unique tests — real compiled-child reconnect after the shell was evicted and cancel after eviction, history-ring eviction, pinned-newest module test, full-window HTTP replay — were ported into #715. #718 (#686 streaming) was told how to resolve against it.fix(workbench): bound producer / completed-envelope render history en…