For agents, on top of README.md, which they read first.

Notes for agents

Spec first. Update SPEC.md — its body, and a new revisions entry — before changing song.js, in the same commit. The spec is the source; the song is its output.

Score each new revision with Jev. op-env-run -- node tools/critic/critic.mjs --write --song <theme>/<song> writes the revision's art: (the mean of three runs, with each run in artRuns:) and the song's critic: into the frontmatter; commit them with the revision. See tools/critic/.

Records, not summaries. model is the exact model ID that did the work (e.g. claude-opus-5-5); prompt is the instruction that revision carried out, written as a clean, standalone brief of what the user asked for about the music: for a song built from a pitch, rev 1's prompt is its brief; never paste the chat message (no session ids, asides, typos or talk about the session); brief is the pitch in the user's own words, with typos, casing and punctuation fixed and nothing added or reinterpreted (it shows on the song's page, so a raw chat line with typos does not belong there); output names the file a revision wrote, or null if a later revision overwrote it.

A spec may correct its own brief, and must say so. When a pitch asserted something unverified or collided with another song, the spec's body says what changed and why (see jev/hey-listen and jev/borrow-checker-blues). Never rewrite the recorded brief to say something else; fixing its typos is not a rewrite.

A revision is a prompt about this song's music. Session plumbing is not one: "make the X song" or "fork an agent per song" (the pitch is the prompt), and edits across every song such as a sample-path change (git records those). A revisions list shows on the song's page, so noise there is visible.

Frontmatter must parse as YAML. Check with PyYAML after editing; prompt and brief are double-quoted JSON-style strings, so escape inner quotes.

Every song folder gets a README and a CLAUDE.md, like every other directory: README says what the song is and maps its files; CLAUDE.md imports it.

Add to a note pattern with note(). note(x).add(12), .add(roots) and .add(sine…) silently do nothing: Strudel cannot do arithmetic between a control pattern and bare numbers, and says only "[warn]: Can't do arithmetic on control pattern." Write .add(note(12)). Four songs shipped with this bug (2026-09-24); check a new song with get_logs for that warning before calling it done.

Dirt-Samples names lie. cr is six ride cymbals (RIDED*.wav), not a crash; hh27 holds a closed hat (:0), a crash (:1, 001_hh27crash.wav) and a hit. The crash measures −29.5 LUFS against the ride's −14.7, so swapping one for the other needs its gain matched by ear. List a folder before trusting its name: gh api repos/tidalcycles/Dirt-Samples/contents/<name> (2026-09-25).

A sung name must be read back by two whisper models. nix run .#transcribe -- FILE... prints each file as small.en and medium.en hear it (whisper.cpp, one transcription at a time on the machine, so parallel agents cannot exhaust memory); use it rather than loading Whisper yourself. Synthesised "Jev" is the hard case: SAPI's plain "Jev" comes back as Jeff, a vocoded "Jehv" as Jeb, and base.en hears Jeff even in the raw speech before vocoding, so check with small.en and medium.en. What passed (hey-listen, 2026-09-25): speech rate −4, a comma after the name, vocode --dry 1.5; spelling it out ("Jev. J, E, V.") reads every time.

A loop within one kind of section gets a times. A way back inside a groove or a drop (drop2c → drop2b, or a section followed by itself) lets Jev repeat the same material until the form's max: borrow-checker-blues could play 56 bars of groove, select-star 80 bars of keyUp (2026-09-26). times: n on a section is the most it plays in one performance, counted in total, since a loop is a cycle of different sections where none repeats back to back. Put times: 2 on the section each such loop returns to (once more round at most), then check the longest run of one kind of section a path can still take: where loops chain (a detour out of one loop into another), also cap the loop's last section (times: 1, so the repeat is cut short). A way back across kinds (a verse again after drop 2) is the song's form, which max bounds. A section that has played its times, or whose only ways to an ending run through one that has, is not offered; the written order is never capped. The spec's form table says each times.

A part's level is a whole step. dj[name].score.round(): out (0) is silent, back (1) plays at half velocity, full (2) as written. Masking at score.gt(.5) silenced a score of exactly .5, which Jev's own label calls "back" (2026-09-25). A level question named for the part's orbit is asked only about sections where that part plays as written (jevCore setWrittenParts), so no song lists that by hand; and every djJev has forget: ['into'], because Jev copied its own past "cut" into nearly every section.