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
Evidence provenance (added 2026-08-22). The run-derived figures in this issue come from a private scan that a reader cannot reproduce (raptor-e2e-20260818, a 9.85-hour run against
a Python target). They are reported for completeness, not as independently checkable evidence.
Every code claim below is pinned to public source at b5019628 and is checkable; where a
conclusion rests on the private numbers rather than on the code, treat it as my report of what
that run showed. This label was missing when the issue was filed.
Summary
PR #242 raised simple_text's default max_tokens from 8192 to 20000, with a comment naming the
exact failure mode it was fixing: a thinking-era model consumes the output budget on hidden reasoning
and returns a reasoning-only (empty) completion, which the adapter rejects as "no usable content".
Stage 2 verification does not go through simple_text. It calls binding.adapter.complete directly
with a module constant of 4096, so the raised default never reached verify on any route.
libs/openant-core/utilities/llm/helpers.py:41-49 — what PR #242 (a991416, merged 2026-08-16,
two days before this run) put in place:
# Thinking-era default. Claude-5 / Gemini-2.5+ / OpenAI o-series spend# output budget on hidden reasoning; 8192 can be fully consumed by# reasoning on a large unit, yielding a reasoning-only (empty) completion# that the adapter drops -> hard "no usable content" error. 20000 is under# the Anthropic non-streaming 10-min ceiling (32000 is rejected with a# "Streaming is required" ValueError; 20000 is accepted) and is a CAP, not# a floor on generation -- models still stop at end_turn on small prompts,# so this does not raise cost for short answers.max_tokens: int=20000,
19 hits: one is a comment (context/threat_model_agent.py:288) and two are adapter connectivity
probes with max_tokens=1 (utilities/llm/providers/anthropic.py:240, utilities/llm/providers/bedrock.py:233). That leaves 16 production override sites.
5 bypass simple_text by calling binding.adapter.complete(...) with an explicit budget — context/repo_explorer.py:286, report/generator.py:200, report/generator.py:342, utilities/agentic_enhancer/agent.py:255, utilities/finding_verifier.py:407.
Only 7 call sites inherit the 20000 default (a simple_text(...) call with no max_tokens):
python3 - <<'EOF'
import re, os
hits = []
for dp, dn, fn in os.walk("."):
if "/tests" in dp or dp.endswith("/tests"): continue
for f in fn:
if not f.endswith(".py"): continue
p = os.path.join(dp, f); src = open(p, encoding="utf-8", errors="replace").read()
for m in re.finditer(r'simple_text\(', src):
if "def simple_text(" in src[max(0, m.start()-4):m.end()]: continue
i, depth = m.end(), 1
while i < len(src) and depth:
depth += (src[i] == "(") - (src[i] == ")"); i += 1
hits.append((p.lstrip("./"), src[:m.start()].count("\n")+1, "max_tokens=" in src[m.start():i]))
print("simple_text call sites:", len(hits), " inheriting 20000:", sum(1 for h in hits if not h[2]))
for h in hits:
if not h[2]: print(" %s:%d" % h[:2])
EOF
Runtime evidence that the budget is reached in verify
The verifier already detects the truncation itself, at utilities/finding_verifier.py:487-504 (excerpt — 13 interior lines elided, 4 of them comments):
# A finish call on a turn the model TRUNCATED (stop_reason == "max_tokens")# is not a trustworthy completed verdict: a well-formed# finish(agree=False, "safe") from a cut-off turn would silently downgrade a# Stage-1 vulnerable. Treat it as verification-incomplete ...iffinish_resultandstop_reason=="max_tokens":
...
explanation="Verification incomplete (finish call truncated at max_tokens)",
Over the run's 223 verify checkpoints, 182 are incomplete. Their self-declared reasons:
RUN=~/.openant/projects/gadievron/raptor-e2e-20260818/scans/7dbf9c691d7/python
python3 - <<'EOF'
import json, glob, os, collections
D = os.path.expanduser("$RUN/verify_checkpoints"); h = collections.Counter()
for f in glob.glob(D + "/*.json"):
if os.path.basename(f) in ("_fingerprint.json", "_summary.json"): continue
v = json.load(open(f)).get("verification", {})
if v.get("incomplete") and v.get("explanation"):
h[v["explanation"][:60]] += 1
for k, c in h.most_common(3): print(c, "|", k)
EOF
92 | Max iterations reached
31 | Verification incomplete (finish call truncated at max_tokens)
11 | Verification incomplete (no tool calls)
31 of the 182 incomplete verifications self-declare stop_reason == "max_tokens". The other
151 do not: 92 record "Max iterations reached", 11 record "no tool calls", and 48 are errored records
that carry no explanation at all. Those 151 are not attributed to the 4096 cap here.
Sibling with the same shape: utilities/agentic_enhancer/agent.py:253-259 calls self.binding.adapter.complete(..., max_tokens=MAX_TOKENS_PER_RESPONSE) with the same 4096 constant
(agent.py:37), on a stage that is on by default (core/scanner.py:128, enhance_mode: str = "agentic").
Why it matters
PR #242's comment is an explicit statement that 8192 is too small for a thinking-era model on a large
unit. verify runs a multi-turn tool-using agent on the largest, most complex units in the scan, on
the same model class, at half that figure — and by a code path the fix could not reach, because
the fix lives in simple_text's signature and verify never calls it.
The verifier is careful about the consequence: a truncated finish is deliberately downgraded to incomplete rather than trusted (:487-508), which is the right call. But an incomplete
verification is an un-adjudicated candidate, and on this run 182 of 223 candidates were
un-adjudicated. At least the 31 self-declared truncations are directly attributable to the budget.
Related: #212 reports a large Stage-2 coverage loss on a different route. That issue's own analysis
identifies a different proximate cause; this issue is about the budget path, which is route-agnostic.
Suggested fix
Raise finding_verifier.MAX_TOKENS_PER_RESPONSE to match simple_text's 20000, or better, have
the phase read its budget from configuration with 20000 as the default, so the two cannot drift
again.
Do the same for utilities/agentic_enhancer/agent.py:37.
Consider making the budget a property of the PhaseBinding rather than a module constant in each
consumer, so simple_text and adapter.complete callers get the same number by construction.
That would collapse the 16-site census above into one place.
Add a test that asserts no production call site pins a budget below the simple_text default
without an explicit, commented justification.
Note that a resumed run will not show the effect of a budget change: the phase fingerprint does not
include generation parameters, so old checkpoints are adopted, and errored checkpoints are restored
rather than retried. Both are filed as #286/#287.
What I am not claiming
I am not claiming the 4096 cap explains all 182 incomplete verifications. Exactly 31 of
them self-declare stop_reason == "max_tokens". The 92 "Max iterations reached", the 11 "no tool
calls", and the 48 errored records are unattributed here.
I am not claiming a budget increase repairs a measured percentage of anything. On a resumed
run it repairs 0%, for the checkpoint reasons above. On a fresh scan the effect is unmeasured —
I did not re-run.
The census numbers are static counts at HEAD b501962 produced by the commands shown. They count
call sites, not calls made at runtime.
Sibling gap in the same loop — #291 (added 2026-08-22, back-link).#291 reports that verify
appends raw tool results with no input cap while enhance caps both and documents why. It calls this "the second instance of a verify-specific gap where the general fix exists elsewhere in-tree" and
names this issue as the first.
The two are independent and neither subsumes the other: this issue is the output budget
(max_tokens=4096 passed directly to adapter.complete, bypassing the simple_text default raised
in PR #242); #291 is the input side (no cap on what gets appended to the conversation). #291's own
"What I am not claiming" section says so — "I am not claiming the 4096 output budget (#290) is or is
not the dominant factor. These are independent gaps in the same loop and either may matter more."
Recorded here because the reference was one-way: #291 cited this issue twice and this body cited
nothing back, so a reader arriving here had no route to the sibling.
Summary
PR #242 raised
simple_text's defaultmax_tokensfrom 8192 to 20000, with a comment naming theexact failure mode it was fixing: a thinking-era model consumes the output budget on hidden reasoning
and returns a reasoning-only (empty) completion, which the adapter rejects as "no usable content".
Stage 2 verification does not go through
simple_text. It callsbinding.adapter.completedirectlywith a module constant of 4096, so the raised default never reached
verifyon any route.Evidence
libs/openant-core/utilities/finding_verifier.py:74(HEADb501962):utilities/finding_verifier.py:399-411— the agent loop, which bypassessimple_textentirely:libs/openant-core/utilities/llm/helpers.py:41-49— what PR #242 (a991416, merged 2026-08-16,two days before this run) put in place:
The PR touched
analyze(core/analysis_core.py:279callssimple_textwith nomax_tokens, so itinherits 20000). It did not touch
finding_verifier.py.Census of explicit budgets
19 hits: one is a comment (
context/threat_model_agent.py:288) and two are adapter connectivityprobes with
max_tokens=1(utilities/llm/providers/anthropic.py:240,utilities/llm/providers/bedrock.py:233). That leaves 16 production override sites.Of those 16, 10 pin an effective 4096:
5 bypass
simple_textby callingbinding.adapter.complete(...)with an explicit budget —context/repo_explorer.py:286,report/generator.py:200,report/generator.py:342,utilities/agentic_enhancer/agent.py:255,utilities/finding_verifier.py:407.Only 7 call sites inherit the 20000 default (a
simple_text(...)call with nomax_tokens):Runtime evidence that the budget is reached in
verifyThe verifier already detects the truncation itself, at
utilities/finding_verifier.py:487-504(excerpt — 13 interior lines elided, 4 of them comments):Over the run's 223 verify checkpoints, 182 are
incomplete. Their self-declared reasons:31 of the 182 incomplete verifications self-declare
stop_reason == "max_tokens". The other151 do not: 92 record "Max iterations reached", 11 record "no tool calls", and 48 are errored records
that carry no explanation at all. Those 151 are not attributed to the 4096 cap here.
Sibling with the same shape:
utilities/agentic_enhancer/agent.py:253-259callsself.binding.adapter.complete(..., max_tokens=MAX_TOKENS_PER_RESPONSE)with the same 4096 constant(
agent.py:37), on a stage that is on by default (core/scanner.py:128,enhance_mode: str = "agentic").Why it matters
PR #242's comment is an explicit statement that 8192 is too small for a thinking-era model on a large
unit.
verifyruns a multi-turn tool-using agent on the largest, most complex units in the scan, onthe same model class, at half that figure — and by a code path the fix could not reach, because
the fix lives in
simple_text's signature andverifynever calls it.The verifier is careful about the consequence: a truncated finish is deliberately downgraded to
incompleterather than trusted (:487-508), which is the right call. But anincompleteverification is an un-adjudicated candidate, and on this run 182 of 223 candidates were
un-adjudicated. At least the 31 self-declared truncations are directly attributable to the budget.
Related: #212 reports a large Stage-2 coverage loss on a different route. That issue's own analysis
identifies a different proximate cause; this issue is about the budget path, which is route-agnostic.
Suggested fix
finding_verifier.MAX_TOKENS_PER_RESPONSEto matchsimple_text's 20000, or better, havethe phase read its budget from configuration with 20000 as the default, so the two cannot drift
again.
utilities/agentic_enhancer/agent.py:37.PhaseBindingrather than a module constant in eachconsumer, so
simple_textandadapter.completecallers get the same number by construction.That would collapse the 16-site census above into one place.
simple_textdefaultwithout an explicit, commented justification.
Note that a resumed run will not show the effect of a budget change: the phase fingerprint does not
include generation parameters, so old checkpoints are adopted, and errored checkpoints are restored
rather than retried. Both are filed as #286/#287.
What I am not claiming
them self-declare
stop_reason == "max_tokens". The 92 "Max iterations reached", the 11 "no toolcalls", and the 48 errored records are unattributed here.
run it repairs 0%, for the checkpoint reasons above. On a fresh scan the effect is unmeasured —
I did not re-run.
different proximate cause; the two are compatible.
b501962produced by the commands shown. They countcall sites, not calls made at runtime.
Sibling gap in the same loop — #291 (added 2026-08-22, back-link). #291 reports that
verifyappends raw tool results with no input cap while
enhancecaps both and documents why. It calls this"the second instance of a verify-specific gap where the general fix exists elsewhere in-tree" and
names this issue as the first.
The two are independent and neither subsumes the other: this issue is the output budget
(
max_tokens=4096passed directly toadapter.complete, bypassing thesimple_textdefault raisedin PR #242); #291 is the input side (no cap on what gets appended to the conversation). #291's own
"What I am not claiming" section says so — "I am not claiming the 4096 output budget (#290) is or is
not the dominant factor. These are independent gaps in the same loop and either may matter more."
Recorded here because the reference was one-way: #291 cited this issue twice and this body cited
nothing back, so a reader arriving here had no route to the sibling.