Repository navigation
Map Model Tiers in One Config and Refuse an Unrequested Highest-Tier Dispatch - #3516
Conversation
…Dispatch Skills name a tier and defer its model to the carried AGENTS.md, and a repository whose AGENTS.md predates the tier table leaves `frontier` unresolved, so a session read it as "the frontier model" and dispatched at the most capable one. Close that from three sides: - spec/model-tiers.json states the tier-to-model mapping as data, per tool, and spec/validate.py holds the AGENTS.md table equal to it. - AGENTS.md moves the tier rule into its own "Model Tiers" subsection, says `frontier` is not the industry's frontier model, and has an orchestrator name a tier on every dispatch. The three skills that dispatch by tier carry that subsection as a generated include. - A model-tier-guard PreToolUse hook refuses an Agent or Workflow dispatch at the maintainer-only tier's model unless the session was launched with FLEET_ALLOW_HIGHEST=1, and the host safety block states the same restriction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Declare the hook and its test module in test_cli_usage, and run the test module in the validate action's coverage step. - Match a quoted `model` key in a Workflow script, as JavaScript allows. - State that the maintainer selects the highest tier at session start, which is what the hook enforces, in AGENTS.md and the safety block. - Show each tool's dispatch alias verbatim in the tier table, so a future Codex or opencode row needs no display transform. - Add the guard as requirement 10 of the agent-safety spec and to its audit list, and fold the two matcher helpers into one. - Shorten the new rule sentences the prose gate's sentence check found. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
|
A recorded local strict-review pass covers head |
- Resolve a relative or home-relative Workflow scriptPath against the session's cwd, and refuse a script file the hook cannot read rather than passing it unseen. - Name the configured default tier in the deny text, and match the model name case-insensitively without folding the key's case. - Say the kit refuses a dispatch that names the highest-tier model, which is what the hook can see, and state why requirement 10 sits in the host layer and what it lets through. - Add the guard's self-test to both Verify lists, and drop the incidental requirement counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #3516 +/- ##
===========================================
- Coverage 81.54% 81.53% -0.01%
===========================================
Files 26 26
Lines 8918 8964 +46
===========================================
+ Hits 7272 7309 +37
- Misses 1646 1655 +9
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved fail-closed guard behavior and validation/documentation gaps block safe approval.
14 open issues
Fail closed when scriptPath cannot be read · New Use consistent model-key matching in fail-closed paths · New Check missing configuration before launch allowance · New Describe the configured default model tier · New Validate the model-tiers configuration structure · New Limit host-hook claims to inspectable dispatches · New Add requirement 10 to the specification decision model · New Remove incidental requirement and hook counts · New Include the model guard self-test in verification lists · New Narrow highest-tier enforcement claims to inspectable inputs · New Share PreToolUse registration validation logic · New Reuse one helper for hook group registration · New Remove the incidental requirement count · New Remove the incidental requirement count · New
What changed in this PR
This pull request centralizes model-tier mappings, propagates tier guidance through fleet skills, and adds a Claude Code guard against unrequested highest-tier dispatches.
Changes:
- Adds canonical model-tier configuration and validation against
AGENTS.md. - Deploys and tests the Claude Code model-tier guard and registration logic.
- Updates safety documentation, generated skills, digests, and CI coverage.
| File | Description |
|---|---|
tests/test_spec_validate.py |
Tests model-tier configuration and table invariants. |
tests/test_cli_usage.py |
Classifies the new hook and tests for CLI checks. |
spec/validate.py |
Validates model-tier configuration and documentation consistency. |
spec/model-tiers.json |
Defines the canonical tier-to-model mapping. |
host-setup/agent-safety/README.md |
Documents requirement 10 and host-safety auditing guidance. |
host-setup/agent-safety/opencode/README.md |
Updates opencode audit instructions. |
host-setup/agent-safety/codex/README.md |
Updates Codex audit instructions. |
host-setup/agent-safety/claude/test_model_tier_guard.py |
Tests model-tier guard decision behavior. |
host-setup/agent-safety/claude/test_install.py |
Tests guard deployment, registration, configuration, and digests. |
host-setup/agent-safety/claude/README.md |
Documents the model-tier hook and installation layout. |
host-setup/agent-safety/claude/model-tier-guard.py |
Implements highest-tier dispatch protection. |
host-setup/agent-safety/claude/install.py |
Deploys the mapping and registers the model-tier hook. |
host-setup/agent-safety/claude/claude-md-safety.md |
Adds host-wide model-tier safety guidance. |
AGENTS.md |
Defines model-tier rules and the mapping table. |
.github/skills/unattended-handoff/SKILL.md |
Carries model-tier guidance into the generated skill source. |
.github/skills/local-strict-review/SKILL.md |
Carries model-tier guidance into the generated skill source. |
.github/skills/backlog-burndown/SKILL.md |
Carries model-tier guidance into the generated skill source. |
.github/actions/validate/action.yml |
Runs the new model-tier guard tests in CI. |
.claude-plugin/fleet-skills/skills/unattended-handoff/SKILL.md |
Updates the generated Claude skill distribution. |
.claude-plugin/fleet-skills/skills/local-strict-review/SKILL.md |
Updates the generated Claude skill distribution. |
.claude-plugin/fleet-skills/skills/backlog-burndown/SKILL.md |
Updates the generated Claude skill distribution. |
.claude-plugin/fleet-skills/.source-digests/unattended-handoff |
Refreshes the unattended-handoff skill digest. |
.claude-plugin/fleet-skills/.source-digests/local-strict-review |
Refreshes the local-strict-review skill digest. |
.claude-plugin/fleet-skills/.source-digests/backlog-burndown |
Refreshes the backlog-burndown skill digest. |
.agents/skills/unattended-handoff/SKILL.md |
Updates the authored unattended-handoff skill. |
.agents/skills/local-strict-review/SKILL.md |
Updates the authored local-strict-review skill. |
.agents/skills/backlog-burndown/SKILL.md |
Updates the authored backlog-burndown skill. |
Review effort: Lite
|
A recorded local strict-review pass covers head |
…Tier - unattended-handoff and backlog-burndown dispatch at `highest` only where the session was also launched with it allowed, and stop on a refusal rather than continuing at a tier nobody chose. AGENTS.md names the launch setting and says naming the tier in a prompt is not it. - Match the blocked alias as a word inside a Workflow model value, so a full model id is refused too. - Run the guard's self-test under coverage in the validate action, since the subprocess tests are not traced. - Name the data file in the payload docstring and the --report problem, assert the stale-config test's message, and update the spec's scope and decision-flow summaries for requirement 10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The guard's deny text says to stop and report where the maintainer asked for the tier, and names the default tier only otherwise, so it agrees with the skills that forbid dispatching lower. - The unattended-handoff picker step takes the same launch-allowance condition and refusal handling as the worker step. - The guard's documented blind spots name a child workflow a script starts, beside the saved workflow already listed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A recorded local strict-review pass covers head |
A PreToolUse hook cannot read the session's own model, so a session launched on the highest tier gives that tier to every dispatch naming no model, whatever the launch setting says. AGENTS.md now says such a session names a tier on every dispatch, and the guard's documented blind spots list the inheriting dispatch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude Code ignores a fork's model parameter, so naming a tier on a fork does not move it off a highest-tier session's model. A dispatch naming no model also takes its agent definition's model before the session's. AGENTS.md now says both, and the guard's blind-spot lists name the fork. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guard reads a fork's model like any other dispatch's, so only a fork naming another model passes unseen. "Every other dispatch" read as alternate dispatches, so the rule now says every dispatch meant to run below the highest tier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A recorded local strict-review pass covers head |
The refusal sentence had drifted past the inheritance sentences, so its "any other dispatch" read as every dispatch naming the tier, allowance or not. The bullet now states who selects the tier, how, and that the kit refuses a dispatch naming it only without the launch setting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rewrite left "naming it partway through a session means restarting the session with it allowed" with no actor, which reads as an instruction to whichever agent sees the name. The bullet now says the maintainer restarts the session, and an agent never sets the allowance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A recorded local strict-review pass covers head |
spec/validate.py now reports a tiers value that is not a list, a tools value that is not an object, and a tool entry missing its name or models object, rather than raising on the first one it meets. The agent-safety status table also drops its count of PreToolUse hooks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tier that is not a string would otherwise pass the list check and then raise on set(), so the check reuses the existing helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A recorded local strict-review pass covers head |



