Repository navigation
Conversation
A machine.yml still holding `runner_type` fails with "unknown key(s): runner_type", which is true but leaves the reader to discover on their own that the key used to be valid and that `labels` took its place. Two files in amd/skills are in exactly that state. The key cannot simply be accepted as an alias. `runner_type: instinct` named a hardware class, and resolving a class onto runner labels needs the second file this format dropped on purpose; `labels: [mi300x]` names the labels directly. So the old key is explained rather than translated. Keyed by a table so the next rename is a line rather than a special case. Signed-off-by: John Lybeck <john.lybeck@amd.com>
nowycondro
left a comment
There was a problem hiding this comment.
Review against ai-instruction-quality
Reviewed with the ai-instruction-quality skill from silogen/dev-tooling (the skill named in the request), worked top-down through its references/quality-checklist.md.
A scope note first, so the framing is visible. That skill's own description scopes it to AI instruction files — SKILL.md, references, scripts, cards, CLAUDE.md, AGENTS.md, agents, commands. This PR is Python in a tool that validates skill catalogs, not an instruction file. I applied the parts that genuinely map to what the diff changes — the error string is instruction text read by whoever (or whatever) has to fix a machine.yml, and the module comment and machine_plan docstring are instructions to future maintainers — plus the checklist's [cross] docs-vs-code and script-behaviour items. The rest is ordinary code review. Where the skill had nothing to say, I have not invented a finding to fill the space.
Verdict: no Tier 1 (blocking) defect. Two Tier 2 findings and two Tier 3 notes. Behaviour on valid files is unchanged, the whole test suite passes, and the design instinct here is right. The substantive gap is that the change solves one instance of a problem the repo's own tests say has five.
What this gets right
The checklist's content items land squarely in this PR's favour, and they are worth naming because they are the hard part:
- "Explain Why — the reason is a mechanism, never a citation." The new text says why the model changed —
labelsnames what GitHub actually matches a runner on, so there is no class-to-label mapping in a second file to keep in step — rather than just asserting a rename. A reader who hits this error on some other retired key can extrapolate from that. This is the checklist item most often failed, and it passes cleanly. - "Solve, don't punt." The error handles an expected failure in place with a useful message instead of leaving the reader to reverse-engineer the schema.
- "Avoid time-sensitive content" — superseded guidance lives in a named table rather than a dated aside, which is the
## Old patternsshape applied to a validator. - "Use concrete examples" —
runner_type: instinct→labels: [mi300x]makes the shape unambiguous. - Correctly refuses to auto-translate. Accepting the old key as an alias would need exactly the mapping file the format exists to avoid, and the checklist's "nothing buried in a script that the agent should decide" points the same way: a silent translation is a decision the reader cannot see or override, and a wrong one puts a skill on the wrong hardware.
- "Filtering errs permissive" — a plain typo (
typo_key) still gets the short message and is not dressed up as a migration. Verified. - No invisible Unicode; no line-length violations;
RETIRED_MACHINE_KEYSis disjoint fromMACHINE_KEYS.
Tier 2 — Important
1. Four of the five known retired keys still get the bare error [cross]
Checklist: "Parallel areas have symmetric coverage — adding a rule to one asks whether its siblings need it."
tests/test_skillscope.py:401 names the retired set explicitly, and it is not one key:
runner_type,runner,gate,environment, andreasonall used to live here.
RETIRED_MACHINE_KEYS holds one of those five. I ran each against machine_plan on this branch:
runner_type: instinct -> explained
gate: enable_mi_ci -> "unknown key(s): gate. A machine.yml holds only labels and os."
environment: behavioral-instinct -> "unknown key(s): environment. ..."
reason: because -> "unknown key(s): reason. ..."
runner: [a, b] -> "unknown key(s): runner. ..."
The realistic input is worse than any single key, because an old-format file carried several at once:
error: .../machine.yml: unknown key(s): environment, gate, reason, runner_type.
A machine.yml holds only labels and os.
`runner_type` is replaced by `labels`, ... `labels: [mi300x]`.
The reader learns what replaced one key of four and is left in exactly the state this PR opens by calling out for the other three — now with the added implication that the silent ones are different in kind from the explained one, which they are not.
The four are also the more confusing half. labels is at least a visible successor to runner_type; gate, environment and reason have no successor key at all — they moved out of the file and into the repo's workflow. machine.schema.json already states that ("the pull-request label rationing that pool, the environment holding its credentials — belongs to the repo and is passed to skillscope by the workflow, not restated here"), so the explanation exists and simply is not wired to the error path. The PR's own argument — "the next rename is one entry" — makes the case: the table is the right structure, and four entries it should already hold are missing.
Suggested, in the same shape as the existing entry:
"gate": (
"no longer part of this file. Which pull-request label rations a scarce "
"pool is the repo's decision, passed to skillscope by the workflow"
),
"environment": (
"no longer part of this file. Which environment's credentials pay for a "
"scoped run is the repo's decision, passed to skillscope by the workflow"
),
"reason": "no longer read. The labels say what the work needs; prose above them says why",
"runner": "replaced by `labels`, which adds to the base labels the workflow supplies",2. The stated motivating case no longer exists [cross]
The PR description says:
Two files in
amd/skills(hyperloom-workload-optimizerandserving-llms-on-instinct) are in exactly that state today, so this is the first thing anyone pointing skillscope at the catalog sees.
That is not true as of this review, and was not true when the PR was opened. Both files were migrated in amd/skills commit 52a827d6 ("Replace built-in harness with amd/skillscope harness", #206) on 2026-09-08. This PR was opened 2026-09-11, three days later. All four machine.yml files in amd/skills@main are valid today:
skills/hyperloom-workload-optimizer/evals/machine.yml os: [Linux] labels: [mi300x, gpu, rocm]
skills/serving-llms-on-epyc/evals/machine.yml os: [Linux]
skills/serving-llms-on-instinct/evals/machine.yml os: [Linux] labels: [mi300x, gpu, rocm]
skills/tracelens-analysis-orchestrator/evals/machine.yml os: [Linux]
(serving-llms-on-instinct/evals/machine.yml at 52a827d6~1 was indeed runner_type: instinct, so the observation was accurate when made — it just does not describe the catalog any more.)
This does not make the change wrong. Forks, unmerged branches, and anyone carrying an old file locally still hit it, and the checklist's point about a rule only having force where it loads applies to an error message too. But the urgency claim is what a reviewer weighs the change on, so the description should be corrected rather than left to be discovered — and with the catalog already clean, this is a change with no in-tree case exercising it, which is worth saying out loud.
Tier 3 — Polish and signals
3. The example is the right scenario with the wrong answer [file]
Checklist: "Examples are concrete and accurate." The message says a skill that asked for runner_type: instinct "now says, for example, labels: [mi300x]". The one real instance of that exact migration in the catalog resolved runner_type: instinct to labels: [mi300x, gpu, rocm], and docs/authoring-evals.md shows labels: [mi300x, gpu]. Three renderings of the same example, and the error message picks the thinnest one.
"for example" carries some of the weight, and the skill does allow a literal where the value is illustrating a format. But the risk is specific rather than stylistic: the message reads as a 1:1 substitution — the very translation the new comment correctly says cannot be made — and a reader who copies labels: [mi300x] for a ROCm workload lands on a pool that may not carry rocm. Matching the real migration (labels: [mi300x, gpu, rocm]) costs nothing and makes the "this is a change of model, not a rename" point self-evidently.
4. The continuation line collides with the bullet indent [cross]
datasets.structural_errors collects this message into the error list and cli._report_failures prints each as - {err}. The embedded \n puts the explanation at column 2 — the same column as the - marker, not the column 4 where the error text starts. Actual output:
Structural checks failed:
- /path/evals/machine.yml: unknown key(s): runner_type. A machine.yml holds only labels and os.
`runner_type` is replaced by `labels`, ... `labels: [mi300x]`.
In a list of several failures the explanation reads as a stray entry rather than as a continuation of the one above. \n (four spaces) would hang it under the text. Cosmetic, and it does not affect the direct machine_plan path where the message is already well-formed.
5. Nothing holds the table to the retired set [file]
test_documented_keys_match_the_parser is a good pattern — it pins machine.schema.json to MACHINE_KEYS so the two cannot drift. There is no equivalent tying RETIRED_MACHINE_KEYS to the retired keys enumerated in test_a_retired_key_is_rejected_rather_than_ignored, so the two lists can diverge exactly as they have. If finding 1 is addressed, hoisting that literal tuple to a module-level constant in the test file and asserting both tests read from it would keep them in step. Low cost, and it is the mechanism that would have caught this.
The new test itself is fine but narrow: assertIn("runner_type") and assertIn("`labels`") do discriminate against the old message (which had no backticks around labels), though neither asserts the replacement relationship the test is named for. assertIn("replaced by", message) would.
Checklist items I could not verify
Recorded per the checklist's evidence discipline rather than ticked:
[run]Verified. All 227 tests pass on this branch (python3 -m unittest discover -s tests, withpyyamlinstalled). I also exercisedmachine_planandcli._structural_or_exitdirectly against each retired key and against a plain typo; the outputs quoted above are real, not reasoned.[run]Not verified. Behaviour in real CI — whether the multi-line error renders as described in GitHub Actions log output, where folding and annotation parsing may differ from a plain terminal.[cross]Not verified. Whether any consumer outsideamd/skillsand this repo parses skillscope's structural-error output as one-error-per-line. If something does, finding 4 stops being cosmetic. I found no such consumer in either repo.
Summary
The design is right and the reasoning in the new comment is the best part of the diff — it explains a mechanism rather than asserting a rename, which is the thing that lets a reader handle a case the author did not foresee. Two things to settle before merge: fill in the other four retired keys the repo's own tests already name (finding 1), and correct the PR description, since the catalog it cites was cleaned up three days before this branch was opened (finding 2). Neither is a correctness bug and nothing here blocks.
🤖 Automated review by agent-hub — ai-instruction-quality on AMD AgentHub.
Model claude-opus-5[1m] · took 5m · diff 58660e9ccd57 · run
Not a human review. Reply here if a finding is wrong.
nowycondro
left a comment
There was a problem hiding this comment.
🤖 Automated review · review-code · head c5260efb
Review — no blocking findings
Full review of the whole change.
No ticket named; PR description only.
Blocking
None
Non-blocking
-
Only 1 of the 5 historically-retired
machine.ymlkeys gets the new explanation —skillscope/datasets.py:546-553,skillscope/datasets.py:613-623,tests/test_skillscope.py:402-415
RETIRED_MACHINE_KEYSholds onlyrunner_type. The repo's own pre-existing test,TestMachineRejections.test_a_retired_key_is_rejected_rather_than_ignored, documents thatrunner_type,runner,gate,environment, andreason"all used to live here." I reproduced this directly: feedingmachine_plana file with all five keys still raisesunknown key(s): environment, gate, reason, runner, runner_type...with the\nkeyis ...detail appended forrunner_typeonly — the other four get no explanation. Nothing tiesRETIRED_MACHINE_KEYSto that test's list, so the two can silently diverge further. The PR's own description frames this as deliberately scoped to the one currently-live rename ("so the next rename is one entry"), which is why this is non-blocking rather than a defect, but given the table-driven mechanism now exists, the other four are a natural, low-cost follow-up.
Confidence 75 · architectural · Fix: add entries forrunner,gate,environment,reason(the schema's own prose already explains wheregate/environmentmoved to), and consider hoisting the five-key list in the test file to a shared constant both tests read from. -
The appended explanation misaligns under the CLI's bulleted failure list —
skillscope/datasets.py:616-618,skillscope/cli.py:119-122
machine_planembeds\n(2 spaces) before each retired-key explanation.cli._report_failuresrenders each collected error asf" - {err}", which puts the first line's text at column 4 but leaves the embedded continuation at column 2 — under the-bullet marker rather than under the text above it. I verified this by running a multi-keymachine.ymlthroughstructural_errors()and rendering it the way_report_failuresdoes; the explanation prints as a visually separate bullet-like line rather than a continuation. Does not affect the directmachine_planexception message (which reads fine on its own), only theskillscope structuralbulleted-list path.
Confidence 80 · mechanical · Fix: use\n(4 spaces) so the continuation hangs under the error text instead of the bullet marker.
Decisions for the author
None
Positive signals
- A plain typo in
machine.ymlstill gets the short, unembellished message (the detail is only appended for keys actually present inRETIRED_MACHINE_KEYS), so the new code doesn't overclaim on inputs it wasn't designed for. - Keying the explanation by a
{key: prose}table rather than anif key == "runner_type"special case is the right shape for the stated goal of making "the next rename... one entry."
Deployment notes
None
What this covered
Single-pass review (2 changed files, 34 added lines in one coupled region — small enough for one worker to read completely rather than fanning out). Read skillscope/datasets.py in full, the full _report_failures/_structural_or_exit section of skillscope/cli.py, skillscope/schema/machine.schema.json in full, the relevant section of docs/authoring-evals.md, and the complete TestSchemaStaysInSyncWithParser/TestMachineSchema/TestMachineRejections/TestConfig test classes plus the file's imports and Repo/tier0_dataset helpers in tests/test_skillscope.py (did not read the remainder of that 2519-line file's unrelated test classes, e.g. routing/behavior/credentials/structure, which this diff does not touch). Diffed 0f734b2d...c5260efb (PR-open-time base, confirmed as the current merge-base). Ran the full suite (227 tests, all passing) and independently reproduced both findings above by executing machine_plan/structural_errors against a multi-retired-key machine.yml. Checked CI: all 5 check runs green at the reviewed head sha; no ticket is named by the PR. Noted but not a finding: origin/main has advanced past this branch's base (adding a third sandbox machine key); git merge-tree shows this still merges cleanly with no conflict.
Did not run: agent-instruction adherence (no CLAUDE.md/AGENTS.md in this repo); prior-changes pass (no accessible earlier review comments on this file's history beyond this PR's own thread, which per method is out of scope for a blind pass anyway).
Not reconciled against any discussion on this PR, including its existing review thread.
Not a human review. This is an automated code review; please verify findings independently before acting on them.
Review — needs workFull review of the whole change (2 files, +34/-0). Blocking
Non-blockingNone Decisions for the authorNone Positive signals
Deployment notesNone What this coveredFull read of both changed files in complete context — One independent blind sub-pass (impact-analysis, history, prior-PRs, agent-instruction-adherence, code-comment-compliance) ran as a separate subagent and is the source of the blocking finding above, which I independently re-verified against the source and with an actual test run. A second planned independent worker — a dedicated blind code-correctness pass over the diff — could not be spawned: the shared execution environment was at its concurrent-subagent cap for the duration of this review (two spawn attempts both rejected), so I performed that pass myself directly against the source instead of via an independent context; per the review method this is disclosed as a limitation rather than presented as a fully independent fan-out. No 🤖 Automated review by agent-hub on AMD AgentHub. |
A
machine.ymlstill holdingrunner_typefails with:True, but it leaves the reader to work out on their own that the key used to be
valid and that
labelstook its place. Two files inamd/skills(
hyperloom-workload-optimizerandserving-llms-on-instinct) are in exactlythat state today, so this is the first thing anyone pointing skillscope at the
catalog sees.
Now:
Why explain rather than accept
Accepting the old key as an alias would need a class-to-label mapping, which is
the second file this format deliberately does not have.
machine_plan's owndocstring makes the case:
So the old key is explained, not translated. Valid files behave exactly as
before; only the error path changes.
Keyed by a table rather than a special case, so the next rename is one entry.