Disclosure: I’m Giang Pham, Pianory’s maker. AI helped draft this article and review its claims against the repository. It describes source behavior and existing unit-test cases, not a runtime study or evidence of better learning outcomes.
A practice app can measure how long a screen stayed open. That does not tell it whether a pianist can play the passage. If a planner treats time or a fast playback setting as proof of progress, it can make a difficult task harder for the wrong reason.
Pianory’s goal planner uses a smaller, visible signal: the learner’s own verdict after a task. Its session form offers “Still stuck,” “Playable slowly,” and “Fairly steady.” The session record separately stores the player tempo, and the type comment explicitly says that percentage is “not a measure of skill.” The planner does not listen to the piano or verify the learner’s assessment.
The planner filters to sessions for one goal and ignores review sessions, then reads the newest ordinary session. Its basic ladder is straightforward:
Here is the core boundary in the implementation:
const tempo = last.playbackTempoPercent;
if (last.rating === 'stuck') {
tasks.push(task('slower', passage, last.hands, tempo - TEMPO_STEP));
} else if (last.rating !== 'steady') {
tasks.push(task('repeat', passage, last.hands, tempo));
}
TEMPO_STEP is 10. The separate steady branch chooses the next hand, both hands, or the next tempo step. The planner returns advice; it does not turn every elapsed minute into a promotion.
Suppose a goal covers bars 9–16 for both hands at 60% of the written tempo. The first plan asks for the right and left hands separately at 60%. After a steady right-hand session, it asks for the left hand. Once each hand has been steady at 60%, it asks for both hands at 60%. After a steady both-hands session, it can ask for 70%. If the learner instead reports being stuck at 90%, the next task is the same hand setup at 80%.
The repository’s unit-test examples also probe misleading signals. One gives a session 86,400 practiced seconds and a 140% playback tempo but rates it “Playable slowly”; the expected task is still a repeat at 140%, not a harder step. Another checks that a steady session at 95% advances to 100%, rather than skipping past the target. These are source tests I inspected; I did not run them for this draft.
That suggests a useful test pattern for any adaptive workflow: hold the learner’s explicit verdict constant while making the tempting proxy extreme. A long session with a “stuck” verdict should not advance. A short session rated steady should follow the rule’s next step. The assertion should check the returned task, not just that a planner function ran.
A self-rating is still a self-rating. Pianory’s code can use it to suggest a smaller next task, but it cannot establish accuracy, comfortable fingering, or whether a learner played the passage correctly. That is a deliberate product boundary in this implementation, and it keeps the planner’s promise modest: help choose what to work on next from information the learner actually supplied.
The useful engineering idea is not that every practice app needs this exact ladder. It is that a recommendation should name the signal that moves it forward, keep unrelated telemetry out of that decision, and make the rule easy to test. What input should count as evidence of progress in a learning product?