feat(route): give next-cost a writer, and make the rubric the only copy
The board line's next-cost field had a reader (board.sh) and no writer, so
its value was retyped by hand every session and drifted into several
competing spellings. Cleaning the data could not fix that: the cause was the
missing write path.
route.sh is that writer. Four scored traits of the next task -- path,
verification, reversibility, scope -- plus a required rationale, run through
the operator's model rubric moved here as the single copy. The row table is a
closed set of six values, so a seventh spelling cannot enter circulation, and
route-selftest.sh section 6 runs the round trip (route emits -> board parses)
inside one repo rather than across two.
Two spellings of one decision come out of one table: the rubric name for the
board line, the CLI alias for the command the operator pastes. Effort levels
are the set pinned in this marketplace; model aliases are gated against the
installed claude rather than hardcoded.
Three things worth naming:
- Escalation is asymmetric. Any single trait escalates; the cheapest row needs
all four at the cheap end. Underkill costs one session, overkill costs quota
every session -- but a wrong architecture call costs more than either.
- The Fable rows fire only from an explicit judgement flag, never inferred
from the last-session record. "The session did not finish" also covers
context exhaustion and operator interrupts, which say nothing about the
model, and Fable runs without an advisor.
- The trait block is a single-line HTML comment because board.sh's NESTE
extractor skips only lines that START with '<!--'. Measured first: a YAML
block or a multi-line comment silently replaces the repo's next step on the
board with "next_task:". Pinned by section 7.
board.sh is untouched as a program; its header now points at route.sh for the
value set so this does not reopen the two-specs defect 305f168 closed.
Selftests: coord 136, board 30, route 47 (new), node 7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017peNgsxVt1BR4BTuMwiPoX
This commit is contained in:
parent
305f1687b7
commit
0d1183800e
8 changed files with 808 additions and 11 deletions
152
skills/route/SKILL.md
Normal file
152
skills/route/SKILL.md
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
---
|
||||
name: route
|
||||
description: >-
|
||||
Decide which model and reasoning effort the NEXT session should run with, by
|
||||
scoring four traits of the next task and running them through the rubric row
|
||||
table (`route.sh`). Use at session end, whenever STATE.md's next step is
|
||||
written or rewritten, and whenever the operator asks what to launch the next
|
||||
session with: "what model should I use next", "which effort level", "close
|
||||
the session", "wrap up", "update STATE", "what should the next session run
|
||||
with", "is opus overkill here", "give me the startup command". Also triggers
|
||||
on Norwegian phrasings: "hvilken modell neste økt", "hvilken effort", "avslutt
|
||||
sesjonen", "oppdater STATE", "hva skal neste økt kjøre med", "er opus
|
||||
overkill", "gi meg oppstartskommandoen", "modell og effort". Trigger even when
|
||||
the operator names no model and no tool — choosing the model for the next
|
||||
session IS this skill. Not for choosing which REPO gets the next session:
|
||||
that is the `board` skill.
|
||||
version: "0.9.0"
|
||||
---
|
||||
|
||||
# route — what the next session should run with
|
||||
|
||||
The operator's closing line requires two things every single session: the model
|
||||
and effort for the next session, and a pasteable startup command. This skill
|
||||
produces both from evidence instead of from a hunch, and leaves the scoring
|
||||
behind in STATE.md so a wrong call can be found later.
|
||||
|
||||
**The division of labour is the whole design.** Scoring the four traits is
|
||||
judgement and belongs to you. Turning scores into a model is a lookup, and
|
||||
`route.sh` does it deterministically — same scores, same answer, every time,
|
||||
at zero token cost. Never "reason your way" to a model. If you find yourself
|
||||
weighing whether the task feels hard enough for Opus, you have skipped the
|
||||
scoring step and are doing the thing this skill replaces.
|
||||
|
||||
## The engine
|
||||
|
||||
ROUTE="${CLAUDE_PLUGIN_ROOT}/scripts/route.sh"
|
||||
|
||||
"$ROUTE" --path <known|partial|undetermined> \
|
||||
--verification <strong|weak|none> \
|
||||
--reversibility <cheap|costly|one-way> \
|
||||
--scope <local|multi-file|cross-cutting> \
|
||||
--rationale "why these four scores" \
|
||||
[--opus-xhigh-failed] \
|
||||
[--last-model <name> --last-effort <level> \
|
||||
--last-completed <yes|no> --last-corrections <n>]
|
||||
|
||||
There is no deployed copy anywhere else and no fallback path — one entry point
|
||||
is deliberate. Exit 2 means a bad or missing argument; read stderr and fix the
|
||||
call rather than dropping the flag. `route.sh --help` carries the full row table
|
||||
and the reasoning behind it.
|
||||
|
||||
The script writes nothing. It prints `row`, `rule`, `next-cost`, `command`,
|
||||
`fallback`, `fallback-command`, `route-line` and (when the record is given)
|
||||
`route-last`. **You** paste those into STATE.md.
|
||||
|
||||
## Scoring the four traits
|
||||
|
||||
Score the task the NEXT session will do — the one in the `👉 NESTE` block —
|
||||
not the one that just finished. Read the next step as written before scoring;
|
||||
if you cannot score it from what is written, that is a finding (see below).
|
||||
|
||||
| Trait | Score it by asking |
|
||||
|---|---|
|
||||
| `path` | Is the solution route already described? `known` = the steps are written down or the pattern exists in this repo. `partial` = the shape is clear, one real decision is open. `undetermined` = it has to be found first. |
|
||||
| `verification` | Will a machine catch the error? `strong` = tests, types or a compiler fail on it. `weak` = a smoke test or manual run would probably surface it. `none` = prose, API shape, a security judgement — a wrong answer just sits there. |
|
||||
| `reversibility` | `cheap` = a commit away. `costly` = touches published state, needs a migration or a follow-up release. `one-way` = a pushed tag, a public interface, a deletion. |
|
||||
| `scope` | `local` = one file or one function. `multi-file` = several files, one repo. `cross-cutting` = many subsystems, or more than one repo. |
|
||||
|
||||
`verification` carries the most signal and is the trait most often skipped.
|
||||
Strong verification means a cheap model's mistakes get caught and corrected —
|
||||
cheap model plus tight feedback beats an expensive model without it. When
|
||||
nothing verifies the output, model quality is the only defence left.
|
||||
|
||||
Three rules that keep the scoring honest:
|
||||
|
||||
- **Score the task, never the feeling.** "Hard", "complex" and "important" are
|
||||
not traits here. They are unfalsifiable, and they always resolve upward.
|
||||
- **`--rationale` is required and is the point.** It is where a misscore is
|
||||
caught weeks later, when the recommendation turns out to have been wrong. One
|
||||
sentence naming the evidence: "the pattern exists in handlers/, but the error
|
||||
handling is undecided" — not "medium difficulty".
|
||||
- **When torn between two scores, take the more expensive one.** Escalation is
|
||||
asymmetric by design: any one trait escalates, and row 1 needs all four at
|
||||
the cheap end.
|
||||
|
||||
## The specification check you get for free
|
||||
|
||||
If the next step is scored `path=undetermined` and no design phase is planned,
|
||||
**the task description is underspecified — the model is not too small.** Say so,
|
||||
and rewrite the next step until it can be scored. Upgrading the model to
|
||||
compensate for a vague specification is the most expensive form of
|
||||
procrastination available, and it hides the real defect.
|
||||
|
||||
This check is worth more than the tokens the routing saves. Do not skip it by
|
||||
scoring `partial` to keep things moving.
|
||||
|
||||
## The last-session record
|
||||
|
||||
Rows 5 and 6 (Fable) are history-dependent, so without a record they are dead
|
||||
policy. Write it every session, from what actually happened in the session that
|
||||
is ending:
|
||||
|
||||
- `--last-model` / `--last-effort` — what this session actually ran with.
|
||||
- `--last-completed yes|no` — did this session finish the next step the previous
|
||||
STATE.md set out? Answer about that step, not about the session in general.
|
||||
- `--last-corrections <n>` — how many rounds of rework it took. This is the
|
||||
cheap proxy for whether the routing was right.
|
||||
|
||||
Read the previous `route-last` line out of STATE.md before overwriting it.
|
||||
Pass `--opus-xhigh-failed` **only** when it says an `Opus 5`/`xhigh` session ran
|
||||
at this same next step and did not complete it. A session that ran out of
|
||||
context, was interrupted, or stalled waiting on another repo is not a model
|
||||
failure — and Fable runs without an advisor, so a wrong escalation here is
|
||||
expensive. When it is ambiguous, leave the flag off.
|
||||
|
||||
## Writing it into STATE.md
|
||||
|
||||
Three single-line HTML comments sit directly under the `👉 NESTE` heading, in
|
||||
this order:
|
||||
|
||||
<!-- board: status=in-progress; blocked-on=-; next-cost=Opus 5/high -->
|
||||
<!-- route: path=partial; verification=strong; ...; rationale=... -->
|
||||
<!-- route-last: model=Opus 5; effort=xhigh; completed=yes; corrections=1 -->
|
||||
|
||||
Splice the emitted `next-cost` value into the existing board line — leave
|
||||
`status` and `blocked-on` alone, they answer a different question and this skill
|
||||
knows nothing about them.
|
||||
|
||||
**They must stay single-line comments.** `board.sh` reads the first line under
|
||||
the heading that is not blank, not a heading, and does not *start* with `<!--`,
|
||||
and shows it as that repo's next step across every repo. A YAML block or a
|
||||
comment broken across lines therefore replaces the repo's next step on the board
|
||||
with `next_task:`. Measured, not assumed — `route-selftest.sh` section 7 pins it.
|
||||
|
||||
## Reporting it
|
||||
|
||||
Give the operator the two closing-line items and nothing more:
|
||||
|
||||
- **Modell neste økt:** the `next-cost` value, plus the `fallback` one row
|
||||
cheaper for quota pressure. Name the `rule` that fired — "path=partial" — so
|
||||
the call is auditable rather than asserted.
|
||||
- **Oppstartskommando:** the `command` string, in its own code block, with
|
||||
`/exit` named. **Never prefix it with `cd`:** one repo per terminal tab, so
|
||||
the working directory is already right. If the next step belongs in a
|
||||
different repo, say so in plain words — that is a different tab, not a `cd`.
|
||||
|
||||
If `command` and `fallback-command` are the same as the current session's model,
|
||||
say `/clear` is enough instead — but only if no newly installed plugin or skill
|
||||
needs a fresh process to be picked up.
|
||||
|
||||
Do not paste the whole output block. One row, the rule that produced it, the
|
||||
command.
|
||||
Loading…
Add table
Add a link
Reference in a new issue