Fourteen generic moments of work (session.start, work.start … reply.ask)
plus the skill.<name> family, each with what it means and the kinds of
action that reach it, written for any kind of work rather than software
alone. The catalog is code because every install needs the same mount
points; which actions reach a moment is per-install data (step 2).
require_moment refuses an unknown name with the catalog listed, so a
typo cannot become a mount that never fires. list_moments (read-only)
and GET /api/retrieval/moments hand out the same catalog.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The other half of the bargain in milestone 416 step 4. The model moves
these dials; this is what makes that reviewable rather than merely
automatic.
THE HOLE THIS CLOSES
Every retrieval floor is an ordinary settings key, and `/api/settings`
accepts any key at all. A floor written through it landed correctly and
recorded nothing — a tuning history with holes in it, which is worse
than no history because it reads as complete.
So the generic endpoint now routes registry-owned keys through
`set_dial` instead of writing them as plain rows. ROUTED, not refused:
refusing would only work for callers that had been updated, while this
way the form, a script, and an old client all leave the trail, and
there is no version of "forgot to use the other endpoint". Clearing a
control is written as an explicit set back to the shipped default,
because the operator reverting something is the single most important
move this history can record.
`set_dial` now also refuses to record a no-op. The Settings form
re-sends every field on every save, so without that one press of Save
would write six rows saying the operator set six dials to the numbers
they were already on — and a history nobody can skim is one nobody
reads.
WHAT THE OPERATOR GETS
`/api/retrieval/surfaces`, `/surfaces/<name>` and `/tuning-history`,
with `actor` fixed server-side rather than taken from the payload: a
payload-supplied actor would let a model claim to be the operator, and
"did I do this, or did the session?" is the first question this list
is asked.
In Settings: the five missing BUDGETS (until now only auto-inject had
one, so the only control over a noisy surface was to raise its bar —
which discards that surface's best candidates along with its worst),
and a "What has been tuned" panel showing each change, who made it, and
the reason given. The operator's own changes are marked.
The MCP tool demands a reason; these endpoints do not. That asymmetry
is deliberate and stated in routes/retrieval.py: the requirement exists
to make the MODEL read the records before moving a number on someone
else's behalf, and the operator is that someone — a mandatory
justification box on every control would be friction charged to the one
participant who owes no explanation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy