Outcome first, then a spec for one slice. Two human approvals, an optional third before release, and an evaluation that checks the result, not only the tests.
A test suite can go green and still miss the rule I cared about. A salary below the posted range must never become an offer. A salary above it becomes an offer only after an approver says yes. I can write a spec that lists every status code and still miss one of those. So I don't choose outcome-driven development or spec-driven development. I run them in that order. I stand at two gates before any code is written. The two names Outcome-Driven Development , ODD, names a result a person can observe, the constraint that must hold while I chase it, and the evidence that both happened. I own those three. The design may change. The result may not. Spec-Driven Development , SDD, makes a structured spec the source of truth for me and for the coding agent. Birgitta Böckeler, writing on Martin Fowler's site, describes three levels: spec-first, spec-anchored, and spec-as-source. GitHub Spec Kit walks constitution, specify, plan, tasks, then implement. Thoughtworks adds a warning. A heavy spec is a poor fit for a small fix. The ladder from test-driven, to behavior-driven, to spec-driven, to outcome-driven is one public framing from Jonathan Spooner. It isn't a standard. I use it as a reminder. The spec tells the agent what to build. The outcome tells me whether it mattered. ODD alone leaves the agent free to invent a design that seems to meet a vague sentence. SDD alone can ship a perfect contract for a result nobody needed. Neither is enough. The path The agent drafts the files between the gates. It doesn't walk past a gate that's still waiting. Figure · the hybrid path A no at approval 1 revises the proposal. A no at approval 2 revises the spec. You ask again. A fail still returns to step 3 or 4. The arrow into release is a later pass, after the proposal or the spec has changed. It isn't permission to ship a failed evaluation. Approval 3 is optional. After review, the outcome owner reads EVALUATION.md once more and says yes before release. A no does not release. It joins iterate, back to the proposal or the spec. I add the gate when the slice moves money, access, or candidate data. The offer slice qualifies, because a wrong write stores a salary. A copy fix doesn't. A third gate there only slows the release. After release, production monitoring watches the outcome. A spec can't see a bad write that lands next week. Who writes each file, and what blocks the next step Step File Who writes it What blocks the next step 1. Define the outcome OUTCOME.md A person Nothing starts without it 2. Discovery DISCOVERY.md The agent drafts, the person corrects facts A wrong fact will poison the proposal 3. Propose solutions PROPOSAL.md The agent Human approval 1 4. Generate the spec SPEC.md The agent, from the approved proposal Human approval 2 5. Implement Source and unit tests The agent The spec's clauses 6. Evaluate EVALUATION.md The agent runs the evidence, a person reads the verdict A fail 7. Review The diff A person That person's yes on the diff Human approval 3 (optional) EVALUATION.md The outcome owner A last yes before release, for money, access, or candidate data The offer, written as five files I use one hiring rule from a lab I published. It isn't the ATS I run. The open job is Senior Backend Engineer, posted at $120,000 to $160,000 a year. Alice is the technical recruiter. She can ask for the offer. Bob is the approver. He's the person who may store a salary above that range. $190,000 is above the band. $90,000 is below it. Spring AI can pause the tool call. It doesn't know the band. That part stays application code. OUTCOME.md is the result, not a task list. OUTCOME.md · the offer outcome A salary below the role band never becomes an offer. A salary above the band becomes an offer only after an approver allows it. No offer is stored until a person allows the write. constraint A recruiter's Allow on a salary above the band stays pending. The offer is not stored. evidence A count of offer rows below the band. It must be zero. Every offer above the band has an approver's Allow on it. The trace that links the pause to the later allow. baseline, target, window Baseline: the evidence query, run once before release. Target: zero offers below the band. Zero above it without an approver. Window: 30 days of live traffic after release. may change Class names, prompt text, log wording. may not change The outcome and the constraint. If the agent writes this file, it'll define a result it can already satisfy. I write it. DISCOVERY.md is what is already true, and what I'm refusing to pull into this slice. The pause already exists in the lab. The tech-screen scorecard doesn't. A candidate must not reach onsite on a failing card, and a No must not reject them by itself. That's a second outcome, with its own spec, after this one is evaluated. PROPOSAL.md is three options and one recommendation. Option A puts the band only in the prompt. It's fast, and the model can talk past it. Option B specifies the whole funnel, from applied to hired, and generates it. The spec will be stale before the first review. Option C freezes a spec for the offer slice only. The outcome stays in OUTCOME.md . I recommend C. Human approval 1 is a yes or a no on that recommendation. Silence is not a yes. A no goes back to the proposal, you revise it, and you ask again. Until that yes, nobody generates SPEC.md . SPEC.md is the contract for the slice. For the spec, done means these clauses pass. SPEC.md · the offer slice FR-001 When Alice extends 190000, the system shall return 202, risk HIGH. FR-002 When the owner allows it, the system shall return 403 and keep the approval pending. FR-003 When the approver allows it, the system shall return 200 and store the offer. FR-101 If Alice extends 90000, then the system shall fail with salary out of band, and GET /offer shall stay 404. Each clause has an ID and one shape: when this happens, the system shall do that. A reviewer can see a missing case as a missing ID. If a clause needs a decision I haven't made, the agent writes [NEEDS CLARIFICATION] instead of guessing, and approval 2 waits. I can change this contract. I change it in the spec, then I ask for approval 2 again. I don't quietly patch the code and leave the spec behind. Approval 2 is what allows implementation. Source and unit tests come after that yes, not before. The agent may pick the classes. It may not drop a clause because the clause was awkward. If a test passes and the outcome evidence is still missing, implementation isn't finished. That gap belongs in the evaluation, where I can see it, not in a production surprise. A green spec is the contract for one slice. It isn't the outcome. Pass, fail, and what iterate goes back to EVALUATION.md asks two questions, in this order. Did the outcome move, and does the constraint still hold? Did every clause in the spec pass? Here are two failures I'd expect. Neither is a bug I found in the lab. They show why the outcome check comes first. Every clause passes, and an offer at $119,999 is stored. FR-101 named $90,000, not the edge of the band. The outcome counts every offer below the band, not two sample numbers. That is a spec gap. I add a boundary clause, FR-201, to SPEC.md , and I get approval 2 again. Every clause passes, and the server restarts while Alice's $190,000 request is waiting. A recovery job resumes the paused call and stores the offer. Nobody allowed it. The constraint broke on a path the spec never named. That is a proposal problem, because resuming paused calls was part of the approach. I edit PROPOSAL.md so a restart never resumes a paused write, and I get approval 1 again. I don't edit OUTCOME.md to make a failure look like a pass. Review is a person, reading the diff against the approved spec and against EVALUATION.md . Release follows that review, or approval 3 when the slice needs it. Production monitoring runs the same evidence on live traffic. A green build from last week isn't that cou...