MAX Bridge 11 min read

Your Tool Schema Passes, but the Agent Called It Wrong

MAX contrasting a green tool-schema check against an agent that called the tool at the wrong moment

You wrapped an internal service as an agent tool. You wrote the JSON Schema by hand — every field typed, every enum constrained, required arguments marked. The validator went green on the first pass and stayed green. Then the agent called the tool during a run it was told to skip, passed it a perfectly-shaped payload, and triggered a downstream write that should never have fired. The schema never complained. It had nothing to complain about — the arguments were valid. The decision to call was the part no schema was ever checking.

An agent’s tool interface is an API whose caller improvises. The model reads your tool’s description, then decides on its own whether to call it, when, in what order, and with which arguments — and a passing schema proves only that the arguments were well-formed, not that the call was the right one. The instinct you are leaning on, if it validates, it’s correct, assumed a caller you could read and code-review. This one you cannot.

The Caller Reads Your Docs, Then Improvises

Start with what still transfers, because most of it does. You have shipped public APIs for clients you will never meet. You already know the discipline: version the contract, keep it backward-compatible, make write operations idempotent, and never trust that the caller read the docs the way you meant them. Every one of those instincts survives the move to agents. The tool boundary is still a boundary. Garbage in still produces garbage out, only now in fluent prose.

Here is where the map needs an extension. A coded client calls your endpoint because a developer wrote a line that calls it, reviewed in a pull request, deterministic on every run. The agent calls your tool because a model read the tool’s name and description mid-inference and judged it the best next move. The judgment is fresh each turn. Two runs of the same conversation can call different tools in a different order, and both can validate.

That is why a passing schema stopped meaning a correct call. The old guarantee — if it validates, it’s correct — no longer holds once the caller is a model choosing the call for itself. The contract layer modern agents converged on is real and worth understanding — you declare each tool with a JSON Schema and the model returns a structured argument object the runtime executes, the mechanism MONA walks through in the tool-use explainer. But that schema only governs the shape of the arguments. Whether the call should have happened at all lives one layer up, in the model’s reading of your description. In the original τ-bench evaluation, state-of-the-art function-calling agents passed under half of retail tasks on the first attempt and under a quarter when asked to solve the same task eight times in a row — a 2024 baseline, and today’s models clear it by a wide margin, but the gap it exposed has outlived the numbers: valid is not the same as reliably right.

Mental Model Map: the agent tool contract From: a tool is an endpoint my code calls with validated inputs Shift: the caller is a model that reads the description and decides for itself To: a tool is a contract for a nondeterministic client — the schema is the floor, not the guarantee Key insight: you now design interfaces for a caller that improvises the call site on every turn

Mental model map showing the shift from tool-as-endpoint to tool-as-contract for an improvising caller
The schema validates the arguments. It was never validating the decision to call.

Line the two callers up side by side and the extension gets concrete:

Contract concernCoded clientAgent caller
Timing of the callFixed in reviewable integration codeChosen by the model on every turn
A passing schema guaranteesWell-formed args and an intended callWell-formed args only
Reachable surfaceThe endpoints you documentedEvery composition the model can express
Fix for a wrong callPatch the clientConstrain the contract or rewrite the description

In practice, this means your test for “does the tool work” now has two halves that used to be one: does a call succeed, and does the model call it only when it should. The first is unit-testable the old way. The second is a property of your description and your guardrails, and it fails quietly.

The Description Field Is Runtime Behavior

Watch how this bug gets debugged wrong. The agent keeps selecting the wrong tool — reaching for refund_order when it should have reached for check_refund_status. The team assumes the handler is broken and starts adding logging inside the function. The function is fine. The function was never consulted. The model chose refund_order because its description read as the better match for what the user said, and the model acts on descriptions, not on your intentions.

Treating the description field as documentation — the way a docstring is a nicety the caller might skim — is the misconception that sends engineers debugging the wrong layer. The description is not documentation the model may read. It is the selection logic the model definitely reads, on every call, before every decision. Rewrite the description and you have changed runtime behavior without touching a line of code. That is the fix for a mis-selecting agent, and it is invisible to anyone still staring at the handler.

You debug a wrong endpoint call by reading the client code that made it. You debug a wrong tool call by reading the sentence the model read before it decided. One lives in your repo. The other lives in a string you probably wrote in thirty seconds and never reviewed again.

The SDK-era instinct here misleads. More endpoints means a more capable client — that was true when the client was code, because unused endpoints cost nothing. Applied to an agent, it inverts. Every tool you add is another description the model reads on every turn, another near-neighbor it can confuse for the right one. Input length alone degrades a model’s attention even when everything in the window is relevant, the effect Chroma’s Context Rot work measured directly. A fatter tool menu is a longer prompt and a denser field of look-alikes — so more endpoints can make the caller worse, not better. The failure is not in any single tool. It is at the prompt-and-schema layer that no Agent Frameworks Comparison owns for you. A planning step that sometimes returns a tool call and sometimes returns a plain string will break LangGraph, AutoGen, and CrewAI alike, and the only fix is at that layer the framework does not touch — the point MONA’s framework teardown lands hard. Pick the architecture before you pick the API, and design the contract before you pick the architecture.

