Drop whole tool-call groups when trimming a prompt #116
No reviewers
Labels
No labels
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/agent-proxy!116
Loading…
Reference in a new issue
No description provided.
Delete branch "trim-tool-call-groups"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Bug
Fixes #113.
Eleven
request.prompt_trimmedevents over seven days, and a backend rejection beside every one of them:The ratio held at 24 log lines per trim across three days at three different volumes, and no instance of that 400 appeared on a day with no trim. As far as telemetry shows, trimming had never once produced a request the backend accepted.
Root cause
apply_context_budgetwalked the non-system messages from the oldest end and dropped them one at a time until the running total fit. A tool-heavy round is the first round to cross the budget, and it is exactly the round whose messages cannot be separated: anassistantmessage carryingtool_callsand thetoolmessages answering those calls are one unit in the OpenAI dialect. In the trace on the issue, the trimmer dropped 5 messages out of a 23-message round that carried one assistant turn plus fivesteam__get_store_app_detailsresults, and the resulting payload was rejected on structure.Fix
group_tool_call_turnssplits the prompt into the smallest units the trimmer may drop whole. An assistant message withtool_callscollects thetoolmessages answering its ids; every other message is its own group, so grouping is a no-op for a prompt without tools.unpaired_tool_messagechecks the trimmed list against the upstream rule before dispatch. Group-wise dropping cannot orphan a reply, so a failure there means the prompt arrived unpaired: that raisesPromptPairingErrorand the route answers 400 naming the offending message, instead of spending three retries to receive an opaque upstream 400 wrapped as a 502.How to verify
ward exec test- 8 new cases intests/test_analysis.pyand 1 intests/test_api.py.test_trim_never_orphans_a_tool_replybuilds the trace's shape and asserts neither half of the dropped round survives alone.test_unpaired_trimmed_prompt_is_rejected_locallyasserts the stubbed upstream is never called.Test plan
ward exec format-check,lint,typecheck,pre-commitall cleanRisk
Low. Grouping is identity for prompts without tool calls, so every non-tool path is byte-identical to today. The one behaviour change beyond correct trimming is that an already-unpaired prompt now fails locally with 400 rather than travelling upstream, and only on the path where trimming ran.
Note
The budget that triggers trimming is itself wrong on hosted routes, for a separate reason tracked in #115. This change makes trimming correct wherever it runs.