fix(tangle): truncate oversized tool output instead of discarding a paid turn - #312
Open
drewstone wants to merge 1 commit into
Open
fix(tangle): truncate oversized tool output instead of discarding a paid turn#312drewstone wants to merge 1 commit into
drewstone wants to merge 1 commit into
Conversation
…aid turn 1.1.9 widened the bound on tool output from the 16 KiB metadata limit to the 1 MiB content limit. That removed the instance and left the class: the producer still serializes each tool value up to 4 MiB, and the consumer bound is 1 MiB across the WHOLE record, so a single 2 MiB fetch still died, and so did two 0.6 MiB fetches together. A 256x mismatch became a 4x one. The mismatch is not the real defect. This validator runs inside the terminal result read, after the live stream has drained and after the usage receipt has been credited, so a throw here cannot prevent the work or the charge. It can only destroy a finished, fully paid turn, which a supervisor then reports as a child that did nothing at all. Any bound that throws on this path is one large page away from doing that again. So oversized tool output is now replaced rather than refused. Each pass cuts the widest `toolInvocations[].result` by the record's measured overflow and leaves a marker naming both byte counts, so a reader can tell a truncated result from a tool that genuinely returned little. The caller still receives the turn, its response text, its usage, and every tool call it made. Only tool output is truncatable. Every other field is identity, accounting, or control material where a silently shortened value would be worse than a refusal, so a record whose overflow is elsewhere still refuses — as does one that breaks the node, depth, or array limits, which trimming a string cannot satisfy. Tests: one added, failing on 1.1.9. It pins a 2 MiB single result, the producer's own 4 MiB per-value maximum, several results that each fit but together do not, the marker's byte counts, that every tool call survives, and that an oversized `response` still refuses. Provider suite 264 pass, typecheck clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #311. That PR removed the instance; this one removes the class.
#311 left a 4× mismatch
It widened tool output from the 16 KiB metadata bound to the 1 MiB content bound. But the producer still serializes each tool value up to 4 MiB, and the consumer bound is 1 MiB across the whole record, not per value. Measured against the shipped 1.1.9:
256× became 4×, and the multi-value case is worse than the single-value case.
The mismatch was never the real defect
This validator runs inside the terminal result read — after the live stream has drained and after the usage receipt has been credited. A throw here cannot prevent the work or the charge. It can only destroy a finished, fully paid turn, which a supervisor then reports as a child that did nothing at all.
That failure mode cost one Discovery Lab 143 of 199 children across 16 pursuits, and its six-stage sourcing graph blocked in 24 of 24 invocations.
Any bound that throws on this path is one large page away from doing it again. Widening it is not a fix; it is a bigger margin before the same outcome.
Change
Oversized tool output is replaced, not refused. Each pass cuts the widest
toolInvocations[].resultby the record's measured overflow and leaves a marker naming both byte counts:The caller still receives the turn, its response text, its usage, and every tool call it made. What it loses is the tail of an oversized result, marked where it was cut, so a reader can tell truncation from a tool that genuinely returned little and can choose to re-fetch.
Only tool output is truncatable. Every other field is identity, accounting, or control material where a silently shortened value would be worse than a refusal — so a record whose overflow is elsewhere still refuses, as does one breaking the node, depth, or array limits, which trimming a string cannot satisfy.
Tests
One added, fails on 1.1.9. It pins: a 2 MiB single result survives; the producer's own 4 MiB per-value maximum survives; several results that each fit but together do not converge rather than the first being destroyed; the marker carries both byte counts; every tool call is still present; and an oversized
responsestill throws.Provider suite 264 pass, typecheck clean. 1.1.9 → 1.1.10.
🤖 Generated with Claude Code