Module 09
Specifications that Preserve Judgment
On this page
In plain words
Think of guiding a junior at a college fest. Say nothing and he guesses badly; hand him a 40-step sheet and he follows your mistakes exactly. A good task for an agent sits in between: you fix the outcome, the rules that must never break, and the proof you will accept, and you leave the reversible choices to the agent.
How it flows
- 1Write the outcome
- 2State invariants
- 3Show examples
- 4Mark each decision
- 5Name the proof
- 6Hand it over
A tiny example
spec = {
"outcome": "refund an order",
"invariants": ["never refund twice"],
"decisions": {
"live payment gateway": "locked",
"retry count": "bounded: max 3",
"file layout": "delegated",
},
"proof": ["unit test", "audit log"],
}
hand_to_agent(spec)Notice that no line says how to write the code, only what must hold and who decides what.
What you will learn
- Why telling an agent too little and telling it too much both fail.
- The six parts of a good task description: outcome, invariants, examples, non-goals, decision policy, proof.
- How to mark each decision as locked, bounded, or delegated.
- Why the proof you ask for must match the claim you are making.
The problem, simply
See, think about a college fest. You are the coordinator and Rahul is doing the stage setup.
If you tell him only "make the stage look nice", he will guess. Maybe he puts the speakers in front of the entry gate. Nice for him, disaster for the crowd.
Now go to the other extreme. You hand him a 40-step sheet: this pole here, this cloth there, this light at 30 degrees. He follows it exactly. But you wrote that sheet before seeing the hall, and the hall has a pillar in the middle. He follows your wrong plan perfectly.
Both failed. First time you gave no boundary. Second time you took away his eyes.
Working with a coding agent is exactly this. Under-specify and it guesses your system. Over-specify and it copies your design, mistakes included. The useful thing sits in the middle: fix what must not move, leave the rest to judgment.
The idea
A specification is a boundary, not a screenplay
A screenplay says every line. A boundary says "stay inside this ground, and show me proof at the end". You want the second one.
A good specification has six parts:
- Outcome — what result someone can actually observe.
- Invariants — things that must stay true, always, no exceptions.
- Examples — concrete cases that show what you actually mean.
- Non-goals — nearby things you are deliberately not doing.
- Decision policy — for each choice, who decides.
- Proof — what evidence you will accept before calling it done.
- 1Outcome
- 2Invariants
- 3Examples
- 4Non-goals
- 5Decision policy
- 6Proof
Three decision modes
This is the heart of it. Every decision in a task falls in one of three buckets.
Locked. The agent must not choose. Use it for authority, safety, money, public behaviour, or anything you cannot undo. "Never write to the production database" is locked.
Bounded. The agent may choose, but only inside limits you state. "Retry at most 3 times." "Use only the standard library." It picks; you fenced the field.
Delegated. The agent owns the choice and just tells you what it picked. Variable names, how to split a file, the order of helpers. Cheap, reversible, not worth your time.
Which bucket? Two questions: how bad is it if this is wrong, and how easy is it to undo?
- 1Look at decision
- 2High consequence?
- 3Locked
- 4Human decides
TipTip: If you find yourself locking a decision and cannot say what breaks when it goes the other way, that decision probably belongs in delegated.
A worked example
Suppose Priya is building a refund feature for a small e-commerce site. She asks an agent to do it.
Bad version: "Add a refund API, make it robust and production-ready." Robust is not executable, so the agent invents its own idea of robust.
Better version, as the six surfaces:
- Outcome: a POST endpoint that refunds an order and returns the refund id.
- Invariants: an order can never be refunded twice; the refund can never exceed the paid amount; every attempt goes to the audit log.
- Examples: ₹499 refund on a paid order succeeds. A second refund on the same order errors. A ₹600 refund on a ₹499 order errors. A refund on a cancelled order is forbidden.
- Non-goals: partial refunds, refund emails.
- Decision policy: calling the live payment gateway is locked (test mode only). Retry count is bounded to 3. Helper function placement is delegated.
- Proof: unit tests for the amount rules, plus an audit-log check showing the second attempt was blocked.
Notice: Priya never said how to write the code. She said what must never break, showed four cases, and named the evidence.
Examples and invariants are different tools
Examples show intent. Invariants state a rule that holds everywhere.
You need both. Four passing examples do not prove "an order can never be refunded twice" — only that those four orders behaved. And an invariant alone can be read wrongly; the example pins down what you meant.
Remember: examples reveal intent, invariants state what must always hold. Never let one replace the other.
Proof must match the claim
The evidence has to sit at the same layer as the claim.
- A unit test proves one function behaves.
- A wire test proves data survives being sent and received.
- A browser or end-to-end run proves a user journey actually works.
- Replaying a set of past cases proves behaviour across real variety.
- An audit log proves authority limits held.
If your claim is "the checkout flow works", a passing unit test on the price helper is not proof. Different layer.
WarningWarning: Accepting a lower-layer proof for a higher-layer claim is how teams ship things that pass every test and still break for the user.
Keep the reasons
When you lock or bound something, write one line saying why. Six months later, when new evidence arrives, the next person can revise it deliberately instead of digging through old chats.
A specification should change when evidence changes. It just should not change by accident.
Build it
This little program takes a specification, checks that all six surfaces are present, and checks that every decision has a valid mode and a reason. It runs with a fake reviewer, no real model.
# spec_check.py - validate a specification before handing it to an agent
SURFACES = ["outcome", "invariants", "examples", "non_goals",
"decision_policy", "proof"]
MODES = {"locked", "bounded", "delegated"}
spec = {
"outcome": "POST /refund refunds an order and returns a refund id",
"invariants": [
"an order can never be refunded twice",
"refund amount never exceeds the paid amount",
],
"examples": [
{"case": "normal", "input": "paid 499, refund 499", "expect": "ok"},
{"case": "repeat", "input": "refund twice", "expect": "error"},
{"case": "over", "input": "paid 499, refund 600", "expect": "error"},
],
"non_goals": ["partial refunds", "sending refund emails"],
"decision_policy": [
{"choice": "call live payment gateway", "mode": "locked",
"why": "real money, cannot be undone"},
{"choice": "retry count", "mode": "bounded",
"why": "at most 3, keeps latency sane"},
{"choice": "helper function order", "mode": "delegated",
"why": "cheap and reversible"},
],
"proof": [
{"claim": "amount rules hold", "evidence": "unit test"},
{"claim": "authority held", "evidence": "audit log"},
],
}
def check(s):
problems = []
for name in SURFACES: # 1. every surface present
if not s.get(name):
problems.append("missing surface: " + name)
for d in s.get("decision_policy", []): # 2. modes are valid
if d["mode"] not in MODES:
problems.append("bad mode: " + d["mode"])
if d["mode"] in ("locked", "bounded") and not d.get("why"):
problems.append("no reason for locked/bounded: " + d["choice"])
for p in s.get("proof", []): # 3. no empty evidence
if not p.get("evidence"):
problems.append("claim with no evidence: " + p["claim"])
return problems
issues = check(spec)
print("surfaces present:", len([n for n in SURFACES if spec.get(n)]), "/ 6")
for d in spec["decision_policy"]:
print(" {:<28} {}".format(d["choice"], d["mode"].upper()))
print("problems:", issues if issues else "none - safe to hand over")
# now break it on purpose and see the checker catch it
broken = dict(spec)
broken["proof"] = []
broken["decision_policy"] = [{"choice": "write to prod", "mode": "delegated"}]
print("after breaking it:", check(broken))Look at two things. The good spec reports six surfaces and no problems. Then we empty the proof list, and the checker flags it immediately. That is the whole habit: catch a bad boundary before the agent starts, not after.
Where you will see this
- Claude Code and Cursor work far better when your task says what must not break instead of describing every step.
- GitHub Copilot in a large repo: teams keep a rules file that locks style and forbidden imports, and leaves the rest open.
- Support bots at banks and telcos: "never quote a final settlement amount" is a locked decision baked into the prompt.
- Swiggy or Zomato style assistants: suggesting restaurants is free, but placing the order and charging money is a locked, human-confirmed step.
- Internal tools at TCS, Infosys or a startup where an agent touches a database — read access delegated, write access locked.
Common mistakes
- Adjectives instead of examples. "Make it robust and clean" means nothing. The agent invents a definition, and it will not be yours.
- Locking everything. That is a screenplay. You get no benefit from the agent's judgment and you inherit every mistake in your plan.
- Locking nothing. Then a cheap mistake and an irreversible one look the same. Money moves, data gets deleted.
- Proof from the wrong layer. A unit test does not prove a user journey. This is the most common way a "finished" feature turns out broken.
- Not writing why a decision was locked. Later nobody knows if the lock still matters, so it either stays forever or gets removed blindly.
If they ask in an interview
Q: How do you write a task for a coding agent so it does not go off track?
A: I write it as a contract with six parts: outcome, invariants, examples, non-goals, decision policy and proof. I fix what must never break and what evidence I will accept, and leave reversible implementation choices to the agent. That constrains risk without dictating the design.
Q: What does it mean to mark a decision as locked, bounded or delegated?
A: Locked means the agent must not choose — authority, safety, money, public behaviour. Bounded means it may choose inside stated limits, like a retry cap. Delegated means it owns the choice and just explains it, like naming or file structure. I pick the bucket by consequence and reversibility.
Q: Why can examples not replace invariants?
A: Examples are concrete cases that show intent, but a handful of passing cases can never prove a universal rule like "never refund twice". Invariants state what must hold for every input. Examples pin down meaning; invariants state the guarantee.
Try these
- Take one ticket from a past college project and rewrite it into the six surfaces. Count how many lines were adjectives with no meaning.
- Take a task you wrote as step-by-step instructions. Replace three steps with one invariant and two examples. Does it still say what you wanted?
- Mark every decision in that spec as locked, bounded or delegated. For each locked one, write one line saying what breaks otherwise. Delete the locks you cannot justify.
- Extend the program above so that a decision marked delegated but containing words like "production", "delete" or "payment" is flagged as suspicious.
Words, simply
| Word | Meaning in simple words |
|---|---|
| Specification | A written contract saying what must be true, not how to build it |
| Invariant | A rule that must hold every single time, no exceptions |
| Non-goal | Something nearby that you are deliberately not building |
| Locked decision | A choice the agent is not allowed to make |
| Bounded decision | The agent chooses, but only inside limits you gave |
| Delegated decision | The agent chooses freely and explains what it picked |
| Proof | The evidence you accept before saying the work is done |
| Audit log | A record of what the system did, used to check it stayed in bounds |
Quick recap
- Under-specifying makes the agent guess; over-specifying makes it copy your mistakes. Aim for a boundary, not a script.
- Sort every decision into locked, bounded or delegated using consequence and reversibility.
- Ask for proof at the same layer as your claim, and write down why each lock exists.