Back to blog
Core

Agents Need Contracts, Not Trust (Part 1)

Agents make code cheap. They do not make intent obvious. Contracts give them a target, give tests something to derive from, and keep fast delivery from becoming fast debt.

#contract-driven development#ai agents#software quality#testing#architecture#series
Agents Need Contracts, Not Trust (Part 1) cover

Part 1 of a series. Contracts and formal specification are not new. The question is what they look like when agents write much of the code. This is an opening argument, not a finished method. The next posts will get concrete.

The problem: software is becoming fast fashion

Agents collapsed the cost of writing code. Good. The bill arrives later when teams mistake generated code for understood code. Software is becoming fast fashion: cheap to produce, easy to replace, and often somebody else's problem when it fails.

Writing code was never the hard part of quality. Knowing what the code must guarantee is. That intent needs a form both people and machines can check.

That is contract-driven coding.


This has happened before

Cheaper production makes acceptable quality cheap too. Most people do not need a handmade watch or a perfect camera. They need something good enough today. Software is heading there with agents as the assembly line, and much of it should.

The exceptions are not subtle. Databases, payments, medical devices, aerospace, and core infrastructure are bought on the promise that they will not do something stupid at 3 a.m. Toyota, Casio, Canon, and Sony won real markets on that kind of trust. They are not luxury brands. They are the reliable choice.

That is the useful analogy. Let agents make ordinary software cheap. For work that carries real consequences, make reliability the product. Contract-driven coding is one way to keep the speed without pretending correctness happened by accident.


The abstraction ladder

Software has layers. Product behavior sits at the top. Subsystems, modules, functions, lines, and tokens sit below it. Each layer has a different idea of correct.

We already write pieces of the specification. Product docs describe behavior. Types, docstrings, and tests pin down details. The pieces rarely agree, and almost never prove anything about each other.

The proposal is straightforward and not easy: write an explicit contract at each useful layer, verify the work against it, then let the agent write code. The contract gives the agent context and gives the engineer grounds to trust the result. That is moving fast on purpose.


What is a contract?

A contract is the honest answer to two questions: what does this thing promise, and what does it require in return?

For a function, that means its accepted inputs, returned values, preconditions, postconditions, invariants, side effects, error cases, and cost. It is not paperwork. It is the behavior a caller is allowed to rely on.

The questions stay the same as the system gets bigger.

  • A module contract names its exports, owned state, invariants, error types, and concurrency rules. "The index matches the log" and "no call leaves a half-open connection" are contracts.
  • A subsystem contract names its protocol, ordering, durability, failure behavior, service limits, and backpressure. "Writes are durable before acknowledgement" is a contract.
  • A product contract names what users can do, what must never happen, and which security, privacy, accessibility, and performance limits apply. "A confirmed payment is never double-charged" is a contract.

The syntax can change. The questions do not: what is promised, what can fail, and what does it cost?


The supposedly simple sum

Take sum, a function that accepts an array and returns a total.

function sum(values: number[]): number

Tests alone do not tell us what that means. Does it accept integers, floats, NaN, empty arrays, or nulls? Is the empty result zero or an error? Does it mutate the input? What can it throw?

Then "array" gets suspicious. The values might fit in memory, stream from disk, arrive over the network, appear slowly, or change while the calculation runs. Those are different functions wearing the same name.

No test suite discovers the intended function by accident. A contract bounds the problem first. Tests can then check behavior against a decision someone actually made.


Contracts make tests derivable

Once the contract is explicit, an agent can generate cases that follow it. The engineer can review those cases against the contract before implementation exists. Testing stops being a scavenger hunt for clever inputs.

The pattern works at every useful layer. Function tests follow function contracts. Module tests follow module contracts. Integration tests follow the promises made where components meet.

Gaps become obvious too. When a change breaks a documented promise three layers down, the failure belongs near the broken promise, not in a production incident two weeks later. The contract remembers what the code was meant to do.


Agents make this more important

Faster code generation does not make correctness cheaper. It makes vague intent more dangerous. When writing was slow, it forced some accidental reflection. Agents remove that pause. Intent now has to live somewhere explicit or it does not live anywhere useful.

A contract gives the agent context and gives the engineer a way to verify the result. That is the entire case. Context without checks produces plausible output. Checks without context produce busywork.

Contracts are design work

Writing a contract forces the useful questions: where does state live, what happens on failure, which limit matters, and what must stay true? That is design work. The implementation is often the easy part once those answers exist.

The contract also makes understanding portable. A new engineer should not need archaeological training to find a subsystem's intent. Written promises and executable checks are better than a warning in somebody's head.

Depth costs

Higher-level decisions constrain more of the system. Skipping that work does not save time. It moves the cost to the first incident, migration, or new engineer who has to guess what a boundary was supposed to mean.

Not every function needs a formal contract. That would be its own kind of theater. Put the rigor where failure is expensive: a load-bearing function, a subsystem boundary, or a user-visible guarantee.

Where this goes next

This is the argument. The rest of the series will turn it into a workflow:

  • Write contracts that are specific enough to check.
  • Derive a correctness harness from those contracts.
  • Give agents the contract as context and catch drift when they ignore it.
  • Decide which parts of a system deserve this level of care.

The aim is not more documentation. It is software people trust because someone decided what it should do before asking a model to write it.