Eigenstate
Notes / Ansatz

My tool’s instructions hid a feature it already had

An agent following the documentation would avoid a supported edit. I needed a check that could catch the disagreement.

Jason Shen Draft

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_program

The 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
The recorded disagreement, with the instructions paraphrased. The counts describe the wider mismatch across the repository.

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 mentioned

Parser

resize_note

The 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_sadder

Parser

Not accepted

The check finds an example naming an unknown operation.

Illustrations of the reported help mutations, showing only the name being compared. The second case highlights the invented name; that edit also removes 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.