The instructions for my MIDI editor told an AI agent not to use a feature I had already shipped.
Battuta is a command-line tool I use to work on music with an agent. The agent can inspect a MIDI file and write requests to change notes or instruments; I listen to judge the result. The tool already supported changing the instrument assigned to a MIDI channel. But its agent instructions still said those edits would fail to parse. Following that advice would leave a supported operation unused.
01 / The disagreement
The tool supports it. The instructions say not to use it.
Changing the instrument assigned to a MIDI channel
Implemented operation
set_programThe tool supports this edit.
Agent instructions
Do not write this edit.The instructions said it would fail to parse.
The four accounts of the tool
Implementation
10operation kinds
Command help
7operation kinds
Agent instructions
6operation kinds
README
Pendingcontroller editing
I had updated the code without keeping its descriptions in agreement. The README, command help and agent instructions each carried their own account of what the tool could do. Each had a reasonable purpose: introduce the project, explain a command, or tell an agent how to work with it. Maintaining an operation list in each place meant that adding a feature could leave several different answers behind.
One remaining list can still go stale
The first repair brought those descriptions into agreement. A later pass removed the exhaustive lists outside mid apply --help and directed readers there. The README could still introduce the tool; it no longer had to enumerate its operations.
That reduced the maintenance work, but the help was still written by hand. The next code change could leave it behind again. A test could compare the help with the operations the tool accepted, except that writing an expected list into the test would create another copy to maintain.
The parser already knew the answer. Every edit request names an operation in a field called kind. Give it an unknown name and its error lists the names it would have accepted. The test deliberately submits an empty name:
{ "edits": [ { "kind": "" } ] }
The request is supposed to fail. The useful part is the rejection: the accepted names come from the same type definition that the parser uses, so adding an operation changes the test’s expectation without a second list being updated by hand.
The test then runs the built command’s apply --help and compares what it says with those names. It looks for both missing operations and names the help offers that the parser does not recognise. An omission can keep a caller from discovering a feature; an invented name sends them toward a request that will be rejected.
02 / The comparison
Two small changes expose both kinds of drift.
A real operation disappears from the help
Remove resize_note from the help.
Help
Not mentionedParser
resize_noteThe check finds an accepted name missing from the help.
The help offers an operation that does not exist
Replace delete_note with make_sadder in the help.
Help
make_sadderParser
Not acceptedThe check finds an example naming an unknown operation.
delete_note from its example.The implementation record reports deliberately breaking the help both ways. Removing resize_note was caught. Replacing delete_note with make_sadder was caught too. These experiments checked that the test would object when the help changed, even though the underlying editing code had not.
This still leaves plenty for a documentation review to do. An operation can keep the same name while its parameters change, and a correctly spelled name can accompany a misleading explanation. The check covers the inventory. Deciding whether the instructions actually teach someone how to use those operations takes a different kind of reading.
Sources and verification scope
The documentation correction records the ten / seven / six disagreement and the instructions that discouraged supported edits. The consolidation record removes the remaining exhaustive inventories outside the command help.
The contract tests at the inspected revision provide the code excerpt and the scope of the comparisons. The JSON request in the article is deliberately invalid. The code below reformats the expression used to obtain its rejection; it is an excerpt, not a standalone program.
The implementation record reports the two deliberate help mutations and identifies the release containing the documentation disagreement as 0.1.1. Those historical experiments were not rerun for this note. The figures explain the recorded state and test mechanism; they are not runtime screenshots.
The error text is obtained here; the rest of the helper reads the names following expected one of.
let refusal = serde_json::from_str::<battuta::EditSet>(
r#"{ "edits": [ { "kind": "" } ] }"#,
)
.expect_err("an empty kind is not a kind")
.to_string();
The name checks have a narrower scope than full documentation validation. One searches the help text for accepted names; the other reads names from JSON example lines. The suite also checks that its extraction from the error text is plausible. A separate fixture supplies a parseable example per operation. None of these checks alone establishes the musical result of an edit.