Write Specifications That Preserve Judgment
A useful specification fixes invariants and evidence while leaving reversible implementation choices open. It is a decision boundary, not a screenplay.
Lesson 51 of 54Learn + Build~75 minutesPython (stdlib)
Before this: Lesson 50 of Phase 14
On this page
Learning Objectives
- Separate outcome, invariants, examples, non-goals, and proof.
- Mark decisions as locked, bounded, or delegated.
- Preserve agent judgment where choices are cheap and reversible.
- Require human checkpoints where consequence or public behavior changes.
Two Bad Extremes
An underspecified task asks an agent to guess the system. An overspecified task asks it to transcribe a design that may already be wrong.
The useful middle is an executable contract:
| Surface | Purpose |
|---|---|
| Outcome | The observable result |
| Invariants | Conditions that must always remain true |
| Examples | Concrete cases that reveal intent |
| Non-goals | Adjacent behavior intentionally excluded |
| Decision policy | Which choices are locked, bounded, or delegated |
| Proof | Evidence required before completion |
Three Decision Modes
- Locked: the agent must not choose. Use for public compatibility, authority, safety, irreversible cost, or a product commitment.
- Bounded: the agent may choose inside explicit limits. Use for search budgets, retry counts, allowed dependencies, or a known interface family.
- Delegated: the agent owns the choice and must explain it. Use for local structure, names, reversible refactors, and implementation details.
Specify Behavior Through Examples
Examples compress intent better than adjectives. “Helpful,” “robust,” and “production-ready” are not executable. A small set of normal, edge, failure, and forbidden examples gives both the builder and verifier something concrete.
Examples do not replace invariants. One passing case cannot prove a universal safety rule.
Proof Must Match the Claim
- A unit test proves a local function contract.
- A wire test proves serialization and transport behavior.
- A browser journey proves an interface path.
- A replay set proves behavior over representative cases.
- An audit log proves that authority boundaries held.
Do not accept a lower layer as proof of a higher-layer claim.
Preserve Unknowns Deliberately
A specification can say “the implementation may choose any read-only source that returns within the time budget.” That is not vagueness. It is an intentional delegated decision with a boundary and proof.
Specifications should evolve when evidence changes. Preserve the reason behind locked and bounded choices so later teams can revise them without archaeology.
Build It
The lab validates every contract surface, checks decision modes, and writes outputs/executable-specification.json.
python3 code/main.py
python3 -m unittest discover code/tests -v
Move the production-write decision from locked to delegated. Explain why the schema accepts the value but the product risk does not.
Exercises
- Convert a backlog ticket into the six specification surfaces.
- Replace three implementation instructions with one invariant and two examples.
- Mark every decision and justify each locked or bounded choice.
- Add a proof receipt for every invariant.
- Remove a constraint that has no evidence or risk rationale.
Further Reading
- Nuseibeh and Easterbrook, Requirements Engineering: A Roadmap, for the relationship among goals, precise specifications, validation, agreement, and evolution.
- Zave and Jackson, Four Dark Corners of Requirements Engineering, for separating environmental assumptions, requirements, and specifications.
- Gotel and Finkelstein, An Analysis of the Requirements Traceability Problem, for preserving why a requirement exists and where it came from.
What You Keep
Keep outputs/executable-specification.json. It becomes the contract that coding agents and human reviewers share.
Check yourself
Quick quiz
What is the useful middle between under- and over-specification?
- No plan
- A longer request
- A complete implementation script
- An executable contract that fixes invariants and evidence while preserving reversible choices
Show the answer
D. An executable contract that fixes invariants and evidence while preserving reversible choices The contract constrains outcomes and risk without scripting every implementation detail.
Which decision should usually be locked?
- A local variable name
- Whether the system may write to production
- A reversible file split
- The order of private helper functions
Show the answer
B. Whether the system may write to production Authority and high-consequence behavior require a human-owned decision.
What is a bounded decision?
- The agent cannot act
- The agent may choose inside explicit safe limits
- The choice is random
- There is no proof
Show the answer
B. The agent may choose inside explicit safe limits Bounds preserve useful judgment while keeping risk controlled.
Why pair examples with invariants?
- They reduce all ambiguity
- Examples prove every universal rule
- They replace outcomes
- Examples reveal intent while invariants state conditions that must always hold
Show the answer
D. Examples reveal intent while invariants state conditions that must always hold Concrete cases and universal constraints serve different roles.
What proof matches a user-journey claim?
- A browser or end-to-end journey observation
- A type annotation
- A local unit test only
- A plan
Show the answer
A. A browser or end-to-end journey observation Proof must operate at the same layer as the claim.
Why record rationale for locked decisions?
- To prevent all change
- To make the file longer
- So later evidence can revise the choice without archaeology
- To hide uncertainty
Show the answer
C. So later evidence can revise the choice without archaeology Traceable reasons let specifications evolve deliberately.
Questions people ask
How detailed should a software specification be?
Detailed on outcomes, rules that must always hold, examples, what is excluded, and proof of completion. Light on implementation details that are cheap to change, so the builder or agent can use judgment there.
Which decisions should an AI agent be allowed to make?
Let it own reversible, local choices like naming and code structure. Set limits on things like budgets and retries. Keep decisions about public behavior, permissions, irreversible costs and safety locked for a person.
Lessons from AI Engineering from Scratch by Rohit Ghumare and contributors, MIT license. Notes for owners by Lozo. License