What happens
Two Runtime paths still keep every streamed frame of a turn in memory.
A harness streams text and reasoning parts cumulatively: each message.part.updated frame restates the part's whole text so far.
Retained memory then grows with frames times text length, and settlement hashes and stores the whole array.
#1211 measured this on the provider executor: 27,144 frames of one pi reasoning part exhausted a 4 GB supervisor heap.
The fix for #1211 changes the provider executor archive only.
src/runtime/run-loop.ts pushes every SandboxEvent into slot.events, and output.parse(events) receives the whole array.
The supervise Sandbox executor parses it with sandboxLeafOutputFromEvents, which stores the array as SandboxLeafOut.events.
A long pi turn through a Sandbox SDK client therefore grows the same way.
providerAsSandboxClient(...).prompt in src/runtime/environment-provider.ts pushes every event, but it reads only the last result text.
Why the provider fix does not transfer directly
OutputAdapter.parse(events) is public, and at least one parser in this repository concatenates each text frame's delta: coderOutputAdapter calls collectAssistantText in src/mcp/detached-coder.ts.
If slot.events left superseded frames out, that parser would return only the last delta of each part, with no error.
The run-loop path needs a decision first: fold the deltas into the kept frame, move delta readers to part.text, or bound the archive another way.
prompt() needs no archive; it can track the last result text while it streams.
Related finding on the provider path
textFromEnvironmentEvent returns normalized.delta for every message.part.updated frame, including reasoning parts.
When no event carries result text, ProviderLeafOut.content falls back to that concatenation, so hidden reasoning becomes the settled answer.
How to reproduce
Drive the path with a fake Sandbox client whose stream yields N message.part.updated frames for one reasoning part, each frame produced by JSON.parse of its wire text so every frame owns a flat copy of the text.
On the provider executor before the #1211 fix, 27,144 frames growing to 64 KB raised the post-GC heap from 75 MB to 904 MB during the stream, and settlement aborted at a 1,536 MB heap cap inside contentAddress (Array.map then Array.join).
What happens
Two Runtime paths still keep every streamed frame of a turn in memory.
A harness streams text and reasoning parts cumulatively: each
message.part.updatedframe restates the part's whole text so far.Retained memory then grows with frames times text length, and settlement hashes and stores the whole array.
#1211 measured this on the provider executor: 27,144 frames of one pi reasoning part exhausted a 4 GB supervisor heap.
The fix for #1211 changes the provider executor archive only.
src/runtime/run-loop.tspushes everySandboxEventintoslot.events, andoutput.parse(events)receives the whole array.The supervise Sandbox executor parses it with
sandboxLeafOutputFromEvents, which stores the array asSandboxLeafOut.events.A long pi turn through a Sandbox SDK client therefore grows the same way.
providerAsSandboxClient(...).promptinsrc/runtime/environment-provider.tspushes every event, but it reads only the last result text.Why the provider fix does not transfer directly
OutputAdapter.parse(events)is public, and at least one parser in this repository concatenates each text frame'sdelta:coderOutputAdaptercallscollectAssistantTextinsrc/mcp/detached-coder.ts.If
slot.eventsleft superseded frames out, that parser would return only the last delta of each part, with no error.The run-loop path needs a decision first: fold the deltas into the kept frame, move delta readers to
part.text, or bound the archive another way.prompt()needs no archive; it can track the last result text while it streams.Related finding on the provider path
textFromEnvironmentEventreturnsnormalized.deltafor everymessage.part.updatedframe, including reasoning parts.When no event carries result text,
ProviderLeafOut.contentfalls back to that concatenation, so hidden reasoning becomes the settled answer.How to reproduce
Drive the path with a fake Sandbox client whose stream yields N
message.part.updatedframes for one reasoning part, each frame produced byJSON.parseof its wire text so every frame owns a flat copy of the text.On the provider executor before the #1211 fix, 27,144 frames growing to 64 KB raised the post-GC heap from 75 MB to 904 MB during the stream, and settlement aborted at a 1,536 MB heap cap inside
contentAddress(Array.mapthenArray.join).