Built sonmat v0.11–v0.13 on 2026-04-26~27. Counted the result again on 2026-08-29.
This one’s a little embarrassing. Back in April I built a spec system, and I built it pretty seriously.
Not “toss out a doc template and call it done.” Every spec clause distinguished MUST, SHOULD, MAY. Rejected alternatives had to be recorded next to the chosen one. Revising a spec meant appending a successor doc, not overwriting the body. Deprecation carried an end date and a link to the replacement. Each project got docs/specs/ and a sub-50-line index, and the agent looked up related specs at the start of a task. When the work drifted outside a spec, the scribe agent proposed the revision.
Author, reference, revise, retire. The whole loop was there.
Four months later, I counted.
Adopters: zero. Including me — the person who should have picked it up first.
A pretty thorough failure
If the system had been sloppy from day one, the story would be easy. Wrong form, missing feature, roll it back. This one wasn’t sloppy. If anything, it was too diligent.
v0.11 added: surface implicit assumptions before acting; record novel cases the existing rules didn’t cover after. v0.12 borrowed RFC 2119’s normative vocabulary and PEP 404’s explicit-deprecation shape. v0.13 layered on project-scoped templates, the sub-50-line index, agents that pulled specs automatically, and a scribe that proposed the successor.
I read into architecture, aviation, lean construction, and RFCs. I even worked through the counter-argument that you don’t lift the ceremonies of physical building into software wholesale — that argument is the reason the default was off. Only projects that explicitly opted in with spec_awareness: enabled got the system.
The last line of the shipping notes was this:
The real verification starts when the first user project sets
spec_awareness: enabled.
That sentence was correct. The problem was that first project never came.
Zero also has a denominator
At first I blamed the discovery path. The feature was buried, and turning it on meant creating a directory, an index, a config value, and at least one spec doc — four entry steps, so of course nobody used it.
The obvious fix was a bootstrap command. One sonmat spec init and the whole shape would materialize.
Before writing it, I checked the denominator. This is where the story got weird.
In the same working framework that houses specs, decisions/ and plans/ were everywhere. Dozens of ADR-style decision records across projects, even more plan docs. Only docs/specs/ was empty. Same discovery path, same user, same four months. And it wasn’t that the projects had no need for spec-shaped documents — feature definitions, API contracts, MVP requirements, prompt specifications all existed, under other names, in project after project.
Demand wasn’t missing. My form was being rejected.
The “too many entry steps” diagnosis wasn’t wrong. Just half. A bootstrapper reduces the manual cost of making the files. It does nothing about the commitment to declare a contract in advance and maintain it. A decision record notes something that already happened. A spec obligates a future you. The file was cheap; the promise was expensive.
I hadn’t skipped the tool for lack of a tool. I’d skipped it because I didn’t want to make the promise.
The docs weren’t missing
That surfaced a second misdiagnosis. I’d been framing the problem as “specs are missing.” Look at the projects and the specs weren’t missing — feature docs, API contracts, ops notes were all sitting there.
What was missing was the connection between the docs and the code.
The plan changes but the feature doc stays. The data schema shifts and the API description keeps describing the old shape. The config store is updated but the deployed installation is still reading the old value. Code and infra are each fine on their own, and yet the service breaks because nobody sorted out the restart order between them.
Adding one more doc doesn’t fix that. The failure was between artifacts, not inside them.
The moment I laid out the product this way, the shape came into focus:
users · purpose → service surfaces → feature · domain rules → application · data
→ infra · runtime → operational observation → user · definition change
Screens are one important form of service surface, not the whole. APIs, notifications, files, batch jobs — surfaces with no screen exist. Docs, tests, and design decisions aren’t a separate layer in this loop; they’re artifacts that describe or verify some part or connection of it. Non-functional demands like security and performance aren’t a separate box either — they’re constraints running across the whole loop.
Reframed this way, the job is less “write specs well” and more “check that a change actually traveled the loop to the end.”
I almost built a bigger system
Once I knew the shape of the problem, the next system looked natural.
Assign identifiers to screens and features. Enumerate the relationships between docs, code, and infra. Scan changed files, find connected artifacts, block the hook when an update is missing. New projects ship with the standard relation table; existing projects grow the graph incrementally.
Plausible. And, more importantly, buildable.
So I hit it with /devil, my own devil’s-advocate tool. The load-bearing assumption was one line: “the cost of declaring the relationship now is smaller than the verification value in the future.”
The exact assumption the spec system had failed on.
A thin relationship registry still asks for a present declaration in exchange for a future payoff. You create identifiers, register relationships, maintain the graph whenever the real structure moves. The name changed — from spec to relation table — the burden didn’t.
There was a worse problem too. The partial-graph green light.
Say ten registered relationships all check out. The tool shows success. But you have no idea whether an eleventh relationship exists that was never registered. It stops being a failure-finder and starts being a reassurance layer over unregistered connections. The moment the user forgets that the automation only covers a slice, green stops meaning “verified” and starts meaning “not looked at.”
I was about to reflect on a failed spec system by building a larger spec system.
What I kept was five questions
No graph. No identifiers. No shared config file. Instead, five questions that keep the connections from being dropped on any actual change.
Three before the change:
- Purpose — what actually needs to be different?
- Blast radius — which user surfaces, features, consumers, installations, and derived artifacts are reached?
- How it closes — each connection sorted by: single source of truth, one-way generation, independent check, or human judgment.
Two after:
- Reality check — did I verify against the actual consumer, the actual installation, the actual runtime — not just the file in the repo?
- Remainder — what didn’t get closed, and who finishes it under what condition?
There’s no new form to fill out. If every change demanded a document, the questions themselves become another ritual. Keep them alive inside the working conversation; write them down only when the contract or architecture changed, or when an open obligation is spanning sessions.
Automation isn’t banned either. When the same relationship recurs in real work and there’s an external ground a machine can judge on, that’s when it graduates. OpenAPI-driven client generation, schema-versus-migration diffs — good candidates. Screen intent, rejected alternatives, anything that needs meaning judgment — stays with the person.
The order flipped. Instead of building structure and waiting for use cases, observe use cases and only build structure for the ones that recur.
Where humans end and agents begin
Fixing the working method shook an older role split too. The one where humans do strategy and agents do tactics.
Modern coding agents generate strategic alternatives, design interfaces, find architectural counter-examples. That doesn’t mean you hand them every decision. The issue isn’t capability; it’s authority.
So I rewrote the autonomy criteria as three checks:
- Is it inside an existing contract?
- Is it reversible?
- Is it verifiable by external evidence?
If all three hold, an agent can move broadly — design through implementation through verification. Value judgments, contract changes, wide blast radius, and hard-to-reverse risks stay with the person. Hooks execute contracts already agreed on; they don’t set new ones.
This maps back onto the spec problem. If automation crosses into judgment, it starts declaring unregistered relationships safe on its own authority. The stronger the tool, the sharper the line between enforcement and decision has to be.
This time I didn’t build the hook
April me investigated, designed, and then immediately built the template, the auto-referencer, and the revision ceremony. August me put the five questions into the global working instructions and stopped.
I kept the shared instruction file thin. The must-read-every-session document holds the judgment criteria and pointers to detail; philosophy in the principles file, working method in a separate reference. Then I checked, hash by hash, that all four of my working machines’ repositories and both Claude and Codex actually read the same source.
That last check sounds trivial, and it lines up exactly with the argument of this post. Committing to the repo is not the same as being applied on four machines. You have to verify that the real installation and the real consumer are reading the new rule for the change to be closed. That check turned up a machine whose Codex was still reading the April-vintage generic file, and a Claude that wasn’t loading the shared instructions at all. Repo was right; reality was different.
The first real application of the new discipline turned out to be not a grand product feature but the propagation of the discipline itself.
What isn’t verified yet
This conclusion isn’t a finished product either.
I don’t have data on whether five questions stay light enough to actually reuse across projects. I don’t know yet which relationships turn out to be recurring automation candidates. The line between what a person judges and what a machine checks will move once cases accumulate.
So the success metric right now isn’t graph completeness or spec count. It’s: what connections got dropped in real changes, whether the same one recurred, whether the reality check caught a repo-versus-installation gap.
I didn’t delete sonmat’s existing spec feature. If a project has already adopted it, it can keep using it. I just don’t recommend it as the default structure for new projects, and I won’t extend it further without usage evidence.
I once wrote “user intent is the foundation” and shipped the system on that line. Four months later that sentence read as both a safety and an excuse. If a tool only works when the user brings intent, the design also owes an explanation for why the intent didn’t show up.
When a tool isn’t getting used, the first thing to do may not be to reduce entry steps. It may be to ask again whether the thing that tool was doing was actually the bottleneck.
In my case the specs weren’t missing. Changes weren’t closing all the way.
So this time I left questions instead of a system.
Release notes: v0.11.0, v0.12.0, v0.13.0 Repo: https://github.com/jun0-ds/sonmat
Part of the series Building sonmat. Previous: I put a witness on my AI. Devil’s advocate killed three designs first. (한글판은 /ko/blog/building-sonmat/06-spec-discipline/)