The Agentic Contract: Why a Prompt Is Not a Specification

A raw prompt can express intent, but it cannot define a real behavior contract. The Agentic Contract turns one agent capability into a typed, executable specification with explicit inputs, outputs, failure modes, and economic bounds.

Most production agents are still specified by a long prompt and a vague hope that the model will "generally do the right thing." The prompt contains policy, output format, edge-case handling, tone, tool usage rules, and a handful of scar-tissue exceptions accumulated from previous failures. It looks like a specification because it is long. It is not a specification. It is an instruction block addressed to a stochastic process.

This category error is responsible for a large share of production agent failures. The model is not usually the weak link. The weak link is that the system has no enforceable behavior contract. A prompt can express intent. It cannot define a type signature, enumerate failure modes, guarantee latency bounds, or tell the surrounding system what to do when the model returns something structurally valid but operationally unusable.

The right abstraction is an Agentic Contract: a typed, executable specification for one agent capability. The prompt becomes a derived artifact. The contract becomes the primary source of truth.

TL;DR — Key Takeaways:
  • A prompt is an instruction. A specification is an enforceable boundary. Treating the first as the second is the core design mistake.
  • An Agentic Contract defines four things explicitly: input bounds, output schema, failure semantics, and economic constraints.
  • Pydantic or an equivalent typed layer makes the contract testable in CI: schema validity, confidence thresholds, retry paths, latency budgets, and cost caps become assertions instead of wishes.
  • The contract should be written before the prompt. The prompt is then compiled from the contract, not the other way around.
  • Contracts eliminate an entire class of production failures, but they do not prove semantic correctness. For that, you still need evals and a Golden Dataset.

The Category Error: A Prompt Is Not a Specification

Prompts feel like specifications because they are written in imperative language:

You are a careful financial analyst.
Extract the company name, founding year, and annual revenue.
Return valid JSON.
Do not hallucinate.
If uncertain, say so.
Keep latency low.

This is not meaningless. It gives the model useful direction. But direction is not the same thing as a contract. A real specification answers questions the system can enforce mechanically:

  • What is the maximum input size before truncation?
  • What exact fields are required in the output?
  • What confidence threshold is acceptable for each field?
  • What failures are recognized as first-class states?
  • What deterministic action follows each failure?
  • What are the latency and cost limits for this capability?

A raw prompt answers none of these with machine-checkable precision. It says what you want. It does not define what the rest of the system can verify.

A prompt tells the model what you hope will happen. A specification tells the system what it can check, reject, retry, and escalate.

This distinction matters even more once you adopt a Probabilistic State Machine. The state machine governs transitions: retry, validate, escalate, abort. But each node in that machine still needs a behavior contract. In most codebases, that contract is a raw string. Sometimes it is a Jinja template. Occasionally it is a YAML file that someone calls "config" and nobody reviews with the rigor applied to code.

Capability Surface Raw Prompt Output Schema Only Agentic Contract
Input limits and preconditions Implicit Weak Explicit
Typed output shape No Yes Yes
Failure mode enumeration No No Yes
Deterministic recovery paths No No Yes
Latency and cost budgets Aspirational Absent First-class
CI-testable behavior contract No Partial Yes

The table is the point. Output schemas are useful, but they solve only one slice of the problem. Production agents fail on the whole operating surface, not just on the final JSON shape.

What the Agentic Contract Actually Contains

An Agentic Contract is best understood as four contracts collapsed into one object.

1. Input Contract. What kind of artifact can this capability accept? How large can it be? What pre-processing assumptions hold? What must be grounded before the model ever sees it?

2. Output Contract. What fields are required? What format must they take? What confidence or provenance rules must accompany them?

3. Failure Contract. Which failures are recognized as structural states rather than miscellaneous surprises? Schema violation, refusal, hallucination, timeout, partial completion, tool failure, low confidence.

4. Economic Contract. What are the acceptable latency and cost bounds for this capability? In most real systems, "works" is not enough. The agent must work inside a throughput and cost envelope.

Once these four surfaces are typed, they stop being editorial commentary and become code the system can enforce.

from enum import Enum
from typing import Literal
from pydantic import BaseModel, Field

class FailureMode(str, Enum):
    SCHEMA_VIOLATION = "schema_violation"
    LOW_CONFIDENCE = "low_confidence"
    HALLUCINATION = "hallucination"
    TIMEOUT = "timeout"
    REFUSAL = "refusal"
    PARTIAL = "partial"

class EscalationRule(BaseModel):
    trigger: FailureMode
    action: Literal["retry", "fallback_model", "human_review", "abort"]
    max_retries: int = Field(default=2, ge=0, le=5)
    fallback_model: str | None = None

class CompanyExtractionContract(BaseModel):
    """Contract for extracting structured company facts from filings."""

    # Input contract
    input_type: Literal["sec_10k", "sec_10q", "annual_report"]
    max_input_tokens: int = Field(default=12_000, gt=0)

    # Output contract
    required_fields: list[str] = [
        "company_name",
        "founding_year",
        "annual_revenue_usd",
    ]
    confidence_threshold: float = Field(default=0.85, ge=0.0, le=1.0)

    # Economic contract
    max_latency_ms: int = Field(default=3000, gt=0)
    max_cost_per_call_usd: float = Field(default=0.04, ge=0.0)

    # Failure contract
    failure_modes: list[FailureMode] = list(FailureMode)
    escalation: list[EscalationRule] = [
        EscalationRule(
            trigger=FailureMode.SCHEMA_VIOLATION,
            action="retry",
            max_retries=2,
        ),
        EscalationRule(
            trigger=FailureMode.HALLUCINATION,
            action="fallback_model",
            fallback_model="high-reliability-tier",
        ),
        EscalationRule(
            trigger=FailureMode.TIMEOUT,
            action="abort",
        ),
        EscalationRule(
            trigger=FailureMode.REFUSAL,
            action="human_review",
        ),
    ]

This object is small, but its consequences are large. The moment the contract exists, the surrounding system can produce deterministic checks:

  • max_latency_ms becomes a measurable SLO gate.
  • required_fields become schema assertions.
  • confidence_threshold becomes a validation rule.
  • failure_modes become an explicit state space.
  • escalation becomes deterministic routing logic.

That is what makes the contract more than a style preference. It creates a surface the rest of the system can reason about.

An Agentic Contract turns one model capability into four enforceable surfaces
Figure 1: An Agentic Contract turns one model capability into four enforceable surfaces

Why YAML and JSON Schema Are Not Enough

The obvious objection is that teams already have config files, JSON Schema, or tool definitions. That is true, but those artifacts are usually too narrow.

JSON Schema is excellent for output shape. It says little about economics, retries, failure semantics, or model-routing policy.

YAML config is good for parameter storage. It is bad at expressing constraints, invariants, and import-time validation. Untyped config fails socially: somebody puts a string where a float should be, or adds a new key that no reviewer notices because config is treated as a bag of knobs rather than executable policy.

Raw tool definitions solve only the model-facing interface. They do not define how the system behaves when the call is too slow, too expensive, or structurally wrong.

The goal is not to store agent settings. The goal is to create an executable boundary where invalid behavior fails early, visibly, and mechanically.

The difference is governance as much as syntax. A typed model lives in the same review pipeline as code. It is diffed. It is linted. It is type-checked. It can be imported by tests. A raw prompt buried in a string constant is almost invisible to the engineering system. It changes behavior, but it does not enjoy the protective machinery the rest of the codebase already has.

The Prompt Should Be Compiled from the Contract

The most important process change is temporal: write the contract first.

Most teams do the opposite. They start with a prompt, run a few examples, discover output variability, then gradually bolt on schema enforcement, retries, fallback models, timeout handlers, and cost limits. The contract emerges late as scar tissue.

