Code as Specification: Why the Schema Comes Before the Prompt

The central mistake in prompt engineering is treating prose as the primary specification. In production systems, the prompt should be downstream of the contract, not the other way around.

I keep encountering the same mistake in production systems. The central mistake in prompt engineering is treating prose as the primary specification. A team writes a long system prompt, appends caveats after each failure, and gradually turns the prompt into a legal document. The artifact gets longer, more brittle, and harder to reason about. The team feels like it is specifying the system. In reality it is negotiating with it.

That approach is backwards. In production AI systems, the prompt should not be the source of truth. The source of truth should be code: schemas, validators, tool contracts, failure semantics, and metrics. The prompt is still useful, sometimes extremely useful, but it belongs downstream of those harder artifacts. It is an execution surface, not the constitution.

This is the principle of Code as Specification. Define what counts as acceptable behavior in a form the surrounding runtime can enforce, test, and evolve. Then let the prompt help the model operate inside that boundary. If you reverse the order, the system becomes opaque precisely where it most needs to be inspectable.

TL;DR — Key Takeaways:
  • A prompt is not a stable specification; it is a soft control surface over a probabilistic model.
  • The durable artifacts in an agent system are schemas, validators, metrics, tool contracts, and failure rules.
  • Prompts should be compiled around those artifacts, not used as substitutes for them.
  • If the behavior matters enough to test, audit, or rely on, it should exist in code somewhere outside the prompt.
  • The shift is from "ask the model nicely" to "make the allowed behavior structurally legible."

The Prompt Is the Wrong Layer To Carry Truth

The Prompt Is a Hyperparameter already established the first half of the argument: prompts are optimizable artifacts, not sacred source code. Their quality depends on the model, the task, the dataset, and the metric. Change any of those variables and the "best" prompt may change as well.

That alone should make teams suspicious of using prompts as the deepest layer of system intent. You do not want your source of truth to be the most model-sensitive, least testable, most linguistically ambiguous component in the stack. Yet many agent systems are still built exactly that way. The schema is optional. The validator is thin. The metric is an afterthought. The prompt carries all the policy because it is the easiest thing to edit under deadline pressure.

The result is architectural debt that I have watched accumulate across dozens of agent implementations. Every new edge case becomes another paragraph in the prompt. Every policy change becomes more prose. Every teammate reads the same instructions slightly differently. The system starts to "work" only in the sense that a charismatic operator knows how to babysit it. Nothing durable was specified. It was only narrated.

If the only place a rule exists is inside a prompt, the rule is advisory. Real specification begins when the runtime can enforce it without asking the model for permission.
In production AI systems, the prompt should be downstream of code-level contracts and metrics
Figure 1: In production AI systems, the prompt should be downstream of code-level contracts and metrics

What Belongs in Code

The answer is not "everything." The model still needs language. It still needs task framing, decomposition hints, and examples. But the parts of the system that define correctness should be expressed in harder artifacts.

Input bounds. What shapes of requests are accepted? That belongs in schemas and preconditions. Output structure. What shape must the result take? That belongs in typed models, constrained decoding, or strict post-generation parsing. Failure semantics. What happens when the model is uncertain or malformed? That belongs in retry rules, escalation paths, and explicit state transitions. Economic boundaries. When is a bigger model justified and when is it waste? That belongs in routing logic and cost-aware policy.

This is why the Agentic Contract matters. A real contract names the accepted inputs, the permitted action space, the output schema, and the stopping conditions. It does not merely advise the model to "be careful." It creates a lane in which carefulness is partially enforced by the wrapper itself.

That same logic appears in structured extraction, tool-calling, and evaluation systems. A field type is not just developer convenience. It is a way of turning vague intent into a boundary the runtime can inspect. A regex on a date field, a whitelist on an enum, or a hard failure on malformed output are small examples of the broader move: the machine is no longer asked to remember the rule. The system is built so the rule is already present around it.

