Skip to main content

Postel's Law Is a Liability at the Tool Boundary

· 9 min read
Tian Pan
Software Engineer

In 1980, Jon Postel wrote a sentence into the TCP specification that became a founding principle of the internet: "be conservative in what you do, be liberal in what you accept from others." For forty years, engineers have applied it everywhere — parsers that tolerate trailing commas, APIs that coerce "10" into 10, HTML renderers that quietly repair broken markup. The web arguably exists because browsers forgave everyone's mistakes.

Then the caller stopped being a human, and the advice inverted. When an AI agent calls your tool with a stringly-typed number, a mis-nested JSON object, or an enum value that's almost right, the tolerant parser that saves a human developer twenty minutes of debugging does something much worse to the agent: it confirms that the sloppy call was correct. The agent's only training signal inside a loop is the feedback your tool returns. Accept garbage, and you are teaching the model — right now, in this episode — that garbage works.

The IETF itself has walked Postel back. RFC 9413, "Maintaining Robust Protocols," argues that the robustness principle actively harms protocol ecosystems over time: every tolerated deviation becomes a de facto part of the spec, implementations fossilize around each other's bugs, and the flexibility you preserved for evolution gets consumed by accumulated leniency. That critique took decades to play out in network protocols. At the tool boundary of an agent loop, the same decay happens in minutes — because the "peer implementation" is a model that updates its behavior on every response you send it.

Leniency Is Negative Feedback You Didn't Mean to Send

Think about what actually happens when a model emits a near-miss tool call. Say your tool expects {"amount": 1050, "currency": "USD"} and the model sends {"amount": "1,050.00", "currency": "usd"}. You have two choices.

The strict path rejects the call: amount must be an integer number of cents (got string "1,050.00"); currency must be uppercase ISO 4217 (got "usd"). The model reads this, corrects itself, and — critically — the corrected format is now in context for every subsequent call in the episode. One failed round trip bought you convergence for the rest of the session.

The lenient path silently strips the comma, parses the float, multiplies by 100, uppercases the currency, and returns success. The model got exactly the outcome it wanted with the malformed call, so the malformed call is what gets reinforced. Every subsequent call in the loop carries the same slop, because nothing ever told the model otherwise. And your coercion logic is now load-bearing: the day it encounters "1.050,00" from a model reasoning about a European invoice, it will guess wrong, silently, at 100x the stakes.

This is the core inversion. For human callers, a validation error is friction and coercion is a favor. For model callers, a validation error is in-loop training signal and coercion is a lie about where the contract actually is. Agents demonstrably self-correct when given actionable errors — the whole modern practice of agent-loop design depends on it. Frameworks like LangChain build retry parsers around exactly this mechanism: feed the failed output plus the error back to the model, and success rates on the second attempt jump. Silent coercion opts you out of that mechanism entirely.

The Sloppiness Compounds, and Then It Escapes

A single coerced argument sounds harmless. The problem is that agent loops iterate, and errors that would be one-off annoyances in a human workflow become distributions in an agent fleet.

Consider how the debt accrues:

  • Within an episode: the model's successful-but-sloppy call sits in its own context as an exemplar. Models imitate their own recent outputs; one accepted near-miss begets ten more.
  • Across your schema: once the model learns that your parser forgives type errors on one field, it has no reason to believe any field is strict. Leniency on amount erodes discipline on account_id.
  • Across tools: agents generalize. A fleet that learns "this platform coerces" produces sloppier calls against every tool on the platform, including the ones that are strict — and those now fail in ways the model doesn't expect.
  • Into your data: the worst case isn't a failed call, it's a succeeded-wrong one. The classic unit bug — dollars where cents were stored — is precisely the kind of error a coercing boundary waves through, because both magnitudes look plausible in isolation.

There's a name for the endgame: Hyrum's Law. With enough callers, every observable behavior of your system becomes a dependency, regardless of what you promised. When the callers are models making thousands of calls an hour, "enough callers" arrives immediately. Your accidental tolerance for mis-nested JSON is now an API commitment, exercised at machine speed, that you can never remove without breaking fleets you've never heard of.

Real-world infrastructure bugs show the same shape one layer down. LiteLLM had an issue where malformed tool-call arguments from a model were silently discarded rather than surfaced — the agent never saw the failure, so it couldn't correct, and the corruption cascaded into subsequent requests until the whole conversation failed. Silence at the boundary doesn't contain errors. It launders them.

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