SDD
Who is this for? Someone who knows OpenSpec, Spec Kit, BMAD or Agent OS and wants to know how this relates to them. When should I read it? When comparing, or when you already use one of them and wonder what this adds.
Spec-driven development, and what was taken from each system built around it.
Six systems were read before any of this was written: OpenSpec, Agent OS, Spec Kit and BMAD on the SDD side, handoff-driven development and plain TDD next to them, plus the Learn Harness Engineering course the vocabulary comes from. Each one organises how work gets done. None of them re-checks the claim that the work is finished — and that is the only thing this package does.
So nothing here competes with them. Every rule below is one idea taken from one of those systems and reduced to a command that exits non-zero, with the framework, the CLI, the personas and the templates left behind.
flowchart LR
P["Plan
OpenSpec · Spec Kit · BMAD"] --> B["Build
your agent"]
B --> V["Verify
Harnessimo"]
V -->|"green"| D["Merged"]
V -->|"red: file, line, fix"| B
D --> H["Carry over
HDD: tracks + handoffs"]
H --> P
style V stroke-width:3px
The map
| System | Taken | Left behind | Lives here as |
|---|---|---|---|
| OpenSpec | specs/ is the current truth; one spec, one deliverable; archive after shipping |
the CLI and its change/archive machinery | tracks + the specs/ scaffold |
| Agent OS | standards live apart from specs and are never copied into them | the document hierarchy, the role scaffolding | instructions + .harness/1-instructions/ |
| Spec Kit | an acceptance criterion must be executable | the templates and slash-command bindings | tasks + proof |
| BMAD | verification is a step with its own artifact | the agent personas and prompts | queue |
| HDD | unfinished work crosses sessions in a written handoff | the as-built spec genres, the separate audit script | tracks + brief |
| TDD | the judgement about "works" belongs in something that exits non-zero | nothing — it is extended, not replaced | queue runs your tests |
| Learn Harness Engineering | the five subsystems, and externalising the completion judgement | — it is the course this implements | init, locked, cleanExit, coldStart |
Everything below is the same table, one system at a time, with the file it lives in and the commands you actually type.
OpenSpec
What it is. Spec-driven development for AI assistants: a change gets a proposal, a spec
and a task list in its own folder, and the folder is archived once it ships. specs/ is the
current truth about the system, not a pile of historical documents.
Taken
- one spec = one deliverable, in its own directory;
specs/describes what is true now, and a shipped change leaves it rather than accreting;- closing a piece of work is a distillation, not an append.
Left behind
- the CLI and its change/archive commands. A directory and a Markdown file do not need a binary, and a tool that owns your specs is a tool you have to keep alive;
- the proposal/solution document set. One
spec.mdwith executable criteria carries it.
Where it is implemented
| Piece | File | Check | Config |
|---|---|---|---|
| The track index resolves and every track carries a status | src/tracks.ts |
harnessimo tracks |
tracks.file, tracks.log |
The specs/ shape, the spec and handoff templates |
templates/specs/ |
written by init |
tracks.specsDir |
How to use it
harnessimo init # writes specs/TRACKS.md, TRACKS-LOG.md and the two templates
cp specs/spec.template.md specs/0007-checkout/spec.md
harnessimo tracks # the index must resolve and carry statuses
When the work ships, its outcome goes into specs/TRACKS-LOG.md in one or two sentences and
the track line disappears. Git keeps the rest — that is the archive.
Agent OS
What it is. A way of capturing a team's standards — conventions, architectural decisions — so they are injected into an agent's context instead of being re-explained in every prompt, and kept separate from the specs they shape.
Taken
- standards and specs are different things and live in different places;
- a standard is referenced, never copied into a spec, so there is one copy to correct;
- the instruction file is a router to those documents, not the documents themselves.
Left behind
- the layered document hierarchy and the role scaffolding. The shape here is five directories from lecture 02, not a taxonomy of documents.
Where it is implemented
| Piece | File | Check | Config |
|---|---|---|---|
| The instruction file stays a router | src/cleanexit.ts |
harnessimo instructions |
instructions.limits |
| Where the standards live | templates/.harness/1-instructions/ |
written by init |
— |
How to use it
{ "instructions": { "limits": { "AGENTS.md": 120, "CLAUDE.md": 120 } } }
$ harnessimo instructions
FAIL instructions
AGENTS.md 187 lines, limit 120
fix: move a section into a document and link it — lecture 04: what lands in
the middle of a long instruction file is what gets ignored
The limit is the enforcement. A rule that says "keep it short" and never fails is a wish.
Spec Kit — spec-driven development
What it is. GitHub's SDD toolkit: /speckit.specify, /speckit.plan, /speckit.tasks,
/speckit.implement turn an intent into a spec, a plan and a task list per feature.
Taken — one rule, and it is the most valuable idea in any of these systems:
an acceptance criterion must be executable — a test name, an eval case, a query.
Left behind
- the 300-line templates and the slash-command bindings. Generate your specs with Spec Kit, with BMAD, or by hand; this does not care which.
Where it is implemented
| Piece | File | Check | Config |
|---|---|---|---|
| A ticked box must name a check that passes | src/tasks.ts |
harnessimo tasks |
tracks.gateTasks, tracks.taskFile |
| A claim in a document must name its evidence | src/proof.ts |
harnessimo proof |
docs.roots, docs.commands |
How to use it
Point the gate at whatever the generator produced:
{
"tracks": { "specsDir": "specs", "taskFile": "tasks.md", "gateTasks": true },
"docs": { "roots": ["docs", "specs"], "commands": { "npm run": "package.json" } }
}
Then a tick has to carry its evidence:
- [x] T3 Totals round per currency, not per line
<!-- proof: tests/<file>.spec.ts#<the test name> -->
FAIL task gate
specs/0007-checkout/tasks.md:14 T3 is checked but names no proof
Only live tracks are gated, so adopting this does not require going back through every finished spec.
BMAD
What it is. An agile framework for AI-driven delivery: clarify → plan → build and verify, worked through specialised perspectives (product, architecture, UX, development, testing) that produce durable artifacts instead of chat.
Taken
- verification is a step with its own artifact, not a feeling at the end of a story;
- a unit of work is sized to be finished, and carries what would settle it.
Left behind
- the agent personas. No prompts ship in this package, and none are needed: the queue does not care who or what did the work.
Where it is implemented
| Piece | File | Check | Config |
|---|---|---|---|
Only a passing command moves an item to passing |
src/queue.ts |
harnessimo queue verify <id> |
queue.file |
| Every passing claim is re-run | src/queue.ts |
harnessimo check --reverify |
queue.timeoutMinutes |
| WIP limit and the review backlog | src/readiness.ts |
harnessimo queue activate <id> |
wip_limit in the queue file |
How to use it
Each item is a story with the command that closes it:
{
"id": "checkout-totals",
"behavior": "An order total matches the sum of its lines, in every currency.",
"verification": "npm test -- checkout",
"state": "not_started"
}
harnessimo queue status # what is in flight, what is waiting
harnessimo queue verify checkout-totals # activates it, runs the command, records the outcome
harnessimo check --reverify # CI re-runs every item claiming to pass
The agent never writes state itself. That is the whole point of the section: editing it by
hand is not a shortcut, it is the thing --reverify catches.
HDD — handoff-driven development
What it is. A session ends and its memory is gone, so unfinished work crosses that boundary in a written handoff rather than in someone's head.
Taken
- a live track index, one line per track: essence, handoff link, status, next step;
- a
handoff.mdnext to the spec: context, what to load and what not to, state, decisions taken, first step — edited in place, never appended to; - closing a track distils the outcome into a log and deletes the handoff.
Left behind
- the as-built spec genres and the separate audit script. The audit is a check inside the same gate everything else runs through, so there is one command, not two.
Where it is implemented
| Piece | File | Check | Config |
|---|---|---|---|
| A dead handoff link, or a track with no status, fails | src/tracks.ts |
harnessimo tracks |
tracks.file |
| The next session is handed the state at startup | src/brief.ts |
harnessimo brief |
— |
| The hook that runs it without anyone remembering | src/hooks.ts |
harnessimo hooks install --agent |
— |
How to use it
harnessimo hooks install --agent # SessionStart, Stop and SubagentStop in .claude/settings.json
harnessimo brief # the same output, on demand
== specs/TRACKS.md (live work tracks — load a track's handoff first) ==
- Checkout totals — [handoff](specs/0007-checkout/handoff.md) — active, next: T3 currency rounding
- Hosted deploy — none — blocked (on an API token)
A handoff nobody reads is a diary. A handoff pushed into the session's first turn is a protocol — which is why the delivery half matters as much as the check.
TDD
What it is. The oldest version of the same principle: the judgement about "works" is externalised into something that exits non-zero, and it is written before the code.
Taken — the principle, extended to the three places tests do not reach: documentation
(proof), task lists (tasks) and workflow state (queue).
Left behind — nothing. If your tests are good, the queue costs one line of JSON per item, because the command it runs is the test you already have.
How to use it
{ "id": "cart-merge", "verification": "npm test -- cart", "state": "not_started" }
That is the whole integration. The value added is not another test framework: it is that the result is written down by the tool and re-run later, so "it passed in March" cannot stand in for "it passes now".
Learn Harness Engineering
What it is. The course this implements, and where the vocabulary comes from: a harness is everything in the engineering infrastructure outside the model's weights.
Taken
- the five subsystems — instructions, tools, environment, state, feedback — as the directory
layout
initwrites; - externalising the completion judgement (lecture 09);
- the reward-hacking failure: a system that can edit its own scoring will;
- a clean session exit (lecture 12) and the instruction-file limit (lecture 04).
Where it is implemented
| Piece | File | Check | Config |
|---|---|---|---|
| The five directories, with starter documents | templates/.harness/ |
written by init |
— |
| The scoring surfaces are out of an agent's reach | src/locked.ts |
harnessimo locked |
locked.paths, locked.baseline |
| No debris, and progress written down | src/cleanexit.ts |
harnessimo clean-exit |
cleanExit.* |
| A fresh clone runs from the repository alone | src/coldstart.ts |
harnessimo cold-start |
coldStart.commands |
How to use it
harnessimo init # the five directories and the starter documents
harnessimo doctor # which of the checks are on, and which are not
The standard says which lecture each rule comes from, and where this implementation makes a choice the course leaves open.
What none of them gave: documentation that cannot lie
Every system above produces documents. None of them re-checks that the documents are still true, and the failure this package was written for was exactly that: a README describing infrastructure that had never been built.
So the proof marker is the one part that is not borrowed. A claim names its evidence, and the build resolves it:
Totals round per currency, not per line. <!-- proof: docs/<a-file>.md -->
Check it yourself with `make verify`. <!-- proof: make <a-target> -->
FAIL proof markers
README.md:31 docs/ARCHITECTURE.md
no such file
Implemented in src/proof.ts, run by harnessimo proof, configured under docs.
docs.mustCarryProof names the documents that must carry at least one marker, so a rewrite
cannot quietly drop the evidence along with the claim.
What this does not decide for you
No personas, no prompt templates, no opinion on how a spec should be written or how big a
story should be. There is no runtime, no daemon and no dependency. The rules are plain
functions over strings; src/resolver.ts and src/cli.ts are the only files that
touch the disk.
Pick the system you like from the list above. This is the part that tells you the truth about it afterwards.
Next: use cases and what it looks like in practice · why each rule exists · adopting it in an existing repo