Spec-Driven Development Might Be Dead; Fable Does It

Part 3 of 3 in The Model Ladder

"Use spec-driven development" means something specific when I say it to a model. It means: before you write code, write the requirements, the design and the task list, and let me check them. Kiro builds a whole IDE around that instruction, and my own specs in this repo follow its five-phase shape.

With Fable 5.2 the instruction is dead. I asked Fable for code and nothing else, and it wrote the spec, split the work and checked the result on its own, at about a seventh of the length my pipeline produces. What the instruction used to force, the model now does unasked.

One scope note before the evidence. This is a single experiment on one medium-sized task, a 400-line module, run twice per arm. Large work, the kind that spans several services or a migration, may still need the traditional spec workflow, and I come back to that near the end.

That was my guess going in, and I wanted it measured before I said it in public. So I ran my usual Kiro-style pipeline, where intent, research, design, requirements and tasks get handed to a fresh-context implementer. Against it I ran an orchestration chain that never asked for a spec: an Opus 5.5 coordinator writing a handoff, a Fable 5.2 orchestrator, and Sonnet 5.5 subagents doing the work.

Recurrence rules, three ways

I wanted a job a single prompt couldn't one-shot and a person could still check. That turned out to be a zero-dependency TypeScript module that expands iCalendar recurrence rules. Every arm got the same short request with the function signature fixed, running on Node 26 with Temporal built in and nothing installable.

Each run was a headless claude -p session with --output-format json, which reports cost per model. Subagents can't start subagents of their own, so the three tiers in arm B are two sessions; Opus writes HANDOFF.md, and a fresh Fable session reads it and delegates.

Every A and B run then got the same change request through its own process. That round tests what a spec costs to keep alive, and the oracle got 13 more cases for it.

Scoreboard: a tie at 59 and 60

Round 1 is below. For the review column I relabelled the five implementations and asked Opus, in a fresh session, to score each one out of 40:

run  process              cost    wall   hidden  own tests  review
A1   Opus spec > Sonnet   $3.86  15.5m   59/60       79       25
A2   Opus spec > Sonnet   $3.24  12.3m   60/60       77       27
B1   Opus > Fable > Son.  $3.10   8.7m   60/60      282       31
B2   Opus > Fable > Son.  $3.55  10.9m   60/60      469       30
C1   A1 spec > Fable      $6.38  16.6m   59/60       86       29

Round 2 kept the shape. The B runs cost $2.72 and $3.49 and needed 5.6 and 9.8 minutes; the A runs cost $3.36 and $3.81 and needed 10.8 and 12.0. In both rounds, writing or updating the spec was 75 to 80% of arm A's bill. The implementer was cheap.

Cost lands within 15% across arms. I wouldn't defend that gap at two runs each. The orchestrated runs finished about 30% sooner, wrote three to six times as many tests, and took first and second in the review. Its author also ran a 3,000-case random differential across all five implementations and found zero disagreements on core expansion, so every difference sat in edge cases like Pacific/Apia's missing day in 2011.

Fable's 1,229-word scratch spec

I opened the transcripts expecting Fable to have gone straight to code. It didn't, in either run. Each B orchestrator first wrote a SPEC.md into its scratch directory (1,229 words and 1,278) covering the API, input validation, and expansion semantics. Then it started two Sonnet subagents in parallel: an implementer, and a test author working from the RFC who never saw the code. Fable ran the suite and read every line itself once both had returned, which in B1 took about eight minutes end to end.

Then each run went further on its own. B1 wrote a brute-force date oracle to check the RFC's published examples. B2 seeded 16 deliberate bugs to test its own test suite, caught 14, and wrote tests for the other two. On the change request B1 wrote a 718-word SPEC-2.md; B2 skipped the file and put 1,104 and 1,263 words of spec straight into its two subagent briefs.

Every move my Kiro pipeline forces showed up here without the forcing: a written spec before code, separate implementation and verification, and a check against the source standard. I can't see whether Fable carries the spec frameworks in its weights or its prompt, only what it did. It sized the spec to the job at about 1,250 words, where my pipeline produced 9,148 words in five files (14,695 after the change). Then it threw the spec away, and nothing in the repo points at those files.

Opus did the same thing one level up. I asked the coordinator for a handoff in one sentence and got back about 800 words of RFC pitfalls in 33 seconds, for $0.40. So arm B never ran spec-free. It ran on two specs nobody requested: 800 words from the coordinator and about 1,250 from the orchestrator, each written by the model about to use it.

02:30 on spring-forward day

RFC 5545 contradicts itself in Section 3.3.5: it reads a nonexistent local time with the offset from before the gap, which pushes it forward. Section 3.3.10 says recurrence instances with a nonexistent local time "MUST be ignored". A1's spec quoted 3.3.10 and chose to skip. A2's spec found the same contradiction and chose to shift, because "silently dropping a household event is worse than moving it by the gap length." Same model, same prompt, opposite answers.

Both implementers did exactly what their spec said. A1's Sonnet session skipped the event. C1's Fable orchestrator, given that same spec, skipped it too, and listed the skip as an open reviewer call in its deviations. In round 2 the decision cost A1 another test, an event at 02:00 on the same Sunday. Neither B orchestrator ever had to decide; both coordinator notes had already said "shift forward".

A1's intent.md had seen it coming. Its Open section asked the question outright (skip or shift?), marked it "non-blocking, reviewer call", and moved on. My Kiro workflow puts me between the phases to answer exactly that kind of line, and this experiment took me out. The spec carried the decision to the implementer anyway, and a good implementer, Fable included, followed it. That one Open item is the only place in 9,148 words where the pipeline's output depended on a person reading it.

What I'm not claiming

Two runs per arm, one task, and a hidden suite that saturated. The review leaked a little, since arm A's code cites spec section numbers in comments. My tool allowlist stopped subagents from running node. The orchestrators ran tests themselves instead, while arm A's implementer was a top-level session that could run anything on the list. My oracle also takes one side of an RFC contradiction, which makes A1's "59/60" a policy disagreement scored as a failure.

I'm not claiming the five-file spec is waste at every size, either; "dead" here means dead as an instruction to the model on medium-scoped work like this. Large tasks may still need the full workflow. A change spanning three apps and a deploy pipeline has to survive several sessions, several reviewers and weeks of calendar time, and a disposable scratch spec can't carry that. My synth-speed spec was that kind of job, and it earned its length. I haven't tested where the line falls, and one medium task can't locate it.

Retiring the instruction

So I'm retiring "use spec-driven development" as an instruction for work this size. My next Kiro run keeps intent.md with its Open list and drops research, design, requirements and tasks; Fable writes and discards the working spec. The Open list is a different thing from SDD. It is a short list of decisions a model shouldn't make for me, and it comes to me before the orchestrator starts. In this experiment it held one DST question, and two Opus runs answered it in opposite directions without asking me.