Every engineering org I’ve worked in has a folder like this somewhere. A docs/ directory, a Confluence space, a Google Doc linked from a wiki page nobody visits. Inside it: the spec. Written carefully before a feature shipped. Reviewed by three people. Approved. And then, the moment the code merged, functionally dead — a fossil of what the team intended, drifting from what the system does.
I don’t say that to complain about lazy documentation habits. Nothing in the workflow ties the spec to what ships — nothing breaks when the code changes and the doc doesn’t, and nothing in CI cares. The traceability matrix that maps requirements to implementation gets updated once, at launch, because keeping it current isn’t anyone’s job. Six months later an engineer greps the spec for the retry policy, finds three paragraphs of prose, and can’t tell whether they match the code or a decision quietly overridden in a hotfix a year back.
Spec-driven development, in its traditional form, fixes this with process: write the spec first, get sign-off, then build to it. That helps at the start of a project and does nothing for the middle, where the spec is still a separate artifact checked by separate tooling — if it’s checked at all. The argument here is narrower than “write better specs.” The fix isn’t a better document; it’s making the document part of the same system that builds and tests the code, so it can’t drift without something noticing.
TL;DR
Nothing enforces the spec-to-code link, so it rots. A standalone spec has nothing tying it to what ships; nothing breaks when it drifts, so it does.
The reframe: the spec becomes part of the workflow — referenced inline from code comments, parsed as data by the build, versioned in the same commits as the implementation.
trusty-mpm is a working instance, not a proposal: a spec requirement runs verbatim inside the prompt the model reads each session, code comments cite spec sections by ID, and a Rust module parses spec markdown at build time to check code against it — flagging code that cites a spec revision that’s since changed.
This isn’t new — literate programming, Gherkin’s living documentation, rustdoc doctests, and design-by-contract all tried versions of it — but AI-generated code gives it new urgency: the volume of change makes a hand-maintained spec even less credible than before.
The industry is converging on the same shape: GitHub Spec Kit, AWS Kiro, OpenSpec, and Tessl all bet that spec and code must co-evolve as tracked artifacts, not a document and its distant cousin.
A minimal frontmatter schema — stable ID, version, status,
applies_toglobs — is the concrete thing to adopt this quarter, whether or not you touch any of the tools above.
The reframe: the spec as workflow, not deliverable
Here’s the alternative. Instead of describing the system from outside, the spec becomes a component the system consumes. Code references it by a stable ID; the build (or a linter, or a review bot) parses the spec and checks the referenced section still says what the code assumes. Editing the spec means touching something with known consumers, not filing a document into a drawer.
None of this is new. Knuth’s literate programming interleaved explanation and code decades ago; Gherkin made acceptance criteria executable; Rust’s doctests and mdbook test run documentation’s code examples in the test suite, so a doc that lies about the API fails CI; design-by-contract (Eiffel, Ada/SPARK) puts pre- and postconditions in the interface; RFC 2119 gave the field MUST/SHOULD/MAY, precise enough for a machine to check.
What’s changed is the pace, and who’s writing the code. When a team ships every two weeks, a stale spec is an annoyance you catch next planning cycle. When an agent generates hundreds of lines and opens a PR before you’ve finished coffee, an unchecked spec is stale before the reviewer finishes the diff. The old techniques weren’t wrong; they just didn’t have to survive this throughput.
Why code alone isn’t enough
Open a function you didn’t write and you can read exactly what it does. What you can’t read is whether that’s what it was supposed to do. Code is a precise record of behavior and a silent one about intent — in the source tree a bug and a feature look identical, and the only thing that tells them apart is what someone wanted, which lives outside the code.
Tests don’t fill that gap, even though we reach for them as if they do. A test locks in behavior — it keeps the code doing what it does now — but it can’t tell you that behavior was the one you wanted. A test that a webhook retries three times passes just as happily whether “three” was a deliberate choice or a number someone typed on a Friday. The spec carries the intent, separate from the code meant to deliver it.
The deadline hits, so you take the shortcut. The hotfix goes in at 2 a.m. and nobody circles back. Every codebase is a running negotiation with expediency, and each shortcut gets absorbed into the code, where it stops looking like a shortcut and starts looking like a decision. A year later nobody can tell which lines were chosen and which were just the fastest way through. The spec is the one term of that negotiation you agree not to move — a survey marker: the code shifts around it, and because it stays put, you can measure how far. Take it away and a shortcut is just the code, indistinguishable from what’s near it.
That’s what trusty-mpm’s drift check does, later in this piece: when a spec section moves to a new revision and the code still points at the old one, the check flags it. The marker moved; the tool says so.
That agreement is the line between formal engineering and vibe coding. Vibe coding keeps the output and throws away the intent behind it. It works until the output has to change and neither the humans nor the model can reconstruct what it was for. Sean Grove, whose argument I reach below, calls this version-controlling the binary and shredding the source. AI sharpens the line rather than softening it: ask an agent to fix the case in front of it and it will often comply by quietly narrowing a behavior — solving the immediate problem, dropping an edge case nobody mentioned — and without a spec, that narrowing is just the new code, shipped with passing tests. The difference between formal engineering and vibe coding is the spec: the idea that doesn’t drift for expediency.
An exemplar: trusty-mpm
trusty-mpm — the tm binary — is a Rust-based multi-agent orchestration harness I’ve been building: the layer that composes the prompt assets, agent definitions, and skills a Claude Code session consumes. (Not the unrelated Python project of a similar name.) The spec-as-workflow pattern here wasn’t designed up front; it emerged from a failure. What follows is what it does and why it matters — the file paths and function names are collected in the appendix.
A spec the running system reads
A live trusty-mpm session once misidentified its own harness — told the user it was running under something it wasn’t. The fix wasn’t a prompt patch and a changelog note. It was a spec: a short document stating what the system must know about itself, and when.
What makes it more than a postmortem is where that spec ended up. Its central requirement doesn’t just sit in a docs folder — it runs, close to verbatim, inside the prompt the model reads at the start of every session. And the live instruction cites its own source, naming the exact spec section that specified it. Change that section without updating the prompt and the citation is still pointing at it, flagging the mismatch. The requirement can’t quietly fall out of the running system, because the running system reads it every time.
Links that run both ways
For that to hold up across a whole codebase, specs need addresses. Each spec section has a stable ID, and the code that implements it carries a comment naming the section it satisfies. The spec points back the other way: each requirement lists the modules that implement it. Either side can find the other — which means either side drifting from the other gets caught, rather than surfacing months later as a mystery.
A check that catches an outdated pointer
Those section IDs carry a revision number. When a spec section changes in a way that matters, its revision moves forward. Code that still points at the old revision was written against a spec that has since moved — so a check flags it instead of trusting the stale link (the specifics are in the appendix). It’s the traceability-matrix problem from the opening, turned into something a machine checks on every change instead of a spreadsheet cell a human forgets to update.
A test that fails when the shipped artifact drifts
That self-awareness spec has acceptance criteria: things the shipped prompt must contain. A CI test asserts exactly those. If a later edit strips a required section out of the prompt, the test fails on the pull request that caused it, not in a bug report three sprints later. The spec’s requirement and the test’s pass condition are the same statement, written once.
The smallest version of the same idea
The discipline scales all the way down. Inside individual functions, a short comment states why a behavior exists and names the tests that enforce it, right beside the code. And the house rule is explicit: a spec is a behavior contract — it says what, not how — and code must link to its spec section from the first pull request that implements it, even while the spec is still a draft. Load-bearing before it’s finished.
The industry is formalizing the same idea
trusty-mpm isn’t the only place this is showing up; the convergence from different directions is itself evidence the constraint is real.
The clearest version of the thesis comes from Sean Grove at OpenAI, in a talk transcribed as “The New Code”. Code, he argues, captures maybe 10-20% of a piece of software’s value; the rest is the structured communication — intent, constraints, tradeoffs — that produced it. Most teams version-control the binary and shred the source: they keep the generated code and throw away the spec. He cites OpenAI’s own Model Spec as one meant to stay living and cross-functional rather than one team’s document the rest of the org ignores.
Several tools are building toward the same shape. GitHub Spec Kit runs spec → plan → tasks → implement with artifacts colocated in the repo (specs/[branch]/spec.md), FR-001-style IDs, and a project constitution.md of non-negotiables — though it doesn’t use YAML frontmatter, a gap I address below. AWS Kiro structures each feature as requirements.md, design.md, and tasks.md, plus persistent “steering files” for conventions. OpenSpec is the closest to spec and code co-evolving as tracked diffs: a propose → apply → archive cycle built on delta specs (ADDED/MODIFIED/REMOVED/RENAMED, in GIVEN/WHEN/THEN form). And Tessl raised $125M on the strongest version of the bet — the spec is the source, the code a regenerable output marked // GENERATED FROM SPEC - DO NOT EDIT — though as of mid-2026 still a thesis, not a practice with a track record; I cite it as direction, not proof.
None of these do exactly what trusty-mpm’s drift check does — parse the spec at build time, check it against the linked code, and flag an outdated pointer automatically, in CI rather than argued for as a future state.
The spec stops being about the system
The through-line is narrower than “documentation matters” — most teams agree it matters, and stale docs keep happening anyway. The claim is mechanical: a spec with a stable ID, cited from code, parsed by a build step, checked for drift, behaves differently than one in a wiki — not because anyone tries harder, but because something other than good intentions is now checking.
That’s the demarcation from the opening, made concrete: the difference between formal engineering and vibe coding is the spec — the idea a team agrees not to drift for expediency. The mechanisms in this piece exist to keep that agreement from decaying back into a good intention.
I should hold trusty-mpm to that standard, and it half meets it. The conventions are there: document numbers, stable section IDs, links from code to the spec sections it implements, the drift check, a status / owner / last-updated header on each spec. What’s missing is the last mile I’m prescribing to other teams: that header is semi-prose, not a machine-readable schema — no formal frontmatter, no published schema, no CI gate validating it. trusty-mpm parses spec prose today, not spec metadata, because the metadata isn’t structured enough to parse.
So the next step is to close that gap in trusty-tools: a formal frontmatter block on each spec, a schema behind it, and a CI check that fails when a spec’s metadata drifts from the code it governs. That’s what adopting the standard means here — making the build enforce conventions the project only half-follows, instead of prescribing to others a discipline I’ve left to good intentions in my own repo. The spec becomes a component of the system at the point where the build won’t let it drift. That’s the part still to build.
Bob Matsuoka is CTO of Duetto and also writes about AI business at AI Power Ranking.
Related reading:
It’s the Harness, Stupid — What a harness is and why orchestration, not model quality, drives the spread in outcomes — the same infrastructure that makes spec-as-workflow possible.
What Is Harness Engineering? (And Do You Need to Learn It?) — The durable skill underneath the harness: designing the loop, not just the scaffolding — spec resolution is one piece of that loop.
AI Power Ranking — Tool comparisons and benchmarks for AI practitioners.
LinkedIn Newsletter — Strategic AI insights for CTOs and engineering leaders.
Appendix: how it actually works
The practical takeaway: a frontmatter schema that plugs into the build
If you want a piece of this without a whole tool, start with a frontmatter schema for your specs — the structured metadata block at the top of a markdown file — that your build can validate. A precedent to copy: Structured MADR ships a CI-validated YAML schema (JSON Schema plus a GitHub Action) with fields like title, status, created/updated, author, and related. Kubernetes’ KEP (kep.yaml) and Python’s PEP headers are older versions of the same idea: metadata structured enough for tooling to check, not just a reviewer.
Here’s a schema to start from, adapted for code-adjacent specs rather than pure design docs:
Field Example Purpose id SPEC-042 Stable anchor code comments point at (e.g. // impl of SPEC-042#retry-policy) title "Retry policy for outbound webhook delivery" Human-readable name status accepted One of draft / review / accepted / deprecated / superseded version 1.2.0 Bump on any semantically meaningful change → enables revision-drift detection owners ["@handle"] Who owns the spec applies_to ["src/webhooks/**"] Globs this spec governs; CI flags code changed under a glob with no spec touch (and vice versa) supersedes [] IDs of specs this one replaces requirement_ids ["FR-014", "FR-015"] Requirement IDs this spec governs last_verified 2026-06-01 Last time someone confirmed spec still matches code
Each field does something a build can act on, not just something a human can read. Two do the heavy lifting: version makes drift checkable — without it, “the code cites an old section” is a judgment call, not a fact a machine can determine — and applies_to lets CI flag a PR that touches a governed glob with no spec commit, and the reverse. Together they turn the staleness signal a traceability matrix only promised into something a linter checks, not a column a human forgets.
trusty-mpm grew its own version of this organically, out of that self-identification failure. Before any schema existed, its section IDs were already doing the job id does above, and its revision-tagged sections plus the drift check were doing the job version does. Adopting this from scratch, you don’t need to rediscover that path — the frontmatter above is the same mechanism, explicit from day one.
How it works
The mechanics behind each mechanism in the exemplar section — file paths, identifiers, and the code that backs them.
The self-awareness spec —
docs/specs/trusty-mpm-self-awareness.md, tracked as DOC-28. Its requirement R2 runs close to verbatim in the session prompt,BASE_SM.md, and in the output-style file. The live instruction cites it inline: “…the active palace carries anis_facttriple identifying this framework (see docs/specs/trusty-mpm-self-awareness.md §5).”The ID grammar — each spec carries a
DOC-Ndocument number; each governed section a stable ID in theSPEC-{SUBSYSTEM}-{NN}~{rev}grammar (e.g.SPEC-CONFORMANCE-02~draft), where~{rev}is the revision the drift check watches. A{#SPEC-…}heading marker anchors each section — like an HTMLid— so code links to the section, not the whole file.Code-to-spec links — a
# Spec Referencesblock in a module’s doc comment (the rustdoc Rust renders into API docs). Fromfront_gate.rs:
//! # Spec References
//! - [`SPEC-CONFORMANCE-02~draft`](docs/specs/intent-conformance.md#SPEC-CONFORMANCE-02~draft) (§5.1 FRONT gate)
//! - [`SPEC-CONFORMANCE-01~draft`](docs/specs/intent-conformance.md#SPEC-CONFORMANCE-01~draft) (§4 decision matrix)The reverse direction: each spec requirement ends with an “Implementing Modules” table plus inline file:line citations.
The parser and drift check —
spec_resolve.rs.parse_spec_refs()scans Rust source for# Spec Referencesblocks;resolve_spec_section()reads the spec markdown, finds the anchored section, and extracts its “Behavior Contract” and “Rationale.” Two gates run it through the same path —front_gate.rsbefore work starts,conformance.rs(in trusty-review) after — so they can’t diverge on what a section requires. If code links~v1of a section that’s since become~v2, it flagsrevision_drift = truerather than trusting the citation.The CI test —
bundle_tests.rs, testoutput_styles_carry_identity_protocol_and_load_marker, asserts every bundled output style contains the non-overridable Identity protocol section and the marker<!-- trusty-mpm-instructions-loaded: v1 -->. Drift from DOC-28’s acceptance criteria fails the PR that introduced it.The doc-comment convention —
harness_doc.rspairs a Why / What / Test triad inside///comments: the rationale plus the exact test names that enforce it. The house rule is stated indocs/specs/README.md— “a behavior contract… without prescribing the implementation” — and requires code to link to spec sections from the first implementation PR, even while the spec is~draft.




