Your Agent's Last Move Should Be a Tool Call, Not Text
The Agent Optimization Manual · No. 1
We were building a benchmark harness for agent context strategies — the kind of thing where you run the same research agent a few dozen times and compare cost and quality. Before we could measure anything interesting, the harness handed us a different finding, and it’s the one worth sharing first:
28% of our agent runs ended with an empty deliverable. Not a wrong answer. Not an error. The agent did the work — searched, fetched documents, gathered every fact it needed — and then its final turn came back with nothing in it. No exception, no failure status, cost fully incurred. The run looked successful everywhere except the one place that mattered.
If you run agents on Gemini and your agent’s job ends with “now write up your findings,” there’s a decent chance this is happening to you and your logs don’t show it.
The failure signature
Our setup: a research agent on Gemini 2.5 (Flash and Pro both exhibit this), 15–35 tool calls per run, ending with a written briefing. Every run’s model and tool calls were metered through our gateway, so we had full per-call accounting. Across 32 runs, 9 ended like this:
- 10–20 successful tool calls — the research actually happened
- a final model turn that generated 300–600 completion tokens
- zero visible output text
- a clean “natural finish” as far as the loop could tell
The tokens are real — you pay for them — but they’re thinking tokens with no answer behind them. The model reasons its way to the end of its final turn and stops without emitting text. It’s intermittent, it correlates with large accumulated context, and because nothing throws, an agent loop that treats “model stopped without requesting tools” as completion will happily record the run as done.
The insidious part is the shape of the failure: silence. A crash pages someone. An empty string that walks like a completed run doesn’t.
What didn’t fix it
We did the obvious things first, and they’re worth listing because each one looked like the answer:
- Retries on the model call — helps for rate limits, does nothing here; the call “succeeds.”
- Temperature 0 — no effect; this isn’t sampling noise.
- Toggling thinking — disabling thinking on Flash reduced one variant of the problem and introduced others (and Pro won’t run with thinking disabled at all). Dead end.
- A forced “wrap up now” retry turn when the deliverable came back empty — recovered some runs, but it’s a band-aid: you’re re-asking the same model to do the same fragile thing.
Every one of these treats the symptom: the model sometimes ends a free-text turn with no text. You can’t reliably patch that from the outside, because from the outside it isn’t distinguishable from a model that legitimately chose to say nothing.
The actual fix is a design rule
The fix came from noticing something about our production agents, which had never hit this in months of scheduled runs: none of them end on a free-text turn. Every production agent we run delivers its result by calling a tool — send the email, post the message, write the record. The deliverable travels in the tool call’s arguments. Our deliverable-extraction code goes further: it reads results only from outbound tool calls and deliberately refuses to fall back to whatever text the model happened to emit.
So we gave the benchmark agent the same contract. Instead of “write your final briefing,” the agent gets a briefing.submit tool — structured fields for exactly what the task requires — and one rule: calling this tool is the only way to deliver; a written answer does not count.
The silent failure stopped.
Same model, same task, same context sizes. Across the gated warm-ups and the full batch since the change — 37 runs — 36 delivered through the tool (97%), against 72% under text delivery. And the one run that didn’t deliver is the point: it shows up as an unattempted run — a boolean you can see and alert on — instead of an empty string masquerading as success. The failure mode didn’t just shrink; it became observable. Function-calling turns, in our data, don’t exhibit the silent empty-finish behavior that free-text turns do.
Why this works (and what it buys you beyond the bug)
Mechanically: a tool call is a structured object the API either emits or doesn’t. There’s no such thing as a half-hearted, thinking-only tool call — the failure mode collapses from “silent empty text that parses as success” to “the tool was never called,” which is detectable.
And that detectability is the deeper win. Once delivery is a tool call, your agent loop gets three things free:
- A crisp reliability metric. “Did the run call the submit tool” is a boolean you can alert on, chart, and hold steady across deployments. We now track attempt rate separately from output quality — an agent that researched brilliantly and delivered nothing is a reliability failure, not a quality failure, and conflating them poisons both metrics.
- Structured, verifiable output. Scoring a prose briefing means string-matching and hoping. Scoring
briefing.submit({companies: [...]})means comparing fields. Anything downstream — evaluation, storage, another agent — gets data instead of paragraphs. - A clean termination signal. The tool call marks the run’s job as done. Our production loop uses this to stop re-prompting after delivery — which is also how you avoid the “agent sent the same email twice” class of bug.
If you’ve read our writing on governing agents, you’ll recognize the shape of the argument: the tool call is the unit you can see, meter, and enforce. Free text is where agent behavior goes unobservable. That’s true for security policy, it’s true for cost attribution, and — it turns out — it’s true for basic delivery reliability too.
The checklist
If your agents end by writing text:
- Add a submit tool. Name the fields your task actually needs. Tell the agent it’s the only valid delivery path.
- Refuse text fallback. If the run ends without the submit call, record it as unattempted — don’t grade whatever text happens to be lying around.
- Track attempt rate as a first-class metric, separate from quality. Ours was 72% before the change and 97% after — and before the change we’d have told you it was ~100%, because nothing was erroring.
- Meter your runs. We only caught this because every model call was metered and the run rollups showed real cost with no deliverable attached. If your observability stops at “the run finished,” this failure is invisible by construction.
Caveats, honestly
Our mechanism evidence is Gemini 2.5-specific (thinking-heavy final turns emitting no visible text); we haven’t reproduced the same signature on other providers, and the 32-run baseline is one task shape (multi-step research, 15–35 tool calls, large accumulated context). The design rule doesn’t depend on the mechanism, though — structured delivery is strictly more observable than prose delivery on any provider, and it’s how our production fleet has run from the start. We’ll publish the full benchmark this came out of — context strategies vs. metered cost and quality — separately, with the raw data.
Every number here is metered, not estimated: per-call cost and token accounting captured at the gateway. That’s the same instrumentation the cost X-ray runs on.
Part of The Agent Optimization Manual — measured lessons on making agents cheaper and more reliable, every number metered at the gateway, not estimated.
What is an Agentic Control Plane? → · Read Manual No. 3: don’t give up on cheap models →
- . Your Agent's Bottleneck Is Almost Never the Model
- . One Sentence in Our System Prompt Doubled the Agent's Bill
- . Your Agent's Last Move Should Be a Tool Call, Not Text · you are here
- . Don't Give Up on Cheap Models — Give Up on Fragile Loops