Skip to main content

The Tool Default Argument Is a Policy Decision in Disguise

· 10 min read
Tian Pan
Software Engineer

Open the trace of any agent run and look at a tool call. You see the tool name and the arguments the model chose to pass. What you do not see is everything it did not pass. A search call with query set and nothing else still ran with a page size, a timeout, a result ranking, and a visibility scope. The agent decided none of those. You did, months ago, when you wrote the tool's schema and left those parameters optional with a default.

That default is not a convenience. It is a policy decision wearing the costume of a sensible blank. The default page size caps how much of the world the agent can see in one call. The default timeout decides when the agent gives up and improvises. The default visibility scope decides whether "search the docs" means the public handbook or the entire internal wiki including the unreleased roadmap. The default dry_run flag decides whether the agent's action is a rehearsal or a real, irreversible event in production.

None of these choices show up where you review agent behavior. You audit the system prompt. You audit the tool descriptions. You audit the model's reasoning traces. But the effective arguments — the merged result of what the agent passed and what your defaults supplied — are an unowned layer that no review process touches. The prompt gets a design doc and three rounds of feedback. The default limit=50 gets typed once and never mentioned again. They have equal power over what the agent actually does.

The Default Is the Most Likely Value

Here is the uncomfortable part. For any optional argument, the default is not the least-used value. It is the most-used value, because the model reaches for it constantly.

A model calling a tool behaves like a developer skimming an API: it fills in the parameters that are obviously load-bearing for the immediate task and leaves the rest alone. If query is required and scope is optional, the agent passes query every time and scope almost never. Your default for scope is therefore not an edge case. It is the production behavior. It governs the overwhelming majority of calls, and it does so silently.

This inverts how we normally think about defaults in human-facing APIs. When a human developer integrates an API, they read the docs, notice the optional parameters, and make a deliberate call about which ones matter. The default catches the cases they did not think about. With an agent, the default catches the cases the agent did not think about — which, for any parameter that is not screamingly central to the task, is nearly all of them.

So the question for every optional parameter is not "what is a reasonable fallback." It is "what behavior do I want on the vast majority of calls, because that is what this value will produce." If you would not be comfortable putting that value in the system prompt as a stated policy — "the agent searches only public docs," "the agent retrieves at most 50 rows," "all destructive actions are rehearsals unless explicitly confirmed" — then you should not be comfortable leaving it as a quiet default either. It is the same policy. One version is reviewable and one is not.

Three Defaults That Quietly Decide Everything

Consider the parameters that look most like harmless ergonomics and turn out to steer the whole system.

Page size and result limits decide what the agent can know. An agent reasoning over a list_tickets tool with a default limit=20 is not reasoning over your ticket queue. It is reasoning over the first twenty rows of it, in whatever default sort order you also did not think about. Ask it "are there any urgent tickets" and it answers confidently from a window that may not contain the urgent ones. The agent is not wrong; it is working from the slice your default handed it. The AWS CLI documents the inverse failure too — a default page size set too high causes large list calls to time out entirely — so there is no safe value to leave unconsidered, only a value matched to the data.

Timeouts decide when the agent abandons reality for improvisation. A tool call that hits its default timeout does not return clean failure to the model in a way the model treats as terminal. It returns an error the agent will often route around — retrying, trying a different tool, or, worst of all, proceeding as if it had the data. A timeout that is too short converts slow-but-correct into fast-but-fabricated. The default timeout is the line between "the agent waited for the truth" and "the agent made something up." That is not a tuning knob. That is a behavioral contract.

Scope and visibility defaults decide what "do the task" is allowed to touch. This is the one with teeth. A search_documents tool with a default scope of "all indexed content" means every retrieval call reaches into everything the service account can see. A send_message tool with a default channel or default recipient list means a vaguely-worded request lands somewhere specific. A query_database tool that defaults to a read-write connection instead of a read replica means a malformed call can mutate state. Security research on least-privilege agent frameworks is blunt about the principle: a tool call should be denied unless a policy explicitly permits it and its parameters. The optional-with-broad-default pattern is the exact opposite — permit everything unless the agent happens to narrow it, and the agent rarely does.

