diff --git a/audit/agents/architect.md b/audit/agents/architect.md index f92d6c6..5272689 100644 --- a/audit/agents/architect.md +++ b/audit/agents/architect.md @@ -2,6 +2,7 @@ name: architect description: Read-only architecture reviewer. Checks at cycle close whether the codebase still matches the project's design ledger and CLAUDE.md, identifies drift and technical debt with paths and short justifications, and recommends one direction for the next iteration. Names problems; does NOT propose implementations. tools: Read, Glob, Grep, Bash +model: opus --- # architect diff --git a/audit/agents/bencher.md b/audit/agents/bencher.md index 7433120..219941c 100644 --- a/audit/agents/bencher.md +++ b/audit/agents/bencher.md @@ -2,6 +2,7 @@ name: bencher description: Hypothesis-driven performance benchmarker. Designs workloads, runs measurements, interprets results to answer "is X better than Y?" — not "is X fast in absolute terms?". Reports evidence including the limitations of the bench design. Does NOT ship features. tools: Read, Write, Edit, Glob, Grep, Bash +model: opus --- # bencher diff --git a/brainstorm/agents/synthetic-user.md b/brainstorm/agents/synthetic-user.md index c822f67..234c37a 100644 --- a/brainstorm/agents/synthetic-user.md +++ b/brainstorm/agents/synthetic-user.md @@ -2,6 +2,7 @@ name: synthetic-user description: Read-only synthetic design-stance respondent for the brainstorm swarm. Answers the orchestrator's enumerated design forks from ONE fixed adversarial stance, grounded in the project's audience / design ledger / glossary, with a mandatory source citation per answer. Does NOT decide and does NOT author approaches; the convergence vs divergence of many stances is the orchestrator's fork-triage evidence. tools: Read, Glob, Grep, Bash +model: sonnet --- > Violating the letter of these rules is violating the spirit. diff --git a/debug/agents/debugger.md b/debug/agents/debugger.md index fc58c4f..1f08808 100644 --- a/debug/agents/debugger.md +++ b/debug/agents/debugger.md @@ -2,6 +2,7 @@ name: debugger description: Diagnoses bugs in the project. Reproduces, finds the root cause, writes a RED test that pins down the symptom, and hands off to implement mini-mode for the GREEN side. Does NOT apply the fix itself. tools: Read, Edit, Write, Bash, Glob, Grep +model: opus --- # debugger diff --git a/docs/agent-template.md b/docs/agent-template.md index 7872bbf..958cbcf 100644 --- a/docs/agent-template.md +++ b/docs/agent-template.md @@ -12,6 +12,7 @@ specific identifiers. name: description: tools: +model: --- > Violating the letter of these rules is violating the spirit. @@ -105,6 +106,37 @@ dispatch still carry no `Agent` tool — the no-nested-dispatch rule is unchanged; the workflow simply does the orchestration the retired `implement-orchestrator` agent used to fake with inline role-switches. +### `model` + +Mandatory. Every agent pins an explicit model — `opus` or `sonnet`, +nothing else. An omitted field means the agent inherits the *session* +model, which routes the dispatch to whatever the user happens to be +chatting on (possibly `fable` — banned for all plugin agents and +workflows, by owner decree). Pinning removes that coupling. + +Assignment rule, in priority order: + +1. **Consequence of a miss** — agents whose errors silently poison + downstream work (adversarial gates, root-cause diagnosis, + architecture judgment) run `opus`. Opus is the documented + strength for code review, debugging, and deep reasoning. +2. **Mechanical scope** — tightly-scoped execution of a pre-made + plan, recon, extraction, compliance-diffing run `sonnet` + (near-opus coding quality at lower latency and cost). +3. **Volume × wall-clock** — agents dispatched per-task inside + loops or fanned out in parallel swarms multiply their model's + latency; they run `sonnet` unless rule 1 overrides (currently + only `quality-reviewer`, the loop's last correctness gate). + +Current distribution: `opus` — architect, bencher, debugger, +fieldtester, grounding-check, plan-recon, quality-reviewer, +spec-skeptic, tdd-author. `sonnet` — docwriter, glossary-extractor, +implementer, spec-reviewer, synthetic-user, tester. + +The same rule binds Workflow scripts: every `agent()` call passes an +explicit `model:` option (see the model-policy header comments in +`implement/workflows/*.js`). + ## Sections in detail ### Spirit-letter lead-in diff --git a/docwriter/agents/docwriter.md b/docwriter/agents/docwriter.md index f5f749a..f9aa797 100644 --- a/docwriter/agents/docwriter.md +++ b/docwriter/agents/docwriter.md @@ -2,6 +2,7 @@ name: docwriter description: Writes and maintains API documentation for the project's source. Brings top-level, module-level, and public-item docs up to a level where a newcomer can navigate the generated docs without having read the design ledger first. NOT for changing APIs, NOT for editing files under the project's design or specs directories. tools: Read, Edit, Write, Bash, Glob, Grep +model: sonnet --- # docwriter diff --git a/fieldtest/agents/fieldtester.md b/fieldtest/agents/fieldtester.md index da39793..f74d11e 100644 --- a/fieldtest/agents/fieldtester.md +++ b/fieldtest/agents/fieldtester.md @@ -2,6 +2,7 @@ name: fieldtester description: Implements 2-4 real-world tasks against the project as a downstream consumer would, runs them, and reports friction, bugs, and spec gaps as a structured spec. Simulates a downstream user who has only the public interface (the project's design ledger, READMEs, public docs, and example corpus) — never the implementation source. Does NOT fix bugs. tools: Read, Edit, Write, Bash, Glob, Grep +model: opus --- # fieldtester diff --git a/glossary/agents/glossary-extractor.md b/glossary/agents/glossary-extractor.md index efabc17..f2cdf73 100644 --- a/glossary/agents/glossary-extractor.md +++ b/glossary/agents/glossary-extractor.md @@ -2,6 +2,7 @@ name: glossary-extractor description: Read-only per-slice prose extractor for glossary bootstrap. Sweeps one slice of a project's prose surface and reports the recurring domain-concept terms and their competing synonyms as actually written, with frequencies and locations. Coins nothing; dispatched fan-out by the glossary skill's bootstrap procedure. tools: Read, Glob, Grep, Bash +model: sonnet --- > Violating the letter of these rules is violating the spirit. diff --git a/implement/agents/implementer.md b/implement/agents/implementer.md index 724e679..7debc47 100644 --- a/implement/agents/implementer.md +++ b/implement/agents/implementer.md @@ -2,6 +2,7 @@ name: implementer description: Carries out a tightly scoped implementation task. Reads the task extract handed by the controller, implements, builds, tests, reports a structured status with the diff. NOT for architecture decisions, NOT for self-curated scope; this agent executes a plan that has already been made. tools: Read, Edit, Write, Bash, Glob, Grep +model: sonnet --- # implementer diff --git a/implement/agents/quality-reviewer.md b/implement/agents/quality-reviewer.md index c097f96..d596590 100644 --- a/implement/agents/quality-reviewer.md +++ b/implement/agents/quality-reviewer.md @@ -2,6 +2,7 @@ name: quality-reviewer description: Read-only code-quality reviewer for project diffs. Reports Strengths, Issues by severity (Important / Minor / Nit), and a Recommendation. Runs after spec-reviewer is green; spec-compliance is NOT this agent's concern. Does NOT propose fixes. tools: Read, Glob, Grep, Bash +model: opus --- # quality-reviewer diff --git a/implement/agents/spec-reviewer.md b/implement/agents/spec-reviewer.md index 1ffb8ee..b2df254 100644 --- a/implement/agents/spec-reviewer.md +++ b/implement/agents/spec-reviewer.md @@ -2,6 +2,7 @@ name: spec-reviewer description: Read-only spec-compliance reviewer. Compares a recent diff against the task text from a plan under docs/plans handed by the controller. Reports missing requirements and unrequested extras. Does NOT review code quality (that is quality-reviewer's job) and does NOT propose fixes (the implementer fixes; the orchestrator coordinates). tools: Read, Glob, Grep, Bash +model: sonnet --- # spec-reviewer diff --git a/implement/agents/tester.md b/implement/agents/tester.md index ee0a15c..4236c37 100644 --- a/implement/agents/tester.md +++ b/implement/agents/tester.md @@ -2,6 +2,7 @@ name: tester description: Writes new fixtures and E2E tests after a cycle or feature ships. Verifies a feature works from build through to observable output. Each test protects a named property; tests check observable behaviour, not implementation internals. tools: Read, Edit, Write, Bash, Glob, Grep +model: sonnet --- # tester diff --git a/implement/workflows/compiler-driven-edit.js b/implement/workflows/compiler-driven-edit.js index 627dfa5..5e6fc18 100644 --- a/implement/workflows/compiler-driven-edit.js +++ b/implement/workflows/compiler-driven-edit.js @@ -39,6 +39,11 @@ // stay unstaged for the orchestrator. The script has no shell of its own — // the edit and the verify both run inside agent() calls. // +// Model policy: every agent() call pins an explicit model — never the +// session-model inherit (which could route to an unintended tier, e.g. fable — +// banned for all plugin agents and workflows). Both phases here are mechanical +// (compiler-enumerated propagation, build/suite re-run), so both run sonnet. +// // Carrier — Workflow `args`: // edit_description: what type/signature change to make // def_site: the definition site (file + symbol) it originates from @@ -120,7 +125,7 @@ const edit = await agent( 'Then run the project build and report: is the build clean, does any hole need a decision (with detail + bounce_to), ' + 'how many sites were touched, and — checked against `git status --porcelain` / `git diff HEAD`, NOT from memory — ' + 'whether the working tree actually changed (applied_changes).', - { agentType: 'implementer', label: 'edit+propagate', phase: 'Edit + propagate', schema: EDIT_SCHEMA }, + { agentType: 'implementer', model: 'sonnet', label: 'edit+propagate', phase: 'Edit + propagate', schema: EDIT_SCHEMA }, ) if (!edit) return { status: 'BLOCKED', reason: 'edit agent did not return' } if (edit.hole_needs_decision) { @@ -162,7 +167,7 @@ const verify = await agent( 'every test passes and NO test was added, removed, or weakened versus the pre-edit baseline (compare `git diff ' + 'HEAD` for test-file changes). Also report working_tree_dirty — whether `git status --porcelain` / `git diff HEAD` ' + 'shows ANY change at all (a clean tree means no edit landed). Do NOT edit anything; do NOT commit.', - { label: 'verify(build+suite)', phase: 'Verify', schema: VERIFY_SCHEMA }, + { model: 'sonnet', label: 'verify(build+suite)', phase: 'Verify', schema: VERIFY_SCHEMA }, ) // ---- Phase 3 — verdict -------------------------------------------------- diff --git a/implement/workflows/implement-loop.js b/implement/workflows/implement-loop.js index d8a2cb2..87ce0de 100644 --- a/implement/workflows/implement-loop.js +++ b/implement/workflows/implement-loop.js @@ -42,6 +42,11 @@ // • The script has no filesystem or shell access of its own — every // working-tree mutation (code, stats.json, BLOCKED.md) happens inside // an agent() call. +// • Model policy: every agent() call pins an explicit model — never the +// session-model inherit (which could route to an unintended tier, e.g. +// fable — banned for all plugin agents and workflows). Mechanical and +// in-loop steps run sonnet; the quality gate is the one deliberate opus +// call (last correctness check before a task is marked DONE). // // Carrier — passed as the Workflow `args` object by the orchestrator: // mode: 'standard' | 'mini' @@ -245,7 +250,7 @@ const pre = await agent( 'Run `git status --porcelain`, `git rev-parse HEAD`, and `git rev-parse --abbrev-ref HEAD`. ' + 'Report whether the tree is clean (no porcelain output), the HEAD sha, the branch, and any dirty paths. ' + 'Do NOT modify anything, do NOT commit. The HEAD sha is an informational anchor only — never a reset target.', - { label: 'preflight', phase: 'Preflight', schema: PREFLIGHT_SCHEMA }, + { model: 'sonnet', label: 'preflight', phase: 'Preflight', schema: PREFLIGHT_SCHEMA }, ) if (!pre) return { status: 'BLOCKED', iter_id, reason: 'infra: preflight agent did not return' } if (!pre.clean) { @@ -276,7 +281,7 @@ if (mode === 'mini') { `${STANDING}\n\nRead the plan at ${plan_path}. Extract ` + (task_range ? `tasks ${task_range[0]}..${task_range[1]} (inclusive, 1-based)` : 'every task') + ', each as its VERBATIM block with the task id and a one-line title. Do not summarise or reorder. Do not edit anything.', - { label: 'plan-extract', phase: 'Per-task loop', schema: TASKS_SCHEMA }, + { model: 'sonnet', label: 'plan-extract', phase: 'Per-task loop', schema: TASKS_SCHEMA }, ) if (!extracted || !extracted.tasks || extracted.tasks.length === 0) { return { status: 'NEEDS_CONTEXT', iter_id, reason: `no tasks extracted from ${plan_path}` } @@ -294,7 +299,7 @@ async function runTask(task) { 'inner-loop discipline — add the failing test inline even if the task text forgot to script it). ' + 'Build and test must be green before you report DONE. Leave all edits UNSTAGED; never commit.\n\n' + `mode: ${mode}\niter_id: ${iter_id}\n\nTASK ${task.id} — ${task.title}\n${task.text}`, - { agentType: 'implementer', label: `impl:${task.id}`, phase: 'Per-task loop', schema: IMPL_SCHEMA }, + { agentType: 'implementer', model: 'sonnet', label: `impl:${task.id}`, phase: 'Per-task loop', schema: IMPL_SCHEMA }, ) if (!impl) return { id: task.id, title: task.title, outcome: 'BLOCKED', reason: 'implementer agent did not return' } if (impl.status === 'BLOCKED') return { id: task.id, title: task.title, outcome: 'BLOCKED', reason: impl.reason || 'worker-blocked' } @@ -313,7 +318,7 @@ async function runTask(task) { `${STANDING}\n\nSpec-compliance review of task ${task.id}. Compare \`git diff HEAD\` against the task ` + 'text ONLY — list missing requirements and unrequested extras. No quality opinions, no fix proposals. ' + `Focus on the files this task claims to touch.\n\nTASK ${task.id} — ${task.title}\n${task.text}`, - { agentType: 'spec-reviewer', label: `spec:${task.id}`, phase: 'Per-task loop', schema: SPEC_SCHEMA }, + { agentType: 'spec-reviewer', model: 'sonnet', label: `spec:${task.id}`, phase: 'Per-task loop', schema: SPEC_SCHEMA }, ) if (spec && spec.status === 'compliant') break if (spec && spec.status === 'unclear') return { id: task.id, title: task.title, outcome: 'BLOCKED', reason: 'spec-ambiguous', detail: spec.ambiguity } @@ -323,7 +328,7 @@ async function runTask(task) { `${STANDING}\n\nRepair task ${task.id} to satisfy the spec-compliance review. Address EACH finding; ` + 'do not widen scope. Keep build + tests green; leave edits unstaged.\n\n' + `TASK ${task.id} — ${task.title}\n${task.text}\n\nFINDINGS:\n- ${(spec?.findings || ['(none reported)']).join('\n- ')}`, - { agentType: 'implementer', label: `impl-fix-spec:${task.id}`, phase: 'Per-task loop', schema: IMPL_SCHEMA }, + { agentType: 'implementer', model: 'sonnet', label: `impl-fix-spec:${task.id}`, phase: 'Per-task loop', schema: IMPL_SCHEMA }, ) if (specFix && specFix.applied_changes === true) wroteAnyFile = true } @@ -346,7 +351,9 @@ async function runTask(task) { 'Nits never gate. Also report `diff_fingerprint` (REQUIRED): the sha256 hex of the FULL `git diff HEAD` ' + '(`git diff HEAD | sha256sum` — the whole working-tree diff, NOT narrowed to this task\'s footprint). ' + 'The loop compares it across rounds to detect a no-op/cyclic repair.', - { agentType: 'quality-reviewer', label: `qual:${task.id}`, phase: 'Per-task loop', schema: QUAL_SCHEMA }, + // opus by design: the loop's last correctness gate (spec-reviewer only checks + // task-text correspondence) — real-bug finding is the documented opus strength. + { agentType: 'quality-reviewer', model: 'opus', label: `qual:${task.id}`, phase: 'Per-task loop', schema: QUAL_SCHEMA }, ) if (qual && qual.status === 'approved') break // No-op / cyclic-repair backstop: a changes_requested verdict over a diff-state already reviewed-and-flagged @@ -380,7 +387,7 @@ async function runTask(task) { 'A finding whose only remedy is to diverge from the ratified plan must be held or escalated, never chased ' + 'into a deviation the next review flags.\n\n' + `TASK ${task.id} — ${task.title}\n${task.text}\n\nISSUES:\n- ` + (qual?.issues || ['(none reported)']).join('\n- '), - { agentType: 'implementer', label: `impl-fix-qual:${task.id}`, phase: 'Per-task loop', schema: IMPL_SCHEMA }, + { agentType: 'implementer', model: 'sonnet', label: `impl-fix-qual:${task.id}`, phase: 'Per-task loop', schema: IMPL_SCHEMA }, ) if (fix && fix.applied_changes === true) wroteAnyFile = true if (fix && (fix.status === 'BLOCKED' || fix.status === 'NEEDS_CONTEXT')) { @@ -464,7 +471,7 @@ if (mode === 'mini' && outcome === 'DONE') { 'fix). Then run the full suite and confirm it is green (no regression). Also report working_tree_dirty — ' + 'whether `git status --porcelain` (NOT `git diff HEAD`, which misses new untracked files) shows any change at ' + 'all (a clean tree means no fix landed). Do NOT edit anything; do NOT commit.', - { label: 'mini-verify', phase: 'Verify', schema: MINI_VERIFY_SCHEMA }, + { model: 'sonnet', label: 'mini-verify', phase: 'Verify', schema: MINI_VERIFY_SCHEMA }, ) if (!miniVerify || !miniVerify.red_test_now_green || !miniVerify.suite_green || !miniVerify.working_tree_dirty) { outcome = 'BLOCKED' @@ -491,7 +498,7 @@ if (mode === 'mini' && outcome === 'DONE') { '`git status --porcelain` and report files_touched = its line count (every changed, added, or untracked ' + 'path). Use `git status --porcelain`, NOT `git diff`, so brand-new untracked files are counted. Do NOT edit, ' + 'do NOT commit.', - { label: 'tree-check', phase: 'Verify', schema: TREE_SCHEMA }, + { model: 'sonnet', label: 'tree-check', phase: 'Verify', schema: TREE_SCHEMA }, ) if (!treeState || treeState.files_touched === 0) { outcome = 'BLOCKED' @@ -519,7 +526,7 @@ if (mode === 'standard' && outcome === 'DONE') { 'E2E fixtures for them in the project\'s canonical fixture form; each test\'s doc comment names the property ' + 'it protects. Run the project\'s test command — all green. Leave edits unstaged; never commit.\n\n' + `iter_id: ${iter_id}\nshipped: ${completed.map((c) => `#${c.id} ${c.title}`).join('; ')}`, - { agentType: 'tester', label: 'e2e', phase: 'E2E coverage', schema: E2E_SCHEMA }, + { agentType: 'tester', model: 'sonnet', label: 'e2e', phase: 'E2E coverage', schema: E2E_SCHEMA }, ) } @@ -575,7 +582,7 @@ const fin = await agent( 'and `## Files touched` (from `git status --porcelain`).\n') + 'Report the stats path and whether you wrote BLOCKED.md. Do NOT commit anything.\n\nAGGREGATE:\n' + JSON.stringify(aggregate, null, 2), - { label: 'finalize', phase: 'Report', schema: FINALIZE_SCHEMA }, + { model: 'sonnet', label: 'finalize', phase: 'Report', schema: FINALIZE_SCHEMA }, ) // The no-op verdict was already taken before finalize (the tree-check / mini-verify diff --git a/planner/agents/plan-recon.md b/planner/agents/plan-recon.md index 066fe4d..94452d4 100644 --- a/planner/agents/plan-recon.md +++ b/planner/agents/plan-recon.md @@ -2,6 +2,7 @@ name: plan-recon description: Read-only code-and-doc-recon agent for plan writing. Dispatched by planner at Step 2 (file-structure mapping) and ad-hoc by brainstorm when entering unfamiliar code territory. Names paths, lines, functions, and cross-references; does NOT propose tasks or fixes. tools: Read, Glob, Grep, Bash +model: opus --- # plan-recon diff --git a/specify/agents/grounding-check.md b/specify/agents/grounding-check.md index a21447e..613a634 100644 --- a/specify/agents/grounding-check.md +++ b/specify/agents/grounding-check.md @@ -2,6 +2,7 @@ name: grounding-check description: Read-only grounding-check reviewer for spec drafts. Dispatched by the specify skill in Step 5, between linguistic self-review and user-approval. Reads the draft with fresh context, extracts its load-bearing assumptions about current codebase behaviour, and for each one searches the workspace for a currently-green test that ratifies it. Reports PASS or BLOCK. Does NOT propose fixes, does NOT edit files. tools: Read, Glob, Grep, Bash +model: opus --- # grounding-check — spec grounding-check agent diff --git a/specify/agents/spec-skeptic.md b/specify/agents/spec-skeptic.md index ce31141..f1b81ca 100644 --- a/specify/agents/spec-skeptic.md +++ b/specify/agents/spec-skeptic.md @@ -2,6 +2,7 @@ name: spec-skeptic description: Read-only adversarial bias-breaker the orchestrator pulls ad hoc when deciding a load-bearing fork under `/boss` — one lens (or a few) of criterion, grounding, scope-fork, ambiguity, plan-readiness. Tries to REFUTE the spec/decision along its single lens and reports SOUND or BLOCK. The finding feeds the orchestrator's decision; it is NOT a gate and NOT a veto. Does NOT propose fixes, does NOT edit files. tools: Read, Glob, Grep, Bash +model: opus --- # spec-skeptic — ad-hoc adversarial bias-breaker diff --git a/tdd/agents/tdd-author.md b/tdd/agents/tdd-author.md index 7e219b4..b01c434 100644 --- a/tdd/agents/tdd-author.md +++ b/tdd/agents/tdd-author.md @@ -2,6 +2,7 @@ name: tdd-author description: Turns a test-specifiable feature description or issue into a single minimal, autonomous RED executable-spec test ("how it should work"), then hands the GREEN side to implement mini-mode. On a design fork (behaviour not test-specifiable) it bounces to brainstorm; on a GREEN one iteration can't reach, it decomposes the headline into BLOCKER sub-tests. Does NOT implement the feature. tools: Read, Edit, Write, Bash, Glob, Grep +model: opus --- # tdd-author