Contract
Context
When one part of a system uses another, both sides carry expectations. A contract is the explicit or implicit promise about what will happen across an interface. It operates at the architectural scale, governing the agreements that hold components together.
Contracts can be formal (a typed function signature, an API schema, a service-level agreement) or informal, like the unwritten assumption that “this function never returns null.” Formal contracts are enforceable by machines. Informal contracts live in developers’ heads and break when someone new, human or agent, arrives who was never told the rules.
Problem
How do you ensure that the two sides of an interface agree on what is expected — and stay in agreement as both sides evolve independently?
Forces
- Tight, detailed contracts are safe but restrictive. They limit how implementations can change.
- Loose, vague contracts are flexible but dangerous. Misunderstandings cause silent failures.
- Contracts that live only in documentation drift out of sync with the code.
- Every consumer of an interface has its own interpretation of what the contract means.
Solution
Make contracts as explicit as the situation warrants. For internal modules that change frequently, typed function signatures and automated tests may suffice. For published APIs consumed by external parties, you need versioned schemas, clear error codes, and documented behavior for edge cases.
A good contract specifies at minimum:
- Preconditions — what must be true before calling.
- Postconditions — what will be true after a successful call.
- Error behavior — what happens when things go wrong.
- Invariants — what is always true, regardless of inputs.
In agentic coding, contracts matter even more. An AI agent can’t ask clarifying questions mid-execution the way a human colleague can. If a tool’s contract says it returns a list but sometimes returns null, the agent’s downstream logic breaks. Clear contracts let agents plan multi-step workflows with confidence.
How It Plays Out
A team defines a REST API for user management. The contract specifies: POST to /users with a JSON body containing email and name returns a 201 with the created user, or a 409 if the email already exists. A frontend developer and a mobile developer both build clients independently. Because the contract is explicit and tested, both clients work correctly without coordination.
An AI agent is given a create_file tool. The tool’s contract states: “Creates a file at the given path. Returns the file path on success. Raises an error if the file already exists.” The agent uses this contract to plan: it checks for existence first, then creates. If the contract had been silent on the “already exists” case, the agent would have learned about it only through a runtime failure, wasting a step and potentially corrupting state.
The most dangerous contracts are the ones nobody wrote down. If a behavior is relied upon, it is part of the contract — whether or not it is documented. When taking over a codebase, look for implicit contracts in the tests: what do the tests assume?
“Define the contract for the POST /users endpoint: it accepts email and name in JSON, returns 201 with the created user on success, and returns 409 if the email already exists. Write contract tests that verify both cases.”
Consequences
Explicit contracts reduce misunderstandings, enable independent development, and make automated testing straightforward (contract tests verify that implementations honor their promises). They are especially valuable in agentic workflows where the consumer cannot exercise judgment about ambiguous cases.
The cost is maintenance. Contracts must be kept in sync with implementations. A contract that promises something the code no longer does is worse than no contract at all; it’s an active source of misinformation. Automated contract testing (where tests verify the contract, not just the implementation) helps, but it requires discipline.