You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The extract_llm_sweep prefix ask sometimes returns a reply the component reads as the empty
string, which lands in sweep_unparseable — a counter whose name says "the prompt is wrong". It is
not a prompt problem, and it is the dominant residual failure mode in every arm of the
three-way A/B run for #137.
How it was found
Measuring #137. Every unusable reply is logged (cg.sweep.unusable_reply), and splitting them by
shape across three benchmark passes per arm (aws/claude-sonnet-5, LOCA S2L cg_128k_3):
Note the sample log is capped at maxSweepUnusableSamples, so those columns undercount the larger
arm.
A dedicated pass with every reply's content blocks logged shows what the empty replies actually
are. 29 prefix-ask replies, tool_choice removed and no verdict tool declared:
8 of 29 replies (27.6%) carry a tool_use and no text block at all. cheapmodel.CompletePrefixed returns the first non-empty text, so those read as "".
Two distinct causes hide behind one counter:
The model called somebody else's tool. The ask appends to a prefix carrying the agent's own
tools (they are in the cache key, so they cannot be stripped) plus context_guru_expand. With
nothing better on offer the model calls one of those. fix(adjudicate): declare a verdict tool and stop forcing tool_choice:none #137 removes most of this by declaring a
tool worth calling — 55.8% of its replies come back through it — but it cannot remove all of it.
A genuine thinking-only reply, no text and no tool call. Present on main too, where tool_choice: none makes case 1 impossible, so main's 5 empties are all of this kind.
Severity
Low-to-moderate, and mostly a diagnosis defect. Everything fails open: an unusable reply keeps
every output verbatim, so no content is lost. The cost is a wasted ask (real tokens, and up to 90 s
of llmCallTimeout risk) plus a counter that actively misdirects: an operator reading sweep_unparseable will go and edit the prompt, when for case 1 the prompt was never read.
Fix options
Split the counter (small, and worth doing regardless). sweep_reply_empty for reply_len == 0, and sweep_reply_other_tool when the reply's only tool_use names a tool that
is not ours. Today sweep_unparseable conflates three failures needing three different fixes.
Cheap, no behaviour change; the reply shape is already parsed in CompletePrefixed.
Read the thinking block as a last resort for case 2. Tempting and probably wrong: a verdict
recovered from reasoning the model did not commit to is exactly the input sweep_quote_fabricated exists to catch.
Retry once on an empty reply. Bounded, but it doubles the worst-case latency on a path that
already sits inside a 90 s timeout, and it pays a second prefix read. Only defensible once the
counter split shows how often case 2 actually happens.
What
The
extract_llm_sweepprefix ask sometimes returns a reply the component reads as the emptystring, which lands in
sweep_unparseable— a counter whose name says "the prompt is wrong". It isnot a prompt problem, and it is the dominant residual failure mode in every arm of the
three-way A/B run for #137.
How it was found
Measuring #137. Every unusable reply is logged (
cg.sweep.unusable_reply), and splitting them byshape across three benchmark passes per arm (
aws/claude-sonnet-5, LOCA S2Lcg_128k_3):reply_len == 0)origin/main(tool_choice: none)Note the sample log is capped at
maxSweepUnusableSamples, so those columns undercount the largerarm.
A dedicated pass with every reply's content blocks logged shows what the empty replies actually
are. 29 prefix-ask replies,
tool_choiceremoved and no verdict tool declared:8 of 29 replies (27.6%) carry a
tool_useand no text block at all.cheapmodel.CompletePrefixedreturns the first non-emptytext, so those read as"".Two distinct causes hide behind one counter:
tools (they are in the cache key, so they cannot be stripped) plus
context_guru_expand. Withnothing better on offer the model calls one of those. fix(adjudicate): declare a verdict tool and stop forcing tool_choice:none #137 removes most of this by declaring a
tool worth calling — 55.8% of its replies come back through it — but it cannot remove all of it.
maintoo, wheretool_choice: nonemakes case 1 impossible, so main's 5 empties are all of this kind.Severity
Low-to-moderate, and mostly a diagnosis defect. Everything fails open: an unusable reply keeps
every output verbatim, so no content is lost. The cost is a wasted ask (real tokens, and up to 90 s
of
llmCallTimeoutrisk) plus a counter that actively misdirects: an operator readingsweep_unparseablewill go and edit the prompt, when for case 1 the prompt was never read.Fix options
sweep_reply_emptyforreply_len == 0, andsweep_reply_other_toolwhen the reply's onlytool_usenames a tool thatis not ours. Today
sweep_unparseableconflates three failures needing three different fixes.Cheap, no behaviour change; the reply shape is already parsed in
CompletePrefixed.recovered from reasoning the model did not commit to is exactly the input
sweep_quote_fabricatedexists to catch.already sits inside a 90 s timeout, and it pays a second prefix read. Only defensible once the
counter split shows how often case 2 actually happens.
fix(adjudicate): declare a verdict tool and stop forcing tool_choice:none #137 (7 of 77 asks), this is a reasonable resting point — but option 1 is the prerequisite for
ever knowing that.
Recommendation: option 1 now, then re-measure before considering 3.
Found while measuring #137; not folded into it because it is present in
origin/mainat the samerate and is not caused or cured by that change.