Safe-by-Default Matters More for Agents Than for Humans

Every API designer knows the safe-default principle: least privilege, fail closed, narrow scope. It has always been good practice. For agent tools it stops being good practice and becomes structural, for two reasons.

The first is the one above: the agent uses the default far more often than a human integrator would, so an unsafe default is not a rare exposure but the steady state.

The second is that the agent's caller is not trustworthy in the way a human integrator is. A human developer who wires up a tool has read the docs, holds intent, and is accountable. The model assembling a tool call is, from a security standpoint, processing untrusted input — the conversation, retrieved documents, prior tool outputs, any of which a prompt injection could have reached. The standard guidance now is explicit: treat LLM-supplied tool arguments as untrusted, validate against allow-lists, and never let the model populate sensitive parameters at all. The arguments the model omits are even less trustworthy than the ones it supplies, because they were not even considered — they are pure inheritance from your schema.

This is why the most robust pattern separates two parameter classes entirely. Parameters the model is allowed to choose, and parameters the model never sees. User identity, permission scope, the read-vs-write connection, the real-vs-dry-run flag — these get injected by your application code at execution time, after the model has produced its call and before the function runs. The model proposes; deterministic code disposes. A parameter that decides whether an action is reversible should never be a default the model can also override by guessing. It should not be in the model's schema at all.

For the parameters that genuinely must stay in the model's hands, the fix is smaller and older than agents: make the consequential ones required. An optional parameter with a default is a decision deferred to a value. A required parameter is a decision forced into the open. If the page size materially changes what the agent concludes, do not default it — require it, and let the calling layer or the prompt set it deliberately for each context. The mild annoyance of "the model now has to pass scope every time" is the entire point. You have converted an invisible inheritance into a visible, reviewable, trace-able decision.

Log the Effective Arguments, Not the Passed Ones

Even with safe defaults and required-where-it-matters, you have an observability gap, and it is the easiest one to close.

Most agent tracing records the tool call as the model emitted it. That capture is missing exactly the thing this whole problem is about: the defaults. If the model passed {query: "outage"} and your tool resolved that to {query: "outage", limit: 20, scope: "all", timeout_ms: 3000, dry_run: false}, the trace that shows only the first object is lying to you by omission. When you later debug why the agent missed the urgent ticket, the trace tells you it searched for "outage" and shows you nothing about the window-of-20 that actually caused the miss.

The fix is one line of discipline: log the effective arguments — the post-merge object that the function actually executed with — as a distinct field from the passed arguments. Audit-log guidance for agents already calls for recording the arguments passed to every external tool; extend that to record the arguments resolved. Then every default becomes visible after the fact, every silent policy becomes a queryable field, and "the agent timed out and improvised" stops being a guess and becomes a row you can filter on.

This also gives you the audit trail you will eventually be asked for. When a security or compliance reviewer asks "could this agent have read X," the honest answer lives in the effective scope of its tool calls, not in the prompt and not in the model's reasoning. If you only logged what the model passed, you cannot answer. If you logged what the tool ran with, you can.

Treat Every Default as Something Someone Signed Off On

The mental shift is small and it costs nothing to adopt. Stop thinking of a default as a blank you filled in for convenience. Start thinking of it as a line of policy that someone — you — signed off on, and that will govern most of your agent's behavior on that tool.

Practically, that means three habits. First, when you add an optional parameter to an agent tool, write down in one sentence what behavior its default produces in the common case, and decide whether you would defend that sentence in a design review. Second, for any parameter where that sentence is about safety, reversibility, or blast radius — make it required, or pull it out of the model's schema and inject it from trusted code. Third, log effective arguments so the defaults you keep are at least visible in hindsight.

Agents made tool design a higher-stakes discipline than it used to be, and the highest-stakes part is the part that looks like the lowest. The prompt is where you write down what you want the agent to do. The defaults are where you write down what it will actually do when it is not thinking about it — which is most of the time. Give them the same scrutiny, because they carry the same weight. A default left blank is not a decision postponed. It is a decision made, quietly, and shipped.

References:Let's stay in touch and Follow me for more thoughts and updates