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
{{ message }}
Repository navigation
[Engine] 1 <tool_call> block(s) in the answer did not read as a call of a declared tool #4071
I upgraded opencode (but have been using this version for weeks without issue) and restarted Strata 0.1.48, resent the command triggering the problem and now it works fine. I checked the issue before posting and notice other tool_call related issues and decided this might be helpful. Unfortunately I am now unable to trigger the problem at will.
Secondary issue: When I restarted, I had WoW running, and this heavily affects prefill speed (80 tps). However, if I start Strata first and wow second, prefill speed is reasonable (500-800). Token generation is unaffected by the order of startup. After existing WoW, I hoped prefill would improve, and it did, but only to 140 rps, when I expected 1000 tps. I see a huge amount of copy to/from the GPU where there is usually very little during prefill.
Thanks for the issue. To keep the tracker readable, every item needs an area prefix in the title and the parts of the issue form filled in. This one is missing:
Start the title with its area: [Engine], [GUI], [TUI], [Benchmark], [Docs] or [Other], then a short description. Example: "[Engine] decode stops after 2 minutes".
Edit the issue (title and description) and this note goes away on its own. If it is still missing parts in 5 days it will be closed, which is not a rejection: fix it and comment /reopen.
changed the title [-]1 <tool_call> block(s) in the answer did not read as a call of a declared tool[/-][+][Engine] 1 <tool_call> block(s) in the answer did not read as a call of a declared tool[/+]on Oct 25, 2026
Same failure from the Pi + llama-swap side, with a client-side record of one turn - plus one practical obstacle to ever reading your own warning line.
The witnessed case. 2026-10-25, model qwen3.8-flash-next-iq3_s (IQ3 quant) behind llama-swap v266, Pi as the agent harness. One turn (10.9 s, finish_reason: stop, 1042 output tokens, 0 reasoning tokens) came back as an ordinary answer: its text block ends with a complete, well-formed call block - call opener, function name AskUserQuestion, one questions parameter, and all three closers in order - and the response carried no tool_calls at all. AskUserQuestion was not in the request's tools list; it is a tool name from a different harness's list, which the model wrote out of its training traces. Pi stored the markup as assistant text and the loop ended. That is the "tool calls ending up in regular assistant output, breaking the agent loop" report, seen from the client's side. Nothing was cut mid-block and no reasoning was involved, so for this turn it is the undeclared-name path, not a truncated block and not a thinking leak.
Why nobody can confirm it after the fact. That diagnostic (the sentence in this issue's title) is printed by the serve process to stdout. Two consequences behind a proxy:
it never reaches the engine's own log file - strata-*.log only holds the engine's token/cache stats, so grepping the logs finds nothing;
llama-swap keeps upstream stdout in a small in-memory ring. The copy I could grab was 102,401 bytes / 1163 lines = 131 request completions, about 1.5 h of agent use, with nothing older. By the time markup is noticed in the terminal, the line that named the undeclared tool has rolled away - which is exactly why the OP here says the problem cannot be triggered at will.
What would make this diagnosable, smallest first:
Also emit that warning to stderr / the engine's log file, not only stdout. Users running the server under a proxy have no way to see stdout at all.
Make the fact visible in the response, so the client can react without server-side access - a counter in usage or an extra field (something like "1 undeclared call block: AskUserQuestion"), or a response header. [Engine] Agent recovery when undeclared tool calls are returned as text #3796 is about agent recovery; recovery starts with the client knowing the call was dropped rather than never attempted.
For others running llama-swap: v266's request captures (Activity view, or GET /api/captures/{id}; both bodies are base64) retain the response body the client actually received, which is enough to tell this case apart from a truncated-block case afterwards. The budget is captureBuffer in MB (default 10; that held about 60 requests with 20k-40k token prompts). And STRATA_DEBUG=1 in the model profile's env: list makes serve dump the raw decoded model text and the token ids per request - that raw line is the evidence neither I nor the OP had - at the cost of one very long line per request.
I also ran into this issue, both with OpenClaw and with OpenCode (both via LiteLLM). In the OpenCode session it was happening for the first time, and I reverted the message and retried multiple times with the same result. So it's not only poisoned sessions, there is something problematic happening in Strata already.
Some more information from my agent:
Another data point — OpenClaw harness, and the "always on some starts / never on others"
pattern quantified.
Environment. Strata 0.1.48 (CUDA, Windows), upgraded from 0.1.40.3 the same week the
failures started. Models: Qwen3.8-Flash-Next IQ3_S quant + Qwen3.8-27B GSQ, served behind
a LiteLLM proxy (OpenAI-compatible Chat Completions). Harness: OpenClaw.
Separately: 7 assistant replies in one session contained complete call-shaped XML for
tools that WERE declared in the request (subagents, sessions_spawn), as plain text.
If declared-only validation is the only cause, that second shape shouldn't happen for
declared names — worth checking whether those blocks were malformed (truncated or
mis-nested) rather than complete.
Bimodality, measured: one session produced 7 malformed replies in ~15 minutes; other
sessions on the same engine, same model, same period produced zero. Once one
call-shaped block lands in the transcript, the model imitates it — that session
degrades fast and stays degraded. This is what "almost always on some starts, almost
never on others" looks like from the transcript side: it's not start luck, it's
self-reinforcing context.
Intermittent, cannot trigger at will — matches the OP.
What worked client-side. Instruct the model to route every tool not in the visible
list through a declared wrapper tool (tool_search -> tool_call with id/args).
Undeclared direct blocks stopped. Poisoned sessions still needed reset/compaction — the
instruction alone did not outweigh the bad examples already in context.
Endorses #3796's ask. A machine-readable signal (usage counter or response field,
per @ackalker) is what lets a harness distinguish "model chose not to call" from "parser
rejected a complete call", attempt bounded recovery, and quarantine the poisoned turn
before the feedback loop starts.
Question. Did tool-call parsing / declared-only validation change between 0.1.40.3
and 0.1.48? Our timeline points squarely at that update: same models, same harness,
worked before, fails after.
[strata] 1 <tool_call> block(s) in the answer did not read as a call of a declared tool and went back as text (a name the request did not declare, or a form the template does not use); the names the model wrote: shell=command; the response carries them as strata_rejected_tool_calls, /metrics counts them
<tool_call> <function=grep_search=isRegexp <parameter=query> list-options|core.|bmm.|output_folder|planning_artifacts|implementation_artifacts|Available|--set <parameter=filePath> content.txt <parameter=isRegexp> true <parameter=maxResults> 60 </tool_call>
same issue on VS Code with 0.2.7 Strata
Thank you for the log lines. Several reports here show it. Please send the raw model output of one failing turn and the tool names the client declares (OpenCode's tools list), and the Strata and engine version. The name in one report (shell=command) may be a malformed opener rather than an undeclared tool; the raw text shows which.
Fenced variant of this issue, reproduced locally end-to-end, with a fix in #4613.
Environment. Strata 0.2.7 (both the rocm and sycl builds), Qwen3.8-Flash-Next IQ3_S, served behind an OpenAI-compatible gateway, consumed through Open WebUI chat.
The witnessed case. Chat asks the model to "make an SVG of a red circle and save it". The request declares no tools (a plain Open WebUI chat - like the toolless-request shape other reporters hit after a wrapper dropped the list). The reply: finish_reason: stop, tool_calls absent, and content ends with a complete call block for write_file - opener, function name, both parameters, all closers in order - inside a ```xml code fence. Open WebUI renders it as the original report describes: the tool XML sits inside a code block in the answer.
What the code says. Two rules keep this text and both are right to: the declared-only rule (#3075) and the fence rule (#1058 - inside a fence an opener is a quoted example). The problem is the diagnosis channel from #3796: the fence branch of OutputParser returns the block as content without touching unread. At main (46c152a), feeding the exact shape through the parser gives unread=0, unread_names=[] - so the warning line in this issue's title never prints, strata_rejected_tool_calls is absent, /metrics does not move. A fenced block is invisible to every recovery signal a client has. That also sharpens the OP's "cannot trigger at will": whether the model fences the block (invisible) or writes it bare (the warning prints) looks like start luck from the client side.
What #4071 is, as I read it, after tracing the parser: one parser contract with three entry paths - undeclared name outside code (counted, named), malformed opener (counted, unnamed), and complete block inside a fence (until #4613: silent). The bimodality @Malspherus measured is a fourth, trans-script effect: once one block lands as assistant text, the model imitates it every turn.
Fix (#4613). Text and finish_reason stay exactly as they are - the rule the maintainer stated is kept. The fence/inline-code path now counts and names the block like the others, with a reason on each strata_rejected_tool_calls entry: undeclared, in_code (a declared name quoted in a fence), or malformed. Partial names are held until decidable so streamed and whole replies count identically (checked at chunk sizes 1/3/17). Regression test for the fenced shape included; it fails on stock main and passes with the commit, and the parser-suite failure lists are identical before/after.
One ask beyond the PR, for the poisoned-transcript loop that keeps sessions in this state: a client can only quarantine the bad turn if it can see it - the in_code reason is the minimum signal an OWUI function or harness needs to say "that was a dropped call, not the answer".
Activity
github-actions commentedon Oct 25, 2026
Thanks for the issue. To keep the tracker readable, every item needs an area prefix in the title and the parts of the issue form filled in. This one is missing:
Edit the issue (title and description) and this note goes away on its own. If it is still missing parts in 5 days it will be closed, which is not a rejection: fix it and comment
/reopen.Details: CONTRIBUTING.md
[-]1 <tool_call> block(s) in the answer did not read as a call of a declared tool[/-][+][Engine] 1 <tool_call> block(s) in the answer did not read as a call of a declared tool[/+]github-actions commentedon Oct 25, 2026
Thanks, this follows the format now.
ackalker commentedon Oct 25, 2026
Same failure from the Pi + llama-swap side, with a client-side record of one turn - plus one practical obstacle to ever reading your own warning line.
The witnessed case. 2026-10-25, model
qwen3.8-flash-next-iq3_s(IQ3 quant) behind llama-swap v266, Pi as the agent harness. One turn (10.9 s,finish_reason: stop, 1042 output tokens, 0 reasoning tokens) came back as an ordinary answer: its text block ends with a complete, well-formed call block - call opener, function nameAskUserQuestion, onequestionsparameter, and all three closers in order - and the response carried notool_callsat all.AskUserQuestionwas not in the request'stoolslist; it is a tool name from a different harness's list, which the model wrote out of its training traces. Pi stored the markup as assistant text and the loop ended. That is the "tool calls ending up in regular assistant output, breaking the agent loop" report, seen from the client's side. Nothing was cut mid-block and no reasoning was involved, so for this turn it is the undeclared-name path, not a truncated block and not a thinking leak.Why nobody can confirm it after the fact. That diagnostic (the sentence in this issue's title) is printed by the serve process to stdout. Two consequences behind a proxy:
strata-*.logonly holds the engine's token/cache stats, so grepping the logs finds nothing;What would make this diagnosable, smallest first:
usageor an extra field (something like "1 undeclared call block: AskUserQuestion"), or a response header. [Engine] Agent recovery when undeclared tool calls are returned as text #3796 is about agent recovery; recovery starts with the client knowing the call was dropped rather than never attempted.For others running llama-swap: v266's request captures (Activity view, or
GET /api/captures/{id}; both bodies are base64) retain the response body the client actually received, which is enough to tell this case apart from a truncated-block case afterwards. The budget iscaptureBufferin MB (default 10; that held about 60 requests with 20k-40k token prompts). AndSTRATA_DEBUG=1in the model profile'senv:list makes serve dump the raw decoded model text and the token ids per request - that raw line is the evidence neither I nor the OP had - at the cost of one very long line per request.Malspherus commentedon Oct 26, 2026
I also ran into this issue, both with OpenClaw and with OpenCode (both via LiteLLM). In the OpenCode session it was happening for the first time, and I reverted the message and retried multiple times with the same result. So it's not only poisoned sessions, there is something problematic happening in Strata already.
Some more information from my agent:
Another data point — OpenClaw harness, and the "always on some starts / never on others"
pattern quantified.
Environment. Strata 0.1.48 (CUDA, Windows), upgraded from 0.1.40.3 the same week the
failures started. Models: Qwen3.8-Flash-Next IQ3_S quant + Qwen3.8-27B GSQ, served behind
a LiteLLM proxy (OpenAI-compatible Chat Completions). Harness: OpenClaw.
What we saw (Oct 24-25).
wrote: bash, bash". Our harness declares
exec, notbash— same undeclared-name pathas [Engine] Agent recovery when undeclared tool calls are returned as text #3796's
bashvsshellcase.tools that WERE declared in the request (
subagents,sessions_spawn), as plain text.If declared-only validation is the only cause, that second shape shouldn't happen for
declared names — worth checking whether those blocks were malformed (truncated or
mis-nested) rather than complete.
sessions on the same engine, same model, same period produced zero. Once one
call-shaped block lands in the transcript, the model imitates it — that session
degrades fast and stays degraded. This is what "almost always on some starts, almost
never on others" looks like from the transcript side: it's not start luck, it's
self-reinforcing context.
What worked client-side. Instruct the model to route every tool not in the visible
list through a declared wrapper tool (
tool_search->tool_callwith id/args).Undeclared direct blocks stopped. Poisoned sessions still needed reset/compaction — the
instruction alone did not outweigh the bad examples already in context.
Endorses #3796's ask. A machine-readable signal (usage counter or response field,
per @ackalker) is what lets a harness distinguish "model chose not to call" from "parser
rejected a complete call", attempt bounded recovery, and quarantine the poisoned turn
before the feedback loop starts.
Question. Did tool-call parsing / declared-only validation change between 0.1.40.3
and 0.1.48? Our timeline points squarely at that update: same models, same harness,
worked before, fails after.
OutRunner commentedon Oct 26, 2026
I've been seeing this all day today too. I keep having to prompt it to try again.
icalder commentedon Oct 28, 2026
I'm seeing this too. NVFP4. Only started happening with 0.2.7.
zhangjg8116 commentedon Oct 29, 2026
[strata] 1 <tool_call> block(s) in the answer did not read as a call of a declared tool and went back as text (a name the request did not declare, or a form the template does not use); the names the model wrote: shell=command; the response carries them as strata_rejected_tool_calls, /metrics counts them
pluciorx commentedon Oct 29, 2026
<tool_call> <function=grep_search=isRegexp <parameter=query> list-options|core.|bmm.|output_folder|planning_artifacts|implementation_artifacts|Available|--set <parameter=filePath> content.txt <parameter=isRegexp> true <parameter=maxResults> 60 </tool_call>
same issue on VS Code with 0.2.7 Strata
Niko1221 commentedon Oct 29, 2026
Thank you for the log lines. Several reports here show it. Please send the raw model output of one failing turn and the tool names the client declares (OpenCode's tools list), and the Strata and engine version. The name in one report (
shell=command) may be a malformed opener rather than an undeclared tool; the raw text shows which.james-martinez commentedon Oct 29, 2026
Fenced variant of this issue, reproduced locally end-to-end, with a fix in #4613.
Environment. Strata 0.2.7 (both the rocm and sycl builds), Qwen3.8-Flash-Next IQ3_S, served behind an OpenAI-compatible gateway, consumed through Open WebUI chat.
The witnessed case. Chat asks the model to "make an SVG of a red circle and save it". The request declares no tools (a plain Open WebUI chat - like the toolless-request shape other reporters hit after a wrapper dropped the list). The reply:
finish_reason: stop,tool_callsabsent, andcontentends with a complete call block forwrite_file- opener, function name, both parameters, all closers in order - inside a ```xml code fence. Open WebUI renders it as the original report describes: the tool XML sits inside a code block in the answer.What the code says. Two rules keep this text and both are right to: the declared-only rule (#3075) and the fence rule (#1058 - inside a fence an opener is a quoted example). The problem is the diagnosis channel from #3796: the fence branch of
OutputParserreturns the block as content without touchingunread. Atmain(46c152a), feeding the exact shape through the parser givesunread=0, unread_names=[]- so the warning line in this issue's title never prints,strata_rejected_tool_callsis absent,/metricsdoes not move. A fenced block is invisible to every recovery signal a client has. That also sharpens the OP's "cannot trigger at will": whether the model fences the block (invisible) or writes it bare (the warning prints) looks like start luck from the client side.What #4071 is, as I read it, after tracing the parser: one parser contract with three entry paths - undeclared name outside code (counted, named), malformed opener (counted, unnamed), and complete block inside a fence (until #4613: silent). The bimodality @Malspherus measured is a fourth, trans-script effect: once one block lands as assistant text, the model imitates it every turn.
Fix (#4613). Text and
finish_reasonstay exactly as they are - the rule the maintainer stated is kept. The fence/inline-code path now counts and names the block like the others, with areasonon eachstrata_rejected_tool_callsentry:undeclared,in_code(a declared name quoted in a fence), ormalformed. Partial names are held until decidable so streamed and whole replies count identically (checked at chunk sizes 1/3/17). Regression test for the fenced shape included; it fails on stockmainand passes with the commit, and the parser-suite failure lists are identical before/after.One ask beyond the PR, for the poisoned-transcript loop that keeps sessions in this state: a client can only quarantine the bad turn if it can see it - the
in_codereason is the minimum signal an OWUI function or harness needs to say "that was a dropped call, not the answer".