The thinking ate
the answer
A reasoning model can spend its whole output budget thinking and hand you nothing. The fix is not a bigger budget. It is to stop waiting for the text.
The thinking ate the answer
On most providers, reasoning tokens count against the same output budget as the visible answer. If the model spends all of it thinking, you get an HTTP 200, an empty content, and finish_reason: "length", plus a bill for every token it thought.
One real review request, captured while building co-maintainer: 128,000 tokens generated, none of them visible, about five minutes of wall clock, fully billed.
Stop waiting for the text
If the answer lives in content, it has to survive to the very end of the stream, and the end is exactly where the budget runs out. So give the model one tool, resolve, and ask it to return the answer through that tool. The answer becomes the call's arguments, not the message.
That changes two things. The call can arrive while the model is still reasoning, and calling it is terminal: the moment it lands, you take the answer and stop reading. Nothing later in the stream can take it back.
Take it mid-stream, or force it in the next chunk
A · resolve arrives while reasoning streams
The client reads the stream. If a resolve call shows up, the answer is taken right there. It does not matter that the model would never have finished its thinking.
B · it never arrives
Count the reasoning as it streams. When it crosses a soft budget (30,000 tokens in co-maintainer), abort the connection before the provider's own limit. The thinking is not garbage, it is work, so the next chunk re-sends it: the aborted turn's reasoning_details, echoed back unmodified, plus one instruction to finish now and not restart, with the tool call forced.
Either way the run ends the same way: a resolve call and finish_reason: "tool_calls", never "length".
- stream
- abort
- handoff
- turn 2
- resolve
What it cost, replayed
I replayed the captured spiral through this ladder, with a 30,000-token first budget and a 15,000-token second:
finish_reason: "tool_calls"11 sThe spiral it replaced cost $0.15 or more and five minutes, for no answer at all. The same request now takes 157 seconds and a cent: about 2× faster and 15× cheaper, and it ends with an answer.
report_finding; resolve is the generic name for the pattern. And beat A, taking the call mid-stream, is the mechanism as designed. I have no measurement for it here.Where it stops working
- Models that cannot turn reasoning off have no last-resort rung. The first two beats still apply.
- A cut stream never receives the final usage chunk, so the cost of an aborted segment is reported as unknown, not as a wrong lower bound.