Six follow-ups on 0d11838, three of which mattered.
The model-alias gate passed for the wrong reason: after the quoted-alias grep
it fell back to an unanchored grep for the bare word, which matches "opus"
anywhere in `claude --help` and would have reported success even if --model
stopped accepting the alias. A gate that cannot fail is worse than no gate.
Only the quoted form is matched now, and the failing aliases are named.
--last-model and --last-effort were unvalidated free text while the other two
record fields were gated. The next session READS the record back to decide
--opus-xhigh-failed, so a drifted spelling there rebuilds the exact
reader-versus-writer drift this script exists to remove, one field over. Both
are closed sets now: the row table's three model names and the verified effort
levels. That also makes the record's sanitizing dead code, so it is gone.
The skill told future sessions to write the record "every session" while
STATE documented that the effort level is not observable from inside a running
session. A session following both would have fabricated the value, and a
fabricated effort reads back later as a measurement. The skill now says: ask
the operator, and omit the record rather than guess -- explicitly including
that reading it off the previous board line measures what was PRESCRIBED, not
what was RUN.
Also: README said "seven bash scripts" (nine files, six user-facing) and its
skills badge still said 2; selftest counts updated to 50.
Verified, not assumed: the installed plugin cache at 0.9.0 contains only
board and coord-send, so route is not discoverable until a release bumps it --
the smoke test STATE had queued before release would have failed with 127 for
a reason unrelated to the skill. STATE reordered to release-then-test. The
manifest is auto_discover, so no skills array needs an entry.
check-versions.mjs is green (11 OK, 0 ERROR) with the new skill at 0.9.0.
Selftests: coord 136, board 30, route 50, node 7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017peNgsxVt1BR4BTuMwiPoX
165 lines
8.8 KiB
Markdown
165 lines
8.8 KiB
Markdown
---
|
|
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 whenever all four fields are actually known, from what happened
|
|
in the session that is ending — never from what STATE.md prescribed:
|
|
|
|
- `--last-model` / `--last-effort` — what this session actually ran with. Both
|
|
are closed sets (`Sonnet 5|Opus 5|Fable 5`, and the verified effort levels),
|
|
because the next session compares these values rather than just displaying
|
|
them.
|
|
- `--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.
|
|
|
|
**Effort is not observable from inside a running session.** The model is
|
|
readable from the environment; the effort level the operator launched with is
|
|
not. So do not infer it, and in particular do not read it back from the previous
|
|
board line — that measures what was *prescribed*, not what was *run*, and the
|
|
two come apart exactly when the record would be most interesting. Ask the
|
|
operator for the effort level when closing the session. If they are not there to
|
|
ask, **omit the record entirely** — it is all four fields or none. A guessed
|
|
effort is worse than a missing one, because it reads back later as a
|
|
measurement.
|
|
|
|
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.
|