fix(loop): add headless NEEDS_PRO escalation for reasonix run#2195
fix(loop): add headless NEEDS_PRO escalation for reasonix run#2195chunc4730-collab wants to merge 1 commit into
reasonix run#2195Conversation
In headless one-shot mode (`reasonix run`), the flash model can emit `<<<NEEDS_PRO>>>` to signal that a task exceeds its capability. In TUI mode, the UI layer catches this and retries with deepseek-v4-pro. In headless mode, no TUI is listening — the escalation event is lost and the turn ends without retrying. This fix adds inline NEEDS_PRO detection in CacheFirstLoop.step(), immediately after streamModelResponse returns. When the marker is found and the model isn't already pro, we switch to deepseek-v4-pro and continue the iteration — no process restart needed. The flash model's NEEDS_PRO response is never persisted (we skip appendAndPersist), so it doesn't pollute the session log.
esengine
left a comment
There was a problem hiding this comment.
Good catch on the gap — in headless reasonix run there's no TUI layer to act on <<<NEEDS_PRO>>>, so detecting it in the loop is the right place. But the escalation needs to be one-shot, and as written it's permanent.
The escalation contract (src/prompt-fragments.ts:18) promises: 'This aborts the current call and retries this turn on deepseek-v4-pro, one shot.' Your code does this.model = "deepseek-v4-pro" and never restores it — and this.model isn't reset per turn (only set in the constructor / configure(), loop.ts:217/412). So after a single <<<NEEDS_PRO>>>, every subsequent turn for the rest of the session runs on pro, which contradicts the cost-aware 'one shot this turn' design (this whole product is built around cheap tokens / not burning pro unnecessarily).
Please make it one-shot: capture the base model, escalate for the retry, and restore it after the escalated turn completes (or scope the pro override to the retried call rather than mutating this.model for the session). A test that asserts turn N+1 is back on flash after an escalation on turn N would pin it.
Minor: consider writing a one-line stderr note when you escalate in headless (the TUI surfaces '⇧ flash requested escalation — '; headless currently escalates silently). The <<<NEEDS_PRO: reason>>> form carries a reason you could log. Not blocking.
Also FYI this touches the same loop.ts region as #2162 (just merged) so you'll likely need a rebase. CI re-approved on my side.
|
CI is green now, but the one-shot issue from my review is still open — the code still does |
|
Heads up — #2236 (by @BeiZi6) implements the one-shot behavior I asked for here: it captures the base model and restores it after the escalated turn ( |
Problem
In headless one-shot mode (
reasonix run), the flash model can emit<<<NEEDS_PRO>>>to signal that a task exceeds its capability. In TUI mode, the UI layer catches this and retries withdeepseek-v4-pro. In headless mode, no TUI is listening — the escalation event is lost and the turn ends without retrying.Fix
6 lines added in
src/loop.ts(CacheFirstLoop.step()), immediately afterstreamModelResponsereturns. When the<<<NEEDS_PROmarker is found inassistantContentand the model isn't already pro, we switchthis.model = "deepseek-v4-pro"andcontinuethe iteration.The flash model's NEEDS_PRO response is never persisted (we skip
appendAndPersist), so it doesn't pollute the session log.Testing
Related
This is the loop.ts counterpart to the existing TUI escalation handler in
src/cli/ui/slash/handlers/model.ts.