Conversation
… into replayed tool_calls Root cause for NousResearch#83714 (write_file/patch_tool writing literal "...[truncated]" into files, PR NousResearch#83752's guard is the safety net, not the fix): _truncate_tool_call_args_json() in the compression pass shrinks long string values inside a PAST assistant message's tool_calls[].function.arguments — the exact field that represents the model's own prior generated output, replayed back to it verbatim on every subsequent turn. The old marker, a bare "...[truncated]" suffix, is indistinguishable from something the model itself could have written (it's exactly the kind of terse ellipsis abbreviation models already produce). A model conditioned on seeing itself "get away with" that pattern in its own history imitates it in a new tool call, writing the literal marker instead of real content. This is the second bug from the same root text. The first (NousResearch#11762, MiniMax 400s from unterminated JSON) was fixed by shrinking inside the parsed structure so the JSON stays valid, but kept the same visible marker text — fixing the syntax problem while leaving the imitation problem untouched. Fix: replace the marker with one deliberately NOT shaped like prose a model would write — distinctive non-ASCII delimiters, an explicit "not part of the original tool call" disclaimer, and a per-instance char-count that won't match the next omission point even if copied verbatim. The shrunk value stays a plain string (not a nested object) so the NousResearch#11762 valid-JSON/matching-shape contract is unchanged — only the marker text changed. Checked context_compressor.py's other "...[truncated]" call sites (_serialize_for_summary, _compact_fallback_turn, the user-message-only one near _ACTIVE_TASK_MAX_CHARS) — none of them write into a value that gets replayed as the main model's own assistant/tool_calls history, so they don't share this priming risk and were left as-is. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
308812f to
509c5d8
Compare
Related: #83735 fixes the same model-visible context-compression marker by removing it entirely; this PR retains a deliberately non-prose marker. Maintainer choice is needed. |
Review follow-up after local verification + cross-check of remaining markersThis is the right root-cause fix for the #83714 mechanism we confirmed (compressor rewriting past assistant 1. This does not scrub already-compacted session historySessions that already have old Optional follow-up (not required for this PR): on load/replay, rewrite known old markers inside assistant tool_call argument strings to the new non-imitable form (or strip the suffix). That would heal long-lived gateway sessions without waiting for natural turnover. 2. Other model-visible
|
|
Combined with write-guard + shared marker module on djbclark:fix/truncation-combined-83714 (compressor still uses the non-imitable marker; constant now lives in |
|
Independent verification on exact head
Our real failure sequence was consistent with this PR's root-cause model: a past assistant No additional blocker found in this verification. The companion write-path guard in #83752 remains valuable for residual/old markers. |
fix(context-compressor): stop priming models to abbreviate their own tool-call arguments Good idea executed carefully — replacing the prose-shaped
|
|
Landed on Point-by-point on this thread's open items:
Follow-ups on top of your commit (ours):
Deliberate trade-offs, documented in the function docstring: ~60 tokens per shrunk leaf against ~4 for the old marker, and leaves under the ~420-char break-even are now left intact instead of head-truncated. Thanks — the diagnosis and the non-prose marker were the substance of this fix. Closing in favour of #116169; if you want any of the follow-ups shaped differently, say so and I will reopen. |
Summary
Root-cause fix for #83714.
During context compression,
_truncate_tool_call_args_json()shrinks long string values inside past assistanttool_calls[].function.arguments— content that is later replayed to the model as its own prior output. The old suffix was a bare...[truncated], which is easy for a model to imitate in a new tool call (and then write to disk).This is the same marker family that previously caused #11762 (invalid JSON after raw-string slicing). That fix kept JSON valid but left the imitable marker text.
Change
Replace the bare marker with a deliberately non-prose-shaped disclaimer:
⟪⟫)Other
...[truncated]sites in the compressor (summarizer input, fallback recap, user-only paths) are left alone — they are not replayed as assistant tool-call args.Suggested landing order
Testing
TestTruncateToolCallArgsJsonexpectations for the new markerTestTruncationMarkerNotImitablecoveragepytest tests/agent/test_context_compressor.py tests/test_trajectory_compressor.py -q— green on the PR branchRelated
Happy to take review feedback or split further if useful.