Summary
Skills name a model tier and leave its model to the cwd repository's carried
AGENTS.md. A repository whoseAGENTS.mdpredates the tier table leavesfrontierunresolved, and sessions read it as "the frontier model" and dispatched subagents at the most capable, maintainer-only model. This closes that from three sides:spec/model-tiers.jsonstates the tier-to-model mapping per tool. Only Claude Code has a row today, and Codex or opencode join as data.spec/validate.pyholds theAGENTS.mdtable equal to it, since a carried file cannot hold a generated include region.AGENTS.mdmoves the tier rule into a "Model Tiers" subsection. It saysfrontieris not the industry's frontier model and that an orchestrator names a tier on every dispatch.local-strict-review,unattended-handoff, andbacklog-burndowncarry that subsection as a generated include, so the mapping resolves even in a repository whose carriedAGENTS.mdis stale.model-tier-guardPreToolUse hook onAgent|Workflowrefuses a dispatch at the maintainer-only tier's model unless the session was launched withFLEET_ALLOW_HIGHEST=1. The installer deploys it with the mapping and verifies its registration. It is requirement 10 of the agent-safety spec, and the host safety block states the same restriction.Verification
spec/validate.pypasses, andtests/test_spec_validate.pycovers the table check (171 tests).test_cli_usagepasses (16),build_dist.py --checkis current, and ruff and mypy are clean.commentslabel.Local Strict Review
Ten recorded passes ran, the last over 6fdde9b with no finding. Every introduced finding from the earlier passes was fixed on this branch, except one the maintainer deferred:
host-setup/agent-safety/claude/install.py: the tier guard's registration check and its strip-then-register block follow the guard's blocks rather than sharing a helper. Deferred to Share One Registration Helper Between the Agent-Safety Installer's PreToolUse Hooks #3517. (introduced, consolidation)Pre-existing findings, not filed
host-setup/agent-safety/README.mdnear lines 6, 168, and 214, andhost-setup/agent-safety/claude/install.py:196: sentences over the prose gate's optional sentence-length bound, on lines this change touched only incidentally. (pre-existing, style)🤖 Generated with Claude Code