Acolyte never had a spec. Not as a decision, just as history: I established my spec-driven workflow after the project was already mature. This week I closed that gap, and the process taught me more about specs than writing one for a greenfield project ever has.

The tool for the job was my [spec skill](https://github.com/cniska/skills): a spec states what must be true, never how to achieve it. Every requirement gets a stable ID, acceptance criteria cite the requirements they verify, and edge cases become their own labelled requirements so each one earns a test. On a new project you write the spec first and the code follows. Here I had to run it backwards: derive the "what" from twenty-five thousand lines of "how".

## Code is not intent

I gave the task to two different models independently. Both read the docs and the source, both produced a complete spec, roughly a hundred labelled requirements each. Both included a requirement that on first run in a project, the agent bootstraps project memory from the codebase.

That feature does not exist. There is no bootstrap. The closest real behavior is a flag-gated sync that copies one file into memory on each chat request. Both models had read a thin line in the docs, found it plausible, and promoted it to a requirement with a straight face.

A second miss was worse because it touched security. The draft stated that the daemon's RPC requires bearer authentication. The code says: only when an API key is configured, and by default none is. The real posture is that the server binds to loopback and runs open until you set a key. Defensible, but not what the spec claimed.

The fix was not more careful prose. It was verification: two independent readers over the same questions, answers required to carry file and line citations, agreement settles a fact, divergence means I read the disputed code myself. Every claim in the final spec survived that pass. The lesson generalizes past specs: code tells you what the system does, never what the author meant. Any document derived from code inherits that gap, and confident prose hides it.

## One-way doors

The second question the exercise forced: does "runtime is Bun" belong in a spec, or is that implementation detail?

My skill's original rule was that fixed stack choices stay, quarantined under constraints. That rule draws the line in the wrong place, because "fixed" describes both Bun and my formatter config, and they clearly do not belong in the same document. The better axis is cost of reversal.

Bun stays. A Python implementation could pass every acceptance criterion and still be the wrong deliverable; reversing the choice forces a rewrite. The formatter goes. Swap it tomorrow, run one command, every criterion still passes and the diff is cosmetic. The test: a decision belongs in the spec only if reversing it would force a rewrite or change what the deliverable is. Anything a tool can re-apply mechanically is a convention, and conventions live in the project rules file, not the spec.

The test earns its keep on the non-obvious cases. "Distributed as a single static binary" looks like toolchain but forecloses whole implementation strategies, so it stays. So does "no telemetry". User-visible defaults are requirements; internal tuning values are open decisions.

## Two models, one contract

The two drafts made nearly opposite structural choices. One promoted lifecycle, memory, and protocol to their own requirement families and folded auth into security; the other did the reverse. Their requirement counts landed thirty percent apart.

On substance they converged almost completely. Both captured the same subtle guarantees in the same terms: the completion backstop reopens a turn exactly once per cause, a truncated response is classified before the empty-text check because truncation can leave the text empty, the sandbox fails closed and resolves symlinks, the tool budget resets per turn and never counts the terminal step, so the model can always stop.

That split is the useful finding. The hard requirements of a system are stable: two independent readers extract the same contract. The taxonomy is judgment, and arguing about it is wasted effort. If you are reviewing a spec, spend the review on the claims, not the section structure.

## When a spec earns its keep

An honest question I had to answer before committing the result: does [Acolyte](https://acolyte.sh) actually gain anything here? Users will never read this document. It improves no runtime behavior.

The value is narrower and real. Acolyte is largely built by agents, and agents do not have my head. The docs explain how the system works, but nothing distinguished contract from current implementation. Now an agent refactoring the completion path can see that reopen-once is a promise while the cache clearing strategy is a conservative policy, explicitly marked replaceable. The spec is also a deletion map. I remove working code when its assumptions expire, and the constraints, chosen policies, and open decisions sections answer in advance what is load-bearing and what is scaffolding.

The condition: a stale spec is worse than none, because it is a confident authority that lies. So the maintenance rule lives where agents read it, in the project rules: keep the spec current in the same change that changes behavior. The spec never lags the code. With stable IDs the update is one line in a commit message, not a document rewrite. That is the version of spec-driven development I can defend: not ceremony before code, but a verified contract that stays true while the code moves.