Two Ways Your Schema Breaks in Production

The tool-menu model assumes the agent picks one action from a flat list you defined. Two whole classes of agent break that assumption from opposite directions, and both are in your stack already.

The first is the Code Execution Agents path. Give a model a sandbox and it stops choosing from your menu — it writes code that composes your calls. It loops, branches, chains three of your tools into one script, and binds their outputs together in ways you never enumerated. On the CodeAct benchmarks, code-as-action agents reached higher task success in fewer steps than JSON-calling agents, because composition beats a flat menu, as MONA explains in the code-execution primer. The gain is real. So is the shift in your threat model: your reachable surface is no longer the endpoints you exposed, it is every program the model can write against them. A serialization-injection flaw in one popular agent framework’s core let prompt-controlled fields execute arbitrary code — the class of risk you inherit the moment untrusted text can reach a composer. The sandbox is what holds that line; without one you trust, a code-execution agent is an exploit surface, not a feature. That boundary, not the schema, is what you design against — the threat model MONA lays out in the prerequisites piece is the right reading before you turn one on.

The second class removes the schema entirely. A Browser and Computer Use Agents system binds to a UI that exposes no contract at all — it perceives a screen as a DOM tree or a raw screenshot and issues clicks and keystrokes at coordinates. There is no argument object to validate, no enum to constrain. The interface is whatever the pixels say it is, and the grounding strategy you pick decides which failures you will spend the next quarter debugging, not whether the agent works — the trade-off MONA maps in DOM trees versus screenshots. When the perception channel and the real screen diverge, the agent confidently clicks a target that is not there. No schema was ever in the loop to catch it.

In practice, this means “validate the input at the boundary” splits into two different jobs depending on the agent: contain the runtime when the caller composes, and verify the perception when the caller has no contract to validate against at all.

Design for a Caller That Retries Itself

One classical instinct pays off harder here than anywhere else: idempotency. You already make write operations safe to repeat because networks drop and clients retry. Agents make that discipline non-negotiable, because retry is built into the loop. The model re-calls a tool when an observation looks wrong. The durable executor wrapped around it replays activities from the last checkpoint after a crash. Temporal’s own docs are blunt about it — activities must be idempotent because the engine will, by design, replay them, the constraint MONA draws out in the DAGs-versus-state-machines explainer. A DAG user hits the same wall the moment retries turn on, usually the hard way, through a charged-twice incident.

This is where Workflow Orchestration For AI stops being plumbing and becomes the contract. Orchestration is the runtime contract between an agent and the world it acts on — it decides what runs next, what happens on failure, and where state lives. The production pattern that won in 2026 is a durable engine on the outside wrapping a reasoning loop on the inside, and it works because you lock the contract at every layer boundary: input shape, output shape, and checkpoint location, specified before code, the discipline MAX-mode spells out in the production-workflow guide. Skip that and the failure is worse than a crash. A looping or degrading agent still returns HTTP 200 within normal latency, so your monitoring reports a healthy system while the output quietly goes wrong — the silent-failure mode ALAN dissects is the direct cost of leaving the contract implicit.

Shift Diagram: from coded client to agent caller Classic: define endpoint → client coded against it → schema validates → correct call AI: define tool + description → model reads description → model decides when, whether, in what order → schema validates shape only

Shift diagram comparing a coded client calling a validated endpoint against an agent reading a description and deciding the call itself
Same schema on both sides. Only one side had a caller you could review.

Before You Expose an Internal Service as a Tool

Run your own stack through these before you wire the next tool. They are not questions for a search engine; they are questions about your service.

Runtime questionWhy it matters
Does the description say when not to call this, not just what it does?The model reads the description as selection logic; an omission reads as permission.
Is every call idempotent, or does a replay double-charge?The agent and the executor around it will retry by design; a non-idempotent tool corrupts on replay.
What is the blast radius if the agent calls this at the wrong moment?The schema won’t stop a well-formed wrong call — a boundary has to.
Would a second tool with a near-identical description confuse selection?Look-alike descriptions degrade tool choice more than a missing endpoint ever did.
Can your monitoring catch a valid call that was the wrong call?A wrong-but-valid call returns success; logs that watch status but not semantics will call it healthy.

If you cannot answer the middle three, you have not specified a contract yet — you have published a schema and hoped.

Stop treating the tool schema as the contract. The schema is the floor: it proves the shape of an argument and nothing about the judgment that produced the call. The real contract is the description the model reads and the boundary that survives a wrong-but-valid call. Write both before you expose the next internal service — then watch what the agent actually does with the freedom you just handed it.

AI-assisted content, human-reviewed. Images AI-generated. Editorial Standards · Our Editors

Share: