The agent’s job was to wire the application to the partner’s API. The doc? Thirty pages of polished prose, with examples “for illustration purposes”. The generated HTTP client was gorgeous — and wrong. The field was email_address, not customerEmail; the amount was in cents, not euros; and canceled took one L. Three round-trips with production to discover everything the prose had never enforced.
Yesterday, tests as executable spec: tests verify behavior. Today, their twin on the artifacts map: schemas and contracts, which verify boundaries. Context that validates itself instead of asking to be believed. You’ll see: it’s not rocket science.
The wall socket
Look at the power socket on your wall. Nobody ever handed you a manual saying “please insert the plug the right way around”. No need: the standard gave the plug and the socket a shape that makes the mistake impossible. You can’t plug it in wrong — not because you’re careful, but because the geometry refuses.
That’s the whole difference between two families of documentation. Prose describes the boundary (“the amount is in cents”) and hopes the reader complies. A schema is the boundary: a malformed call doesn’t get through, full stop. Prose documentation is a warning sign; a machine-verifiable contract is the shape of the socket.
One contract per boundary
A machine-verifiable contract is a formal description of a boundary — formal enough for a tool to check it with no human in the loop. There’s one for every kind of boundary:
| Boundary | Contract | Who checks it |
|---|---|---|
| HTTP APIs | OpenAPI | client generators, contract tests in CI |
| Payloads, events, config files | JSON Schema | validators, at runtime and in CI |
| The inside of your code | .NET types: nullable reference types, records, enums | the compiler, on every build |
That third row sometimes surprises people: yes, your type system is a contract. A string? that distinguishes “may be null” from string “never null”, an enum listing the only three legal statuses, a record with required members… that’s the spec written into the compiler. And it gets checked more often than any document: on every single compile.
A concrete example, in full
On the contract side, an OpenAPI excerpt for a refund:
# openapi.yaml — excerpt
paths:
/orders/{orderId}/refund:
post:
operationId: refundOrder
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RefundRequest'
components:
schemas:
RefundRequest:
type: object
required: [amount, reason]
additionalProperties: false
properties:
amount:
type: integer
description: Amount in cents.
reason:
type: string
enum: [damaged, late, other]
Feed that file to a client generator — NSwag, Kiota, brand doesn’t matter — and you get C# with the exact shape of the socket:
// Generated from openapi.yaml — do not edit by hand
public partial record RefundRequest
{
public required int Amount { get; init; }
public required RefundReason Reason { get; init; }
}
public enum RefundReason { Damaged, Late, Other }
public partial class OrdersClient
{
public Task<RefundResponse> RefundOrderAsync(
Guid orderId, RefundRequest request, CancellationToken ct = default);
}
Now try adding a RefundToCard = true field: compile error, the member doesn’t exist. An amount as string? Rejected. A reason: "cancelled"? The enum has never heard of it. And the schema’s additionalProperties: false does the same job server-side, at runtime. The boundary isn’t documented anymore — it’s impossible to cross sideways.
Choosing your source of truth
One architecture question remains: which one is authoritative, the schema or the code?
- Schema first (contract-first): the OpenAPI file is written — and reviewed in PR — before the code; clients and server are generated from it. Ideal when several teams consume the same API.
- Code first: the C# code is authoritative, and the schema is generated on every build and published. Ideal when one team owns both ends.
Both work. The only fatal choice is the in-between: a hand-written schema and hand-written code, synchronized “whenever someone remembers”. The rule: one of the two directions must be automatic. Otherwise you don’t have a contract, you have two versions of the truth.
Why it’s worth double in the AI agent era
- The agent reads the schema and hits the target on the first try. Exact field names, types, required vs optional, enum values: everything prose leaves to guesswork, the schema states. An agent with
openapi.yamlin its context generates the correct call where an agent with a PDF generates a plausible hypothesis. - The agent can’t drift. If it invents something anyway, the compiler catches it at generation time and contract validation catches it in CI — before you do. The series formula gets sharper: the AI proposes, the contract rules first, the human arbitrates what’s left, the repository remembers. The feedback loop replaces vigilance.
- The token-to-value ratio is unbeatable. A fifty-line schema costs next to nothing in context and eliminates an entire class of hallucinations — invented fields, the most mundane AI mistake in the world. Few artifacts give back so much for so little.
The honesty moment
- A schema that’s published but never verified in CI is worse than nothing. It silently drifts from the implementation, and everyone — humans and agents — trusts a document that lies. A socket that lies is more dangerous than no socket at all. The guardrail: a CI job that compares schema and implementation on every build.
- Strict typing doesn’t validate semantics. A
decimal Amounthappily accepts a negative price; aDateTimeaccepts a delivery in 1987. Business invariants still have to be coded — the contract guards the boundary, not the meaning. - Contract-first has an entry cost. Generators to wire up, a pipeline to adjust, a team to convince to write YAML before C#. It’s an investment — it pays off from the second consuming team onward, and is debatable for an internal API consumed only by its own author.
In summary
- Prose describes the boundary; a contract is the boundary — like the standardized socket, it makes the mistake impossible instead of documenting it.
- One contract per boundary: OpenAPI for HTTP APIs, JSON Schema for payloads and config, .NET types as the spec inside the compiler.
- Schema first or code first, doesn’t matter — but one of the two directions must be automatic, and verified in CI.
- For agents: a schema in context yields correct calls on the first try, and whatever drifts anyway gets caught by the machine — the feedback loop replaces vigilance.
One YAML file reviewed in PR, one generator in the build, and a whole category of hallucinations — invented fields — vanishes from your code reviews. And that, honestly… it’s not rocket science.