An agent asked to return JSON returns valid JSON almost every time — which is exactly the problem. The failures that matter aren't the ones where the output is obviously broken and a parser throws immediately; those get caught in the first test run. The dangerous failures are the near-misses: a trailing comma inside a code fence, a numeric field returned as the string "12", an enum value that's a close paraphrase of an allowed one, a required field silently omitted because the model judged it "not applicable" without being told omission isn't an option. Each of these can pass a casual glance and fail three layers downstream, in code that assumed the schema was already enforced.
Loose validation (accept anything JSON-shaped, coerce types on the way in) and strict validation (reject anything that doesn't match the schema exactly) fail in opposite directions, and most systems need different answers for different fields in the same object. A numeric field that arrives as a numeric string is almost always safe to coerce — the model round-tripped a number through text and lost the type, not the meaning. A field with an enum constraint that arrives as an unlisted value close to a real one is not safe to coerce silently, because the coercion is a guess about intent standing in for validation. Decide this field by field, before the first failure, rather than in an ad hoc exception handler written under pressure after something downstream broke.
| Failure shape | Safe response |
|---|---|
| numeric value returned as a string ("12" instead of 12) | coerce — the type was lost in text, not the value |
| enum value that's a near-paraphrase of an allowed one ("high-priority" vs. "high") | reject and retry with the exact allowed list restated — do not fuzzy-match, since a wrong silent match is worse than a retry |
| required field missing entirely | reject and retry — never default it, since a default can look like a real answer downstream |
| extra, undeclared fields present | usually safe to ignore and log, unless the schema is meant to be closed |
| valid JSON wrapped in prose or a markdown code fence | strip the wrapper mechanically before parsing — this is a formatting artifact, not a content error |
The single highest-leverage change in a validation-retry loop is what the retry prompt actually says. "That wasn't valid, try again" gives the agent nothing to correct and often reproduces the identical mistake, because whatever caused the first failure — an ambiguous instruction, a schema the model misread — is still present and unaddressed. Passing back the specific validation error (which field, what was expected, what was received) turns the retry into a targeted correction instead of a second independent guess, and in practice resolves the large majority of near-miss failures on the first retry. Cap retries at a small fixed number and fail loudly past that point; an unbounded retry loop against a schema the model structurally cannot satisfy just burns tokens until something else times out.
A repair layer that fixes formatting (stripping a code fence, trimming trailing commas, converting a numeric string) is safe because it only affects representation, not content. A repair layer that fills in a missing field with a plausible-looking default, or fuzzy-matches an unrecognized enum value to the nearest known one, is not safe, because it's making a content decision on the agent's behalf and hiding that decision from everything downstream. The test: could this repair change what the answer means, not just how it's written? If yes, it belongs in the reject-and-retry path, not the silent-repair path, no matter how often the fuzzy match happens to be right.
The failure mode to watch for in production: a repair or coercion step that was added to unblock one specific bad run, left in place, and now silently masks a class of errors that should be surfacing as retries or alerts. Log every repair and every rejection with what triggered it — a repair step with no visibility into how often it fires is a bug waiting for the day its assumption stops holding.
Validation catches malformed output after the call. Preventing the wrong call in the first place is a separate problem — see writing tool descriptions agents actually use correctly.