← back to the diagram (midi-harness.html)

# The Arc — three repos, one idea, getting simpler

The full story behind the front-page diagram. An ambitious first attempt (midi-coach) broke on a hidden flaw, the strip-back rebuild (midi-harness) is what the diagram shows, and the whole point was the public blog post. Source of truth: the two repos and the published post.

The arc as a timeline — complexity falling over time

Each step throws something away: midi-coach had two clocks and a byte-offset marker; midi-harness keeps one clock and uses file-deletion as the turn boundary; the blog post keeps only the lesson. The diagram on the front page is a zoom-in on the middle box.

## 01 · aborted midi-coach

The ambitious first attempt. A separate Go repo (`~/Documents/midi-coach`, git `github.com/jakesimonds/MCP-Dash-Demo`) tried to interleave two streams every turn: the MIDI notes Jake played and his Wispr-Flow voice dictation, time-aligned into a `<spoken-and-played>` block. It tracked progress with a byte-offset marker (`.last-offset`) instead of erasing the log.

The premise: Claude-as-piano-teacher. Jake narrates while he plays — "here comes a G octave… now a C octave… what do you see?" — and the hook slots the keys in where the words landed. A `library/` of per-song references (a Bob Dylan PDF, a Brenda Earle Stokes voicing tutorial) gave the agent ground truth to check against.

It was abandoned because the sync was quietly broken — and worse, it looked like it worked. Jake's verdict after a deliberate test:

> "Wispr is not working at all and I don't know that it ever has been."

From FINDINGS.md (2026-05-29) — the diagnostic that killed it

① The G-octave / C-octave demo dropped the exact notes it was meant to show — the block held only the opening slice of the dictation, and the notes it did show weren't the octaves Jake described.
② A turn where Jake said "this time I'm actually playing a note" came back words-only — zero MIDI clusters. A played note never reached the gate.
③ The hook's whisper re-transcription was regularly shorter than the clean Wispr message — two transcripts of the same speech that didn't match.
★The meta-finding: the output always looked plausible, so nobody caught it being wrong until Jake played a known thing and looked for it. A block full of timestamped chords reads as "synced" whether the notes belong to those words or not. There was never any ground truth.

Root cause (FINDINGS.md, ranked): two independent clocks reconciled after the fact. Wispr recorded audio on its clock; the Go listener logged MIDI on wall-clock epoch; the gate kept only notes whose timestamp fell inside Wispr's clip window. Drift of a few seconds → the window missed the notes entirely. Architectural, not a tunable bug. The agreed fix was radical: kill the second stream.

Lives on only as a pointer in `archive/midi-coach`; the diagnostic itself is `~/Documents/midi-coach/FINDINGS.md`. The repo was later repurposed, so its git history no longer reads as a music tool.

## 02 · shipped midi-harness — the strip-back

The 2026-05-29 rebuild as a fresh independent repo (`~/Documents/midi-harness`, public at github.com/jakesimonds/midi-harness). It dropped the voice stream and the byte-offset bookkeeping entirely. The one idea the front-page diagram captures: emptying the log every turn IS the "since last message" boundary — no offset marker, no time window, no second clock, no audio.

Two pure-stdlib Go binaries: `cmd/listener` (forever-running capture, re-opens the file per write so a deleted log is recreated by the next note) and `cmd/hook` (a `UserPromptSubmit` hook that reads the whole file, clusters notes into chords, emits a `<played>` block, then deletes the file). ~0ms hook startup. ~10 real sessions logged 2026-05-30 → 2026-06-01.

What Claude receives each turn (real hook output):

    <played>
      C3 G3 C4 E4 x3
      F2 F3 A#3 D4
      G4
    </played>
    # notes only — no pitch-class brackets, no header.
    # the format is explained once in the 46-word prompt,
    # so each turn spends tokens only on the notes.

The listener controls (live in this node dir, wired to the local viewer's `/api/run-script`):

listener-start.sh

Kill-then-exec foreground start: `cd ~/Documents/midi-harness`, `make build`, kill any already-running listener (so `events.jsonl` never has two writers), then `exec bin/listener` — the viewer streams its stdout so notes scroll as you play.

listener-status.sh

Emits one JSON line for the node page's status pill. Matches on the binary path (`pgrep -f`), so it sees a listener started by anyone — this button, a terminal, or the repo's own SessionStart hook.

listener-stop.sh

A `pkill` on the same binary path — stops the listener no matter who started it.

Claude's second output — `what-you-played.html`, written ad hoc (no skill, no prompt line): a chord card over a fingered mini-keyboard. Here, C major as a 1–3–5 home-base shape.

Full current-state notes, the "does the sub-project see streetlight's harness?" answer, and the lineage all live in midi-harness.md. Drift committed 2026-06-11 as `7cb9815`; `session-drain.md` is gitignored (local transcript paths).

The lessons (what the blog post is about)

1 · Two clocks reconciled after the fact will drift — and lie.

Notes got silently dropped, and the output always looked plausible so nobody caught it. The fix wasn't to reconcile better; it was to delete the second clock. One stream, one boundary.

2 · You can't tune a system prompt from inside the session it governs.

Claude Code loads `CLAUDE.md` once at session start and never re-reads it per turn — the editing session keeps running the OLD prompt and is a biased judge of its own edit. Edit, then test in a fresh session. See system-prompt-self-edit.

3 · One idea wearing three names IS miscommunication.

midi-coach, midi-harness-rebuild, and a same-named Rust-synth node all answered to "midi-harness." Naming sprawl made it hard to say which thing worked. Cleanest fix: let the live repo own the name.

4 · Plausible output without ground truth is the real trap.

An LLM block that reads as "synced" hides whether it actually is. You only catch it by playing a known thing and looking for it. Build the check before you trust the demo.

## 03 · the point the public blog post

The whole exercise was aimed at a public essay about treating an LLM as an instrument you learn to play — and the lessons from building a tiny harness to do it. The post is live; this node archives once it's out, so the AC is met.

Published post · jakesimonds.leaflet.pub

Midi Harness: An Experimental Stack

jakesimonds.leaflet.pub/3mnzjxem5q22z