Concern Weak prompt-only version Code-as-specification version
Output format "Please return valid JSON." Typed schema, constrained decoding, strict parser, failing validator
Tool authority "Only call tools when necessary." Explicit tool whitelist, typed parameters, scoped capabilities
Error handling "If unsure, be cautious." Retry policy, escalation rule, deterministic circuit breaker
Quality target "Give the best answer possible." Metric, evaluator, regression dataset, acceptance threshold

The pattern is consistent. Whenever a requirement matters enough to be stable, it should move out of prose and into an artifact the rest of the system can inspect. That does not eliminate the prompt. It just demotes it from constitution to interface.

The Metric Is Part of the Specification

One reason teams miss this principle is that they focus only on output schemas. Schemas matter, but they capture the shape of correctness, not always the substance. A perfectly valid JSON object can still route the wrong ticket, propose the wrong remediation, or summarize the wrong fact. The deeper specification is the thing that says what success actually means.

That is where metrics, validators, and judgment artifacts enter. Validator Asymmetry explains why a strict validator is often more valuable than a better generator. Judgment Compression explains how to encode preference among multiple valid options. Together they show that specification is broader than syntax. It includes the rules by which the system recognizes acceptable behavior and the ranking logic by which it prefers one acceptable option over another.

This is also why teams fail when they treat evaluation as a separate phase rather than part of the specification itself. A metric is not just a reporting tool. It is a declaration of what the system is optimizing toward. If the metric rewards verbosity, the optimizer will learn verbosity. If the validator checks only shape, the model will learn to produce beautifully formed nonsense. Specification is incomplete until the feedback function is explicit.

from pydantic import BaseModel, Field


class TicketReply(BaseModel):
    customer_id: str
    intent: str = Field(pattern="^(refund|cancel|billing|support)$")
    confidence: float = Field(ge=0.0, le=1.0)
    reply_text: str


def should_escalate(reply: TicketReply) -> bool:
    if reply.confidence < 0.65:
        return True
    if reply.intent in {"refund", "cancel"}:
        return True
    return False

The interesting part is not the syntax. It is the hierarchy. The prompt may help the model produce a good TicketReply, but the schema defines what a reply is, and the escalation function defines what the runtime should do with it. Those two code artifacts are specification. The prompt is assistance.

Schemas define the shape of correctness. Metrics, validators, and escalation rules define its operational meaning.

What This Changes in Practice

Teams that adopt Code as Specification stop debugging with prose alone. When a failure appears, they ask which artifact is missing or too weak. Do we lack an input contract? Is the output schema too permissive? Is the validator checking syntax but not consequence? Is the metric rewarding the wrong behavior? Should this branch escalate instead of retry? Those questions lead to system improvements that survive model swaps and prompt rewrites.

It also changes how teams collaborate. Product can argue about policy dimensions. Engineering can encode them. Evaluation can measure them. Security can constrain them. None of that depends on one magical prompt whisperer holding the whole system in their head. The architecture becomes legible across functions because the key rules live in artifacts everyone can inspect.

The practical payoff is portability. Swap one frontier model for another, change vendors, or retrain a smaller specialist model, and the specification still holds if it lives in contracts, validators, and metrics. When the prompt is the only real artifact, every model migration becomes a rediscovery exercise. When code carries the specification, migration becomes a bounded adaptation problem instead of a rewrite of organizational memory.

This is why the principle compounds with the rest of the Arizen corpus. The Cognitive Firewall wraps the model in deterministic boundaries. The Probabilistic State Machine turns long tasks into typed steps. The Golden Dataset preserves evaluation memory. Code as Specification is the broader hierarchy that makes all of those moves feel natural instead of optional.

The future of production AI is not prompt maximalism. It is prompt humility. The prompt still matters, but it matters as one layer in a larger system whose real specification lives in code.


Subscribe to Arizen

Architecture notes for teams shipping AI systems into real use.
[email protected]
Subscribe