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.
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
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.
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.
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