The orchestrator's out-of-the-box path, dogfooded on a real multi-file drafting
project with a slow thinking model. Every item here is something that made a
turn look hung, end early, or do more work than it needed: a status row that
said "working" for twelve minutes, a generation that fell into a loop nothing
could see, a 30-minute clock that killed productive work, and an edit tool
the model never reached for.
Added
- The status row says what the model is doing, not just "working". With the
reasoning stream off, every phase of a reply looked the same: a spinner and the
word working, for twelve minutes at a time. The row now shows three things: the
phase of the last delta, colored, with its icon and live token tally —⋯ thinking 2.3k,✎ writing 410,◆ tool call 120, or… waitingbefore the
first delta (queue wait or prefill); the elapsed time, which turns into a red
silent 14swhen no delta has arrived for ten seconds mid-stream; and the
tok/s figure that was already there. The tally is an estimate from the same
chunk-to-token ratio the rate uses (chars/4 before a ratio has been measured).
LLMClient.stream_snapshot()exposes the live picture, poll-cheap. - A generation that locks into repetition is stopped mid-stream, not at the cap.
Observed live (qwen3.8-27b, 2026-09-08): the model's hidden reasoning fell into
413, 313, 213,and stayed there for the rest of a 16,384-token reply while the
status row said "working". Nothing could catch it — the repetition guard only
inspects the final text once the stream ends, a typed nudge lands on the next
request, and an output cap merely postpones the cut, after which the cap grows.
The stream consumer now watches the tail of everything it receives, reasoning
and content alike, and aborts the request the moment a short unit has repeated
back-to-back across a 1,200-character window; closing the stream ends generation
server-side. The loop records the abort in the ledger as anllm_responsewith
finish_reason="degenerate", re-prompts once withpresence_penalty=1.5for the
rest of the turn (Qwen's own guidance for thinking-mode repetition, applied only
after a loop was seen, never as an ambient default), and a second abort ends the
turn as a failure that says why.LLMClient.complete/stream_completetake a
per-callpresence_penalty, sent only when set.
Changed
- No turn time limit by default.
permissions.budget.max_duration_minutesnow
defaults tonull, which means no limit; write a number to pin one, exactly as
with the output cap. The old 30 minutes could not tell a runaway from slow
hardware: live (2026-09-09, qwen3.8-27b at ~20 tok/s), a productive
13-iteration turn resuming interrupted work — 56,000 output tokens, every step
landing — was ended by the clock between iterations at 39 minutes, and its
continuation faced the same wall. What actually stops a turn that has gone
wrong ismax_actions, the stuck detector, the mid-stream repetition guard,
the per-chunk silence timeout, the kill file, and Ctrl-C; those are unchanged.
Built-in subagents keep their own bounded budgets. Aconfig.yamlwritten by an
olderinitstill carriesmax_duration_minutes: 30.0and still pins it —
delete the line or set it tonull. editcomes withwrite, and both tools show their work. A fresh install's
root agent has always heldeditalongsidewriteandbash_exec, but the
model never reached for it: a yaml-defined subagent that listedwritegot no
editat all, and neither tool said what actually changed. Now a child whose
tools.addnameswritegetsedittoo (same capability class — the
capability floor and the grant gate treat both as host-dangerous — and an
explicit deny ofeditstill wins).editreturns a unified diff of the
change, cut at 40 lines, so the model can verify an edit without re-reading the
file.writeover an existing file reports the line delta, and when the change
touched a small slice of a long file it says so and points atedit— the
write still lands, because refusing it would cost a full round-trip, which is
minutes on a local model. TheToolConfigdocstring finally listseditamong
the global built-ins.