Edit a scheme by describing the change (#22) #130
No reviewers
Labels
No labels
area/ai
area/backend
area/frontend
area/infra
area/scheduler
area/wled
good-first-issue
priority/high
priority/low
priority/medium
type/bug
type/chore
type/ci-cd
type/docs
type/feature
type/qa
v1.0.0
v1.1.0
v1.2.0
v2.0.0
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/Iris-WLED!130
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/22-refine"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #22.
A box on the scheme editor that takes "make it warmer and slower" and returns an edited scheme — reusing the existing provider abstraction, response validator, one-shot retry and daily cap, exactly as the issue anticipated.
Two decisions carry the feature
It proposes; it never writes
The endpoint returns a before and an after, and accepting one loads it into the editor's own controls. The user then saves with the ordinary Save — so an AI edit travels exactly the path a manual edit does: promotion to
source: user(#99), staleness cleared, audit entry written.Persisting directly would have let an AI edit of an approved scheme reach the lights with nobody seeing it, which inverts the review model the rest of the project is built on. It's also why refining an approved scheme is allowed at all — generation refuses to touch one, but a proposal that writes nothing is safe, and it's the case a user most wants.
A failed refinement raises rather than falling back
Generation falls back to a built-in template when the model fails, and that's right: generation has nothing to lose, and a template scheme beats no scheme.
Refinement starts from the user's own work. The same fallback would replace what they were editing with a generic template — losing work rather than declining to change it. So
RefinementFailedErroris a distinct exception, surfacing as a 502 whose message says the scheme is unchanged. And it is.Smaller things that took thought
A segment the model forgets keeps its current values. "Make it warmer" is not an instruction to delete a segment, and an omission is far likelier to be the model forgetting than the user asking. The
onflag is likewise carried from the current scheme rather than defaulted totrue, which would quietly switch a deliberately-off segment back on.The refine prompt deliberately omits generation's few-shot examples. Those are whole festive schemes; showing them during an edit invites the model to replace the user's scheme with something that resembles an example rather than adjusting what it was handed. It does share the effect catalogue, the response schema and the retry convention.
The instruction is length-bounded and rejected when blank. The daily cap counts requests, not tokens — so an unbounded instruction makes one pasted novel a single request against the cap and a very large bill. And whitespace satisfies
min_lengthwhile having nothing to act on, which would spend a cap slot on a question nobody asked. My own test caught the second case: I'd written it expecting a rejection and got a 503 from the provider path instead, which meant a stray space was reaching the model.A failed call still counts against the cap. Charging only for successes would make a broken provider an unlimited retry loop.
Tests
27 new backend tests, provider-mocked, covering the happy path and invalid responses as the issue asks. The ones that matter most assert what must not happen:
test_a_provider_failure_raises_rather_than_falling_backtest_the_scheme_survives_a_failed_refinementtest_it_writes_nothingtest_a_segment_the_model_forgot_keeps_its_current_valuestest_the_current_scheme_is_in_the_promptparses the scheme back out of the prompt and compares it, rather than substring-matching — the point is that the model receives the scheme faithfully, and a formatting assertion would pass on a prompt carrying the wrong one.test_the_proposal_can_be_applied_with_putgoes end to end: refine, sendproposalstraight back toPUT /schemes/{id}, confirm the colours land and the source promotes touser.Verification
Backend
ruffclean,mypyclean, 749 tests passing. Frontend innode:22:tsc -bclean,eslintclean.🤖 Generated with Claude Code