From ea15ca4fd7477d2cb82b59d2775241c9b4270d5b Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Tue, 27 Oct 2026 14:08:00 -0700 Subject: [PATCH 1/3] Carry the Stale AGENTS and GOVERNANCE Verbatim Sections From Hub f60f57d Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 17 +++++++++++------ GOVERNANCE.md | 5 ++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 67a6bd5..b78b0db 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,20 +54,25 @@ An agent session is billed on the context it carries, not the work it does. Ever - **Report an execution boundary separately from a check finding.** A denied path, network request, or Docker socket says the check did not run. Preserve that failure, then use the executor's approval mechanism for the required rerun. Request the narrowest reusable command prefix the executor supports. Report the rerun's result as the verification evidence. - **Bound every probe, and prove it ended.** A throwaway probe or test harness runs under `timeout`, and one that can recurse or fan out also runs under a task and memory cap. Start it in a process group of its own, as `setsid --wait timeout ` on Linux, and end it by that group with `kill -- -`. Without `--wait`, `setsid` returns at once when its caller leads a process group, and the probe runs on detached. A `pkill -f` pattern that matches nothing exits 1 and kills nothing. Then confirm with `pgrep` that nothing from any earlier run survives, since a later clean run proves nothing about an earlier one. Never suppress a cleanup command's output or exit status. A command the harness moved to the background, where no background job was intended, is stopped and confirmed gone before work continues. -### Delegation +### Model Tiers -A rule, a Skill, or a brief names a model tier, never a model. A model release is then an edit to the table below alone. Reasoning effort is a separate axis stated beside the tier, such as `bulk` at low effort. +A rule, a Skill, or a brief names a model tier, never a model. A model release is then an edit to the table below and to the hub's `spec/model-tiers.json`. That file states the same mapping as data, and the hub's spec gate holds the two equal. Reasoning effort is a separate axis stated beside the tier, such as `bulk` at low effort. -- **`frontier`** is the strongest reasoning the fleet uses by default, for rule text, design, spec logic, and adversarial review. +- **`frontier`** is the fleet's default reasoning tier, for orchestrating, rule text, design, spec logic, and adversarial review. It is not the most capable model available, which is `highest`. - **`standard`** is ordinary implementation and driving a pull request. Judging a review finding is never mechanical, however often it repeats, so it runs at `standard` or above. A `standard` driver's evidence-backed decline of a finding raised on its pull request stands, since that pull request's reviewers and its merge gate check it. - **`bulk`** is the cheapest model that does the work correctly. It is for high fan-out mechanical work, such as an extraction or a status check. -- **`highest`** is the most capable model available. Only the maintainer selects it, naming the tier or the model in the session or the invocation. +- **`highest`** is the most capable model available, and only the maintainer selects it. The maintainer chooses it for the session itself, or allows it for the session's subagents at launch. The host's agent-safety kit reads that launch setting, `FLEET_ALLOW_HIGHEST=1` on Claude Code. Naming the tier in a prompt does not set it. A maintainer naming it partway through a session restarts the session with it allowed, and an agent never sets it. Without that setting, the kit refuses a dispatch that names it. A dispatch naming no model runs at its agent definition's model where one is set, and otherwise at the session's own. A fork always runs at the session's model, whatever it names. So a session launched on `highest` names a tier on every dispatch meant to run below it, and uses no fork for such work. A session never reaches for it because a task looks hard. | Tool | `highest` | `frontier` | `standard` | `bulk` | | --- | --- | --- | --- | --- | -| Claude Code | Fable | Opus | Sonnet | Haiku | +| Claude Code | `fable` | `opus` | `sonnet` | `haiku` | + +Each cell is the name that tool's subagent dispatch takes, which follows the latest model of its family. A tool with no row here, or one that cannot name a model per subagent, runs every tier at the session's own model. -A tool with no row here, or one that cannot name a model per subagent, runs every tier at the session's own model. +- **A tier resolves only through this table.** A word such as "frontier", "strongest", or "most capable" never resolves to `highest`. The industry's "frontier model" is the opposite of what this tier means here. A session that cannot see this table names no model rather than guessing one, so the subagent runs at the session's own model. +- **An orchestrating seat runs at `frontier` and names a tier on every dispatch.** It defaults a subagent to `frontier`, and picks `standard` or `bulk` deliberately for execution or mechanical work. A dispatch that names no model inherits whatever the session runs on, which is a choice nobody made. + +### Delegation - **Delegate exploration, keep judgment.** A subagent starts from an empty context and returns only its conclusion, so a wide search, a multi-file audit, or a "which of these is affected" question costs a fraction of the same work inline. Delegate when the finding compresses to a short answer, and stay inline when the intermediate detail drives the next edit. - **Match the model tier to the judgment, not to the diff size.** Mechanical work (a known-shape edit repeated across files, an extraction, a status check, a lint fix) runs at `bulk`, at the lowest effort that holds. State the tier in the delegation itself rather than accepting the default. A change to a gate, a ruleset, a release condition, or a carried governance section is a design change however small it looks. diff --git a/GOVERNANCE.md b/GOVERNANCE.md index bcac9bb..5890958 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -109,7 +109,7 @@ Line-ending governance for an operational repo is in [Line Endings](#line-ending A repo whose own stacks or scripts read local runtime credentials from disk, most commonly an operational repo's Docker Compose stack, keeps them in a dotted `.secrets/` directory at the repo root. This is the repo-scoped counterpart to the host-scoped `~/.secrets/` convention a repo's own `OPERATIONS.md` may document, and it is a different thing from `spec/secrets.json`, the CI/GitHub Actions secret-name registry `spec/audit.py` cross-checks. `spec/secrets.json` governs what a workflow reads from GitHub Actions. This convention governs what a repo's own process reads from its own checkout. - **The directory is named `.secrets/`, dotted, never a bare `secrets/`.** -- **A secret file's stem is lowercase snake_case** (`homeassistant_db_password`, `mail_relay.env`), per [`CODESTYLE.md` "Script and Utility File Names"](./CODESTYLE.md#script-and-utility-file-names). +- **A secret file's name is lowercase fields joined by `_`** (`homeassistant_db_password`, `mail_relay.env`), per [`CODESTYLE.md` "Script and Utility File Names"](./CODESTYLE.md#script-and-utility-file-names). - **A single opaque credential file carries no extension** (`homeassistant_db_password`, not `homeassistant_db_password.txt`), the same reason `README` and `LICENSE` carry none. It is a security property, read but never sourced, not a formatting preference. - **A structured credential keeps its format's extension** (`.json` for structured config). - **The shared env file is named for what it configures**, not a bare `.env` (`docker.env` for a repo whose stacks are Docker Compose), so a second env-shaped file added later stays unambiguous. @@ -123,6 +123,7 @@ A repo whose own stacks or scripts read local runtime credentials from disk, mos A fresh checkout then documents its own required shape without ever exposing a real value. This negation keeps a real secret file out of a **new** commit. It does not remove one already tracked: `.gitignore` has no effect on a path git already follows. A real secret file found tracked is removed from the index (`git rm --cached `) and its credential is rotated, not just added to `.gitignore` going forward. - **`.secrets/README.md` is a catalog**, one row per file naming what it holds and what consumes it, plus a short note on how the directory relates to `~/.secrets/` where the repo also touches that. +- **A repository whose shell scripts read a credential from disk declares the `host-secrets` type, and reads each one through the carried readers.** A credential is a secret file or an env file holding one, in `.secrets/` or in the host-scoped `~/.secrets/`, and this bullet reaches both. A single inline read counts, since it is the read a bare existence check and a sourced file get wrong. A script sources its carried `lib/fleet/sh/secrets.sh`, per "Hub-Hosted Tooling", and checks the file before reading it. A private file takes `require_secret` or `secret_set_verdict`, and one a container reads as a Docker secret takes `secret_verdict` with `shared`. It reads one value from an env file with `read_env_value` rather than sourcing the file into its own shell. ## Hub-Hosted Tooling @@ -130,6 +131,8 @@ The fleet's tooling lives in the hub once and a repository runs it from there ra **What a repository carries and what it reaches is decided by what the content is.** It carries the content it is audited against and the configuration that describes it, meaning its rule text and the files the manifest declares. It reaches machinery whose content is identical in every repository, meaning the prose and repository gates, the review digest, and the configuration script, because a file holding no per-repo content is a copy whose only future is to go stale. A tool named in a carried rule is therefore named as the hub's, since the alternative is a pointer to a path the reader does not have, and a pointer that resolves nowhere teaches the reader that a pointer in carried text is decorative. +**Code a repository's own runtime executes is the exception, and it is carried.** Such a library runs on a host the project owns, often unattended and sometimes as root. Fetched from the hub at run time, it would let a GitHub outage or a bad `main` commit stop a scheduled host job. It would also run code the repository never reviewed against its own secrets. So offline operation and review in the repository that runs it outweigh the staleness of a copy. A gate differs, since "the tool did not run" is an acceptable answer there and CI is the backstop. The hub hosts the library once. A repository whose declared types select it carries it byte-for-byte, as a tree the hub's `spec/files.json` declares. The hub owns the carried directory whole. The repository extends the library in a file of its own outside it that sources it, never by editing the copy. The staleness is made visible rather than accepted. The repository gate compares each carried tree with the hub tree its pinned action holds. It reports a difference as a warning that never fails the gate. A failure would break every downstream pull request on each hub fix. The audit reports a stale copy, and the resync refreshes it. + **Reaching it is a checkout of the hub rather than a copy of one file.** A tool reads the payloads, tables, and sibling modules beside it, so a single file lifted out of the tree runs against whatever the caller happens to have, which is the copy problem again in a shorter loop. Read `main`, the promoted and gated state, and fetch immediately before running, because a clone is whatever it last fetched rather than the branch it names, and a stale clone answers confidently instead of failing. Name the tool by its path in that checkout and name the target explicitly, since a tool that defaults to the current directory or the current repository resolves somewhere either way, and a result computed against the wrong repository is well-formed. Which directory the command runs in is the tool's own contract rather than a rule here, so a gate reading a working tree runs in the repository under test while a tool taking its target as an argument runs anywhere and is given one. What the rule forbids is letting a default decide which repository the answer is about. **A loader is outside this section rather than exempt from it.** The rule above governs a tool that reads hub content, because a tool reads the payloads, tables, and sibling modules beside it. A loader reads none of them: it obtains a tree and hands control to a tool inside that tree, on a host that cannot yet obtain one. The bound is what it may contain rather than who runs it, and it is one line: a loader references no path inside the tree it fetches except the single entry point it hands control to, and everything else it touches is the machine or the network. A loader that grows a second path into that tree has become a tool and is governed above. From 4fb5603db6aed81843c651c87cf0ed3505f9ef42 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Tue, 27 Oct 2026 14:08:14 -0700 Subject: [PATCH 2/3] Carry the Hub's Env and Secret File Naming Rules Into CODESTYLE Co-Authored-By: Claude Opus 5.5 --- CODESTYLE.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CODESTYLE.md b/CODESTYLE.md index fbf502a..defa4d7 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -54,12 +54,16 @@ A command-line script is one a person or an agent runs by name. A hook its host | Shell `.sh` and PowerShell `.ps1` | lowercase-hyphenated | The Linux command convention. A cross-platform pair keeps one name, `sync-tools.sh` beside `sync-tools.ps1` | | Windows batch `.cmd` and `.bat` | lowercase-hyphenated | The same convention as shell and PowerShell. A shim that puts a command on `PATH` takes the name of the command it provides, `sync-tools.cmd` launching `sync-tools.ps1` | | PowerShell functions inside a `.ps1` | Verb-Noun with an approved verb (`Get-Verb`) | PSScriptAnalyzer `PSUseApprovedVerbs`. This is a command rule, not a file rule | -| Env and secret files | lowercase snake_case stem | `mail_relay.env`, `db_root_password` | +| Env and secret files, tracked or not | lowercase fields joined by `_`, with no dot but the extension | `mail_relay.env`, `db_root_password`, `app_web-01_prod.env` | | Env variable names and CI secret names | UPPER_SNAKE_CASE | The POSIX and GitHub convention | - **Installed commands.** An executable or launcher on `PATH` or in a system slot (systemd, cron, run-parts, motd) is named as a command, whatever its language. That name is lowercase-hyphenated. It carries no extension unless the platform needs one to run it, such as `.cmd` or `.ps1` on Windows. - **Wrapper exemption.** A thin launcher whose only job is to run a Python tool keeps that tool's stem, so `fetch_report.py`, `fetch_report.sh`, and `fetch_report.ps1` stay grouped together. It covers only a launcher run by its path. A launcher installed onto `PATH` or into a system slot is an installed command instead, such as `fetch-report` rather than `fetch_report`. -- **Externally fixed names are exempt.** That covers Git hook names, `__init__.py`, `conftest.py`, and Pester's `.Tests.ps1` suffix, whose stem mirrors the script under test. It covers a PowerShell data file (`.psd1`) whose name a tool fixes, such as a module manifest named for its module. `PSScriptAnalyzerSettings.psd1` is another, since PSScriptAnalyzer finds it by that name. It also covers any name a platform defines. +- **Env and secret file names reach every file a repository names.** That covers a tracked file or template and a secret file mounted into a container. It also covers a host-side file the repository's docs or scripts name, such as an `ENV_FILE` path or a compose `env_file`. +- **`_` joins the fields of an env or secret file name, and each field keeps its own valid form.** A host name field stays verbatim, hyphens included, so `app_web-01_prod.env` is right and `app_web_01_prod.env` and `app.web-01.prod.env` are not. That field is a short name, the host's first DNS label (`web-01`) or a logical role name (`vps-staging`), and never a fully qualified one. Either way it takes the DNS label form, so its own words join with `-`. In every other field a hyphen never joins words, since that is the job of `_`. A host name label can carry `-` and never `_`, so every `_` is a separator and the name splits back into its fields. The name is lowercase, carries no dot but its extension, and starts and ends with a letter or digit. A tracked template's `.example` suffix is not counted as a dot. +- **A variable name converts separately.** An env variable or CI secret name allows only `A-Z`, `0-9`, and `_`. Where a field becomes one, the hyphen becomes `_` there, as in `APP_WEB_01_PROD_TOKEN`, and never in the file name. +- **Some consumers set limits of their own.** A name used as a Docker secret stays within the 64 characters Docker allows. A name that maps to an env variable or CI secret does not start with a digit, which POSIX and GitHub both forbid. +- **Externally fixed names are exempt.** That covers Git hook names, `__init__.py`, `conftest.py`, and Pester's `.Tests.ps1` suffix, whose stem mirrors the script under test. It covers a PowerShell data file (`.psd1`) whose name a tool fixes, such as a module manifest named for its module. `PSScriptAnalyzerSettings.psd1` is another, since PSScriptAnalyzer finds it by that name. So is an env file a tool reads by a fixed name, such as `.env`, `.env.local`, or `.env.production`. An env or secret file in a `.secrets/` directory is never one, since it is read by a path the repository configures. The exemption also covers any name a platform defines. - **Directories are out of scope.** Departures, stated on purpose so a reader does not "fix" them back: From 531a8d46312d2ecb2f091c46e89f252aa96845b1 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Tue, 27 Oct 2026 14:08:22 -0700 Subject: [PATCH 3/3] Re-vendor Three Stale Skills From Hub f60f57d Co-Authored-By: Claude Opus 5.5 --- .github/skills/backlog-burndown/SKILL.md | 32 +++++++++++++++-- .github/skills/local-strict-review/SKILL.md | 27 ++++++++++++++- .github/skills/unattended-handoff/SKILL.md | 38 ++++++++++++++++++--- 3 files changed, 88 insertions(+), 9 deletions(-) diff --git a/.github/skills/backlog-burndown/SKILL.md b/.github/skills/backlog-burndown/SKILL.md index ef2073f..1e5dede 100644 --- a/.github/skills/backlog-burndown/SKILL.md +++ b/.github/skills/backlog-burndown/SKILL.md @@ -326,12 +326,13 @@ A worker still live, a promotion fix included, keeps its worktree until the next ### Choosing the Worker's Model Tier -`AGENTS.md` "Delegation" owns the model-tier rule and names the tiers. Read it there. This is only -what it leaves to judgment here. The tier is chosen per group rather than defaulted, since a +`AGENTS.md` "Model Tiers" owns the model-tier rule and names the tiers, carried under "Model Tiers" +below. This is only what it leaves to judgment here. The tier is chosen per group rather than defaulted, since a stronger tier produces better work and takes fewer review rounds to land it. That often costs less than a cheaper worker looping. The tier is `frontier` or `standard`, never `bulk`, since every worker judges its pull request's review findings. It is `highest` only where the maintainer -named it for this run. Groups of these kinds are never tiered +named it for this run and launched the session with it allowed, per "Model Tiers" below. Where a +dispatch at it is refused, ask the maintainer rather than dispatching lower. Groups of these kinds are never tiered below `frontier`: - One touching **carried canonical content**, as `GOVERNANCE.md` "Verification Discipline" bounds @@ -536,6 +537,31 @@ Report at every round boundary and at each ending: what merged to develop, what request carries, what was newly filed, what is stopped and on which question, what was held back and on which blocker, and what the next round would pick. +## Model Tiers + +`AGENTS.md` "Model Tiers" keeps the tier rule, carried here whole as a generated include. A tier +this skill names then resolves to its model even where a carried `AGENTS.md` predates the table. + + + +A rule, a Skill, or a brief names a model tier, never a model. A model release is then an edit to the table below and to the hub's `spec/model-tiers.json`. That file states the same mapping as data, and the hub's spec gate holds the two equal. Reasoning effort is a separate axis stated beside the tier, such as `bulk` at low effort. + +- **`frontier`** is the fleet's default reasoning tier, for orchestrating, rule text, design, spec logic, and adversarial review. It is not the most capable model available, which is `highest`. +- **`standard`** is ordinary implementation and driving a pull request. Judging a review finding is never mechanical, however often it repeats, so it runs at `standard` or above. A `standard` driver's evidence-backed decline of a finding raised on its pull request stands, since that pull request's reviewers and its merge gate check it. +- **`bulk`** is the cheapest model that does the work correctly. It is for high fan-out mechanical work, such as an extraction or a status check. +- **`highest`** is the most capable model available, and only the maintainer selects it. The maintainer chooses it for the session itself, or allows it for the session's subagents at launch. The host's agent-safety kit reads that launch setting, `FLEET_ALLOW_HIGHEST=1` on Claude Code. Naming the tier in a prompt does not set it. A maintainer naming it partway through a session restarts the session with it allowed, and an agent never sets it. Without that setting, the kit refuses a dispatch that names it. A dispatch naming no model runs at its agent definition's model where one is set, and otherwise at the session's own. A fork always runs at the session's model, whatever it names. So a session launched on `highest` names a tier on every dispatch meant to run below it, and uses no fork for such work. A session never reaches for it because a task looks hard. + +| Tool | `highest` | `frontier` | `standard` | `bulk` | +| --- | --- | --- | --- | --- | +| Claude Code | `fable` | `opus` | `sonnet` | `haiku` | + +Each cell is the name that tool's subagent dispatch takes, which follows the latest model of its family. A tool with no row here, or one that cannot name a model per subagent, runs every tier at the session's own model. + +- **A tier resolves only through this table.** A word such as "frontier", "strongest", or "most capable" never resolves to `highest`. The industry's "frontier model" is the opposite of what this tier means here. A session that cannot see this table names no model rather than guessing one, so the subagent runs at the session's own model. +- **An orchestrating seat runs at `frontier` and names a tier on every dispatch.** It defaults a subagent to `frontier`, and picks `standard` or `bulk` deliberately for execution or mechanical work. A dispatch that names no model inherits whatever the session runs on, which is a choice nobody made. + + + ## Mechanics Live Elsewhere - The review loop, the Merge Gate, the finding outcomes, and when a finding is a question: diff --git a/.github/skills/local-strict-review/SKILL.md b/.github/skills/local-strict-review/SKILL.md index fac8dfd..144f2d8 100644 --- a/.github/skills/local-strict-review/SKILL.md +++ b/.github/skills/local-strict-review/SKILL.md @@ -72,7 +72,7 @@ Bounds: read-only. No edit, no stage, no commit, no push, no PR-hosted write of Before dispatching, grep the tree for other statements of each rule the diff adds or changes, and add each file holding one to the `Paths:` floor, so a statement the diff has put in disagreement is read rather than missed. -**Model tier:** `frontier`, the tier `AGENTS.md` "Delegation" names for adversarial review, which this pass is. Where `frontier` is not reachable, run the pass on the strongest tier below it that is, even the one that authored the change. A second, adversarially-prompted look still catches what the authoring pass's own "looks ready" judgment did not. +**Model tier:** `frontier`, the tier "Model Tiers" below names for adversarial review, which this pass is. Where `frontier` is not reachable, run the pass on the strongest tier below it that is, even the one that authored the change. A second, adversarially-prompted look still catches what the authoring pass's own "looks ready" judgment did not. Reachable means the tier this session can name when it dispatches the reviewer, rather than the tier this session is itself running on. A session deliberately tiered down for execution work, a worker dispatched by an orchestrator being the ordinary case, names `frontier` for the reviewer where its harness lets it, since tiering down the author is the reason the reviewer must not follow it down. What a given harness and account actually permit varies, so treat this as the tier to ask for rather than one to assume. Where a dispatch exposes no way to name a tier, take what it gives and run the pass, for the reason the fallback above states. A seat that cannot dispatch a subagent hands the pass to whoever dispatched it. The seat commits, runs `python3 /scripts/local_review.py status --target ` in its worktree, and hands back its worktree path, target, and `contentDigest`. The dispatcher fills the brief's `` slot and runs the reviewer. It then runs the record command from "Recording the Pass" in that worktree, with `--reviewer agent-skill --target --expect-digest `. It returns the findings. Each fix commit repeats this cycle, and the seat pushes only when a record matches its content. The brief dispatching the seat states whether the dispatcher can continue that seat. Where the dispatcher cannot, or under `unattended-handoff`, the seat stops, reports, and does not push. Where nobody dispatched the seat, it reports to the maintainer that it could not run the pass, and does not push. Either way the push waits rather than the pass being quietly skipped. The headless `run --backend` route under "Recording the Pass" is not the substitute: it runs a vendor CLI against its own review, which never carries the brief above, so it satisfies the rule this section states only where that separate route is what a capture point asked for. @@ -176,6 +176,31 @@ In the hub, `.husky/pre-push` checks the receipt at the push itself, so the mome This table is the fleet's one enumeration of these, and every other surface states the principle and routes here rather than listing the shapes. That is deliberate: every review round that added a shape also left a restatement of it somewhere else, and keeping one table is what stops the next round doing the same. +## Model Tiers + +`AGENTS.md` "Model Tiers" keeps the tier rule, carried here whole as a generated include. A tier +this skill names then resolves to its model even where a carried `AGENTS.md` predates the table. + + + +A rule, a Skill, or a brief names a model tier, never a model. A model release is then an edit to the table below and to the hub's `spec/model-tiers.json`. That file states the same mapping as data, and the hub's spec gate holds the two equal. Reasoning effort is a separate axis stated beside the tier, such as `bulk` at low effort. + +- **`frontier`** is the fleet's default reasoning tier, for orchestrating, rule text, design, spec logic, and adversarial review. It is not the most capable model available, which is `highest`. +- **`standard`** is ordinary implementation and driving a pull request. Judging a review finding is never mechanical, however often it repeats, so it runs at `standard` or above. A `standard` driver's evidence-backed decline of a finding raised on its pull request stands, since that pull request's reviewers and its merge gate check it. +- **`bulk`** is the cheapest model that does the work correctly. It is for high fan-out mechanical work, such as an extraction or a status check. +- **`highest`** is the most capable model available, and only the maintainer selects it. The maintainer chooses it for the session itself, or allows it for the session's subagents at launch. The host's agent-safety kit reads that launch setting, `FLEET_ALLOW_HIGHEST=1` on Claude Code. Naming the tier in a prompt does not set it. A maintainer naming it partway through a session restarts the session with it allowed, and an agent never sets it. Without that setting, the kit refuses a dispatch that names it. A dispatch naming no model runs at its agent definition's model where one is set, and otherwise at the session's own. A fork always runs at the session's model, whatever it names. So a session launched on `highest` names a tier on every dispatch meant to run below it, and uses no fork for such work. A session never reaches for it because a task looks hard. + +| Tool | `highest` | `frontier` | `standard` | `bulk` | +| --- | --- | --- | --- | --- | +| Claude Code | `fable` | `opus` | `sonnet` | `haiku` | + +Each cell is the name that tool's subagent dispatch takes, which follows the latest model of its family. A tool with no row here, or one that cannot name a model per subagent, runs every tier at the session's own model. + +- **A tier resolves only through this table.** A word such as "frontier", "strongest", or "most capable" never resolves to `highest`. The industry's "frontier model" is the opposite of what this tier means here. A session that cannot see this table names no model rather than guessing one, so the subagent runs at the session's own model. +- **An orchestrating seat runs at `frontier` and names a tier on every dispatch.** It defaults a subagent to `frontier`, and picks `standard` or `bulk` deliberately for execution or mechanical work. A dispatch that names no model inherits whatever the session runs on, which is a choice nobody made. + + + ## Mechanics Live Elsewhere - Review criteria: `fleet-code-review`. diff --git a/.github/skills/unattended-handoff/SKILL.md b/.github/skills/unattended-handoff/SKILL.md index 42aea3b..218c4d7 100644 --- a/.github/skills/unattended-handoff/SKILL.md +++ b/.github/skills/unattended-handoff/SKILL.md @@ -90,22 +90,25 @@ maintainer starts a second run on a repository only once the first has ended. The orchestrator first resolves `/` from the checkout's `origin`, or takes it from the invocation. Apart from the timer "A Dispatch That Fails" names, that is the one command it runs. -From then on it holds only the scope and any `highest` the invocation named, the round count, and +From then on it holds only the scope and any allowed `highest` the invocation named, the round count, and the dispatched handoff numbers. It also holds each round's one-line outcome and the waits spent on a dispatch being retried. Each round runs: -1. **Dispatch the picker** at `frontier` with the brief below, or at `highest` where the - maintainer's invocation named that tier or its model. Its ranking, decision, and worker-tier +1. **Dispatch the picker** at `frontier` with the brief below. Use `highest` only where the + invocation named that tier or its model and the session was launched with it allowed, per + "Model Tiers" below. A refusal there ends the run the way step 3's does. Its ranking, decision, and worker-tier calls are the ones `backlog-burndown` keeps in its orchestrator, and here no maintainer reviews them. Wait on the dispatch mechanism's own completion signal rather than polling. 2. **Read its one line.** `NONE` or `STOP` ends the run, and so does any reply that is not a picker line below. A handoff number already dispatched in this run ends it too, since a handoff a worker neither closed nor parked means the worker failed in a way this seat must not investigate. -3. **Dispatch the worker** on that handoff and track, at the model `AGENTS.md` "Delegation" names +3. **Dispatch the worker** on that handoff and track, at the model "Model Tiers" below names for the tier in the picker's line. Wait the same way. Dispatch at `highest` instead only where - the maintainer's invocation named that tier or its model. + the invocation named that tier or its model and the session was launched with it allowed, per + "Model Tiers" below. Without that allowance every such dispatch is refused. The run then ends + and reports the refusal rather than continuing at a tier nobody chose. 4. **Record its one line** and start the next round. `STOP` ends the run, and so does a reply that is not one of the lines below, rather than being read further. @@ -180,6 +183,31 @@ Under `main` or `release` every unblocked round promotes, so each promotion ordi fix. A change another session merged to develop meanwhile rides along with it. Naming the scope accepts that. +## Model Tiers + +`AGENTS.md` "Model Tiers" keeps the tier rule, carried here whole as a generated include. A tier +this skill names then resolves to its model even where a carried `AGENTS.md` predates the table. + + + +A rule, a Skill, or a brief names a model tier, never a model. A model release is then an edit to the table below and to the hub's `spec/model-tiers.json`. That file states the same mapping as data, and the hub's spec gate holds the two equal. Reasoning effort is a separate axis stated beside the tier, such as `bulk` at low effort. + +- **`frontier`** is the fleet's default reasoning tier, for orchestrating, rule text, design, spec logic, and adversarial review. It is not the most capable model available, which is `highest`. +- **`standard`** is ordinary implementation and driving a pull request. Judging a review finding is never mechanical, however often it repeats, so it runs at `standard` or above. A `standard` driver's evidence-backed decline of a finding raised on its pull request stands, since that pull request's reviewers and its merge gate check it. +- **`bulk`** is the cheapest model that does the work correctly. It is for high fan-out mechanical work, such as an extraction or a status check. +- **`highest`** is the most capable model available, and only the maintainer selects it. The maintainer chooses it for the session itself, or allows it for the session's subagents at launch. The host's agent-safety kit reads that launch setting, `FLEET_ALLOW_HIGHEST=1` on Claude Code. Naming the tier in a prompt does not set it. A maintainer naming it partway through a session restarts the session with it allowed, and an agent never sets it. Without that setting, the kit refuses a dispatch that names it. A dispatch naming no model runs at its agent definition's model where one is set, and otherwise at the session's own. A fork always runs at the session's model, whatever it names. So a session launched on `highest` names a tier on every dispatch meant to run below it, and uses no fork for such work. A session never reaches for it because a task looks hard. + +| Tool | `highest` | `frontier` | `standard` | `bulk` | +| --- | --- | --- | --- | --- | +| Claude Code | `fable` | `opus` | `sonnet` | `haiku` | + +Each cell is the name that tool's subagent dispatch takes, which follows the latest model of its family. A tool with no row here, or one that cannot name a model per subagent, runs every tier at the session's own model. + +- **A tier resolves only through this table.** A word such as "frontier", "strongest", or "most capable" never resolves to `highest`. The industry's "frontier model" is the opposite of what this tier means here. A session that cannot see this table names no model rather than guessing one, so the subagent runs at the session's own model. +- **An orchestrating seat runs at `frontier` and names a tier on every dispatch.** It defaults a subagent to `frontier`, and picks `standard` or `bulk` deliberately for execution or mechanical work. A dispatch that names no model inherits whatever the session runs on, which is a choice nobody made. + + + ## Auto-Resolvable An issue needs no maintainer decision when every one of these holds. Where any is unclear, it