v0.1.26 — tool-turn guard: the agent loop no longer stalls on the simple route
Field-debugged release: three live-validated failure modes made fusion-agents stall the OpenCode agent loop ("does one step, then waits for continue"). All three are now detected and recovered on the smart simple passthrough — the route ~87 % of agent steps take.
Added
- Single-route tool-turn completeness guard (
src/strategies/tool_turn_guard.ts). Detects, on tool-carrying requests: (1) narrate-and-stop — the model describes the next action in prose and ends the turn withfinish_reason:stopand notool_calls; (2) length-cut tool calls — large-file writes truncated mid-arguments (finish_reason:length, unrunnable JSON); (3) upstream stream termination — Ollama Cloud kills generation streams at ~5 minutes. Recovery is ONE live-streamed retry (nudged to emit the tool call and write large payloads in chunks), failing open to the original response. The stream wrapper is reader-driven rather than apipeThroughTransformStream, becauseflush()never runs when the source errors — a transform-based guard is structurally blind to failure (3). - Terminal-state instrumentation. Every tool-carrying single-route stream logs one
tool-turn terminal stateline (finish_reason, tool-call count, guard verdict, lengths, tail) — real-session stalls are diagnosable from the log alone. - Per-model
request_overrides(single strategy + smart inlinesimpleslot). Extra request-body fields merged into every upstream call; core keys (model,messages,stream,tools,tool_choice) are protected. A/B-measured flagship use:reasoning_effort:"none"stops glm-5.2 deliberating for minutes on mechanical steps (reasoning 1692→0 chars, 6 s→2 s, tool-calling intact;think:falseand"low"are ignored by Ollama Cloud). - Fusion synth: agentic tool-action directive — when the request carries tools, the synth is told the panel's prose is deliberation, not the final shape: if the best next step is an action, emit the tool call. Inert on the tool-less research/report path.
Changed
fusion-agentssimple route now sendsreasoning_effort:"none"via the newrequest_overrides. The stale simple-target comment now documents the two real constraints: foreign tool-call-history compatibility and a large context window (a real session overflowed kimi's 262K — do not swap kimi in for speed).
Post-release hotfix on main
8942eb7— the guard now swallows the upstream[DONE]so its framing is canonical (exactly one[DONE], its own) even when the upstream ends without afinish_reasonchunk. Found by a cross-provider post-release review; production clients were shielded by the downstream usage-injection transform, so no observable corruption occurred.
306 → 307 tests green; typecheck clean. Full details in CHANGELOG.md.