Contract-first development inverts the sequence. You define the capability boundary first, then derive the prompt from that boundary. The prompt is no longer a primary design artifact. It is a rendering of the contract for a language model.

def compile_system_prompt(contract: CompanyExtractionContract) -> str:
    return f"""
You extract structured company data from {contract.input_type}.

Required fields:
- {", ".join(contract.required_fields)}

Rules:
- Return valid JSON only.
- If confidence for any required field is below {contract.confidence_threshold},
  return null for that field and explain uncertainty in the rationale field.
- Do not invent values not grounded in the source document.
- Prefer omission over hallucination.
"""

def validate_and_route(raw_output: dict, contract: CompanyExtractionContract):
    parsed = OutputSchema.model_validate(raw_output)

    if parsed.confidence < contract.confidence_threshold:
        return "LOW_CONFIDENCE"

    return "PASS"

This pattern has three practical benefits.

First, model mobility. If the prompt is compiled from the contract, swapping model families is much less disruptive. The contract remains stable while the model-specific phrasing can change.

Second, review clarity. A diff that changes confidence_threshold from 0.85 to 0.70 is legible. A diff that adds three paragraphs of prompt prose is not.

Third, composition. In a multi-agent pipeline, contracts compose cleanly. The orchestrator can reason about aggregate latency, unioned failure modes, or when to hand off to a human. This is much harder when each capability is described by prose alone.

This is also how the contract connects to the rest of the Arizen corpus. The state machine governs retries and escalation. The contract defines what those retries and escalations mean for one capability. The two patterns solve different layers of the same problem.

Contract-first development compiles the prompt from a typed boundary and routes failures deterministically
Figure 2: Contract-first development compiles the prompt from a typed boundary and routes failures deterministically

What an Agentic Contract Solves, and What It Does Not

It is important not to overclaim here.

An Agentic Contract is extremely effective for capabilities with well-bounded surfaces:

  • extraction
  • classification
  • routing
  • tool parameter generation
  • structured summarization
  • SQL or API request construction

These tasks benefit from typed boundaries because "correct enough" can be narrowed to a formal envelope.

Contracts are weaker for genuinely open-ended work:

  • creative ideation
  • exploratory research
  • long-form writing
  • conversation where the value lies in adaptive nuance rather than fixed outputs

And even in bounded tasks, the contract does not prove semantic truth. It can guarantee that the output is well-formed, within cost and latency bounds, and routed correctly after failure. It cannot prove that the agent's reasoning was sound or that a grounded-looking number was actually supported by the document. That is where evals and a Golden Dataset enter.

The contract enforces syntactic and operational correctness. Semantic correctness still lives in evaluation, retrieval quality, and human judgment.

This is not a weakness of the pattern. It is a boundary. Good architecture gets stronger when each layer does one thing well instead of pretending to solve the whole stack.

The Operational Consequence

Once you adopt Agentic Contracts, the daily engineering conversation changes.

You stop asking: "Should we tweak the prompt?"

You start asking:

  • Did we define the wrong failure modes?
  • Is the confidence threshold too loose for this workflow?
  • Should timeout route to retry, fallback, or abort?
  • Is this capability too expensive for synchronous use?
  • Is the input boundary too permissive for reliable extraction?

Those are better questions because they are architectural questions. They are legible in code review, testable in CI, and debatable in concrete terms. This matters because, as argued in Every AI Agent Is a Liar, a Slacker, and a Thief, production agent failures are not exotic. They are repetitive. The system should describe them explicitly instead of rediscovering them in production.

The broader implication is simple. If your most important behavior contract is a 200-line string that cannot be type-checked, diffed meaningfully, or composed across a pipeline, then the most important part of your system is not yet engineered. It is narrated.

That is fine for demos. It is not fine for production.

Subscribe to Arizen

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