/p/2026-09-08 · explainer
Paper explainer · 2609.03966 · Wenbo Wang

The model called the tool.
The server counted zero.

An agent evaluation reports a tool-call rate, and that number is read off the serving stack. This paper shows the number can sit at zero while the model is emitting perfectly well-formed calls, because the chat template and the output parser are two halves of one contract: when they disagree, the server drops good calls before anything downstream sees them. Holding weights, cases, decoding and seeds fixed and changing only that adapter, the same model scores 0.00 or 0.96. A two-by-two over template and parser puts both main effects at exactly zero — nothing is broken on its own, and repairing one side buys nothing.

01 · The contract

Three parties have to agree, and only two of them are yours

A tool call has to survive three separate agreements: what the checkpoint was trained to emit, what the chat template tells it to emit, and what the parser is defined to accept. The template renders your tool schema into the prompt and instructs an envelope. The parser scans the reply for that envelope and lifts the call out. Neither knows what the other was configured with.

Flip both switches. The payload the model writes is identical in every cell — the paper is explicit that it is the same bytes, only the wrapper changes.

Interactive · set the adapter
documented template dedicated template
hermes parser dedicated parser

Three of the four cells return zero parsed calls out of 200 cases; the fourth returns 196. Fixing only the template moves nothing. Fixing only the parser moves nothing. Both main effects are exactly zero and the entire effect sits in the interaction, which is why no amount of staring at either component finds the fault.

The full two-by-two200 cases · first request per case

What makes it silent rather than loud is the shape of the failure. The request returns HTTP 200. The tool_calls array comes back empty. The call text stays in content as ordinary prose. Downstream, that is byte-for-byte what a model that simply declined to use a tool looks like — and the author confirms it is not a parser bug: replaying the parser's own rules over 4,254 stored turns found zero cases where it rejected something its own grammar would have accepted.

02 · The ladder

Bigger models emit more calls; the counter stays at zero

Run the same broken adapter across a 21-fold parameter range and the two quantities come apart completely. Well-formed calls the model writes climb from 0 to 80 out of 100. Calls the server parses stay flat at zero, at every size. Step through the ladder.

Interactive · pick a checkpoint size
0100 items
well-formed calls emitted
calls the server counted
flat zero at every size on this adapter

The obvious objection is that larger models might simply be doing something different, so the author ran a control: a second family whose template does instruct the envelope the parser accepts, over a comparable scale span. There the two quantities track each other, and the silent fraction — calls emitted but never counted — never exceeds 2.

The matched-interface controla family whose template and parser agree

One caution the paper puts on itself, and worth carrying: emitted-call counts are only measurable where the server parses nothing, because a successful parse moves the call out of content and into tool_calls. The evidence of emission is consumed by the thing that works.

03 · Inside the training loop

A branch the policy can never be rewarded for taking

The same mismatch inside a reinforcement-learning rollout loop is worse than a bad metric. The probe takes 115 generations from a 7B checkpoint and sorts them by what the model wrote, then by what the loop accepted.

Interactive · step the rollout through the loop
0115 generations

Forty-five of 115 rollouts attempt the tool with a valid, complete call. Zero are accepted, zero execute, zero return an observation, so every trajectory collapses to a single turn. The tool-using branch never appears in the sampled experience at all, which means it gets no advantage estimate and cannot be reinforced — the paper's phrase is that it is "effectively inaccessible to policy-gradient learning in this cold-start regime". A training curve that looks merely disappointing is in fact measuring a branch that was never reachable. At 1.5B the author declines to make the same claim, because capability fails there too and the zero is over-determined.

04 · Repair

The mechanism moves enormously. The score barely moves.

Swapping in the matching template and parser restores the machinery. On a 115-task retail agent benchmark, parsed calls go from 0 to 636 and tasks that touch a tool at all go from 0 to 103. On the 100-item coding loop, parsed calls go from 0 to 84 and nine items are rescued by a second turn that previously could not happen.

Interactive · pick the experiment
the broken adapterthe repaired adapter

And then the honest part, which the author foregrounds rather than buries. Turn-one pass is identical in both arms — 53 against 53 — which is the internal-validity check, since an adapter can only change what happens after the first turn. Final pass moves 53 to 62, and that gain does not survive correction for the roughly a dozen comparisons the paper runs: it sits at p = 0.093 against a threshold near 0.0042, and a pre-planned 300-item replication reproduces the direction at p = 0.039, still short. Two results do clear the bar, and neither is the pass-rate gain.

What the author explicitly rests no claim on

Repairing the adapter restores the mechanism, and the mechanism is measured overwhelmingly — a flat zero becomes 636 executions. Whether that turns into a better task score at this scale and budget is not established here, and the paper says so in those terms. The reason to run the check is that a metric reading zero cannot tell you anything at all, not that fixing it is guaranteed to raise your numbers.

05 · The check

Assert on the parsed call, not on the status code illustrative

The released preflight is 98 lines and runs in seconds. It issues one canonical request carrying your tool schema, asserts that tool_calls comes back non-empty with the expected name and parseable arguments, then repeats the request with tool_choice set to required as a positive control. That second request is what separates "the model chose not to call" from "this adapter cannot express a call at all". Toggle each assertion off and see which failures walk straight through.

Interactive · build the checkillustrative · the paper's four observed failure layers

Only one adapter failure announces itself — a missing auto-tool-choice flag returns an error. The rest are quiet, and they are per checkpoint, not per stack: the paper catalogues four distinct layers where the contract can break, and the same server configuration can be correct for one model family and silently wrong for the next. One of them is not a format failure at all: a model that issued a call on 97 of 100 items named, on 23 of them, the very function the task had asked it to write, as though it were an available tool. Setting strict: true on the function object took that to zero and raised valid executions from 73 to 97.

Interactive · size the exposureillustrative · this paper's largest observed silent fraction, applied to a run of your own
calls emitted but never counted
what your dashboard would report
the adapter cannot express a call, so every rate computed through it reads zero

The numbers

What the paper actually reports

What this does not show

In practice