Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/agents/trinity.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
model: opus
model: sonnet
description: Trinity Agent (Queen) - Orchestrates multi-agent coordination, executes AEL v2.0
color: "#ef4444"
orchestrator: true
Expand Down
22 changes: 11 additions & 11 deletions .claude/skills/model-routing/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
id: model-routing
name: model-routing
description: Which Claude model does which job -- the orchestrator runs on Opus 5.5, every sub-task it delegates runs on Sonnet 5.5, and a sub-task escalates to Opus only after two failed attempts or on the owner's word. Load before spawning any subagent, writing a workflow, or picking a model in TRI CLAW or the Queen.
description: Which Claude model does which job -- every agent runs on Sonnet 5.5 by default, the orchestrator included, and any job escalates to Opus 5.5 only after two failed attempts or on the owner's word. Load before spawning any subagent, writing a workflow, or picking a model in TRI CLAW or the Queen.
argument-hint: "[role: orchestrate|plan|review-final|implement|port|search|test|docs|fix]"
---

# /model-routing -- Opus orchestrates, Sonnet does the sub-tasks
# /model-routing -- Sonnet by default for every agent, Opus only on evidence

<!-- only-t27-rule -->
> **Only t27: everything is written in `.t27`.** (Owner hard rule 2026-10-05; budget #7371; restated in every skill per #7588.)
Expand All @@ -16,40 +16,40 @@ argument-hint: "[role: orchestrate|plan|review-final|implement|port|search|test|
> - What gen cannot express yet is a compiler defect. File it on #5980 with a minimal `.t27` repro instead of writing the code by hand.
> - This rule overrides any step below that says to write a script or code in another language. The full text is in `AGENTS.md`, "Only t27".

Owner directive 2026-10-18: orchestration through Opus 5.5, sub-tasks through Sonnet 5.5, because it is cheaper; the rule is part of TRI CLAW so development stays economical.
Owner directive 2026-10-19 (translated): "set Sonnet as the default model for all agents -- it is cheaper with the same result" (#10921). It replaces the 2026-10-18 rule (orchestration on Opus 5.5, sub-tasks on Sonnet 5.5, #10493). The rule is part of TRI CLAW so development stays economical.

**The law is `specs/claw/model_route.t27`.** This skill is how to follow it in a Claude Code session. If the two disagree, the spec wins; fix the skill.

## The rule

| Role | Kind | Model |
|---|---|---|
| orchestrate, plan, review-final | orchestration | **Opus 5.5** (`opus`, `claude-opus-5-5`) |
| orchestrate, plan, review-final | orchestration | **Sonnet 5.5** (`sonnet`, `claude-sonnet-5-5`) |
| implement, port, search, test, docs, fix | sub-task | **Sonnet 5.5** (`sonnet`, `claude-sonnet-5-5`) |

- A sub-task goes to Opus only on evidence: **2 failed Sonnet attempts on the same task** (`ESCALATE_AFTER`), or the owner says so. Nothing else raises a tier.
- Orchestration is never routed down. Splitting the work, deciding what is true, and saying merge or stop stay on Opus.
- Any job goes to Opus 5.5 (`opus`, `claude-opus-5-5`) only on evidence: **2 failed Sonnet attempts on the same task** (`ESCALATE_AFTER`), or the owner says so. Nothing else raises a tier.
- Opus without that evidence is waste for every role (`is_waste`), the orchestrator included.
- Why: epic #10066 measured Sonnet 5.5 at least as good as Opus 5.5 on ten bounded spec-to-RTL tasks (9 vs 8 direct Verilog, 7 vs 7 in .t27) at about 40% of the cost.

## How it is wired in Claude Code

- `.claude/settings.json` sets `CLAUDE_CODE_SUBAGENT_MODEL=claude-sonnet-5-5`, so any subagent with no model of its own runs on Sonnet.
- Every sub-task agent in `.claude/agents/` carries `model: sonnet`; the orchestrator `trinity.md` (Queen) carries `model: opus`.
- The main session is the orchestrator: pick **Opus 5.5** for it in the app's model menu.
- Every agent in `.claude/agents/` carries `model: sonnet`, the orchestrator `trinity.md` (Queen) included.
- The main session is the orchestrator: pick **Sonnet 5.5** for it in the app's model menu; switch it to Opus only for a job that failed twice on Sonnet or on the owner's word.

## When you are the orchestrator

1. Plan on Opus: split the task into sub-tasks with a clear input, output and done-check each.
1. Plan (on Sonnet): split the task into sub-tasks with a clear input, output and done-check each.
2. Spawn each sub-task with the Agent tool. Leave `model` unset (the default is Sonnet) or pass `model: "sonnet"`. Give the subagent everything it needs in the prompt: file paths, the issue, the acceptance check. A sub-task prompt that needs Opus to understand is a planning failure, not a reason to escalate.
3. Verify the result yourself (that is review-final, on Opus): read the diff or the output, run the check.
3. Verify the result yourself (that is review-final): read the diff or the output, run the check.
4. If the same sub-task fails twice on Sonnet, re-spawn it once with `model: "opus"`, and say in the result why it escalated.
5. In a Workflow script, set `model: 'sonnet'` on sub-task `agent()` calls and keep the orchestration logic in the script itself.

Parallelism: at most 3 subagents at once on this 8 GB Mac (swap eats the disk); more on the Railway lab.

## Audit

`is_waste(role, tier, failed_attempts, owner_flag)` in the spec answers whether a run spent Opus on a sub-task without evidence. Use it when TRI CLAW or the Queen records which model ran a job.
`is_waste(role, tier, failed_attempts, owner_flag)` in the spec answers whether a run spent Opus on any role without evidence. Use it when TRI CLAW or the Queen records which model ran a job.

## Not covered yet

Expand Down
16 changes: 8 additions & 8 deletions .trinity/seals/claw_ClawModelRoute.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"built_by": "t27c-bootstrap@0.5.2+9b556b7c3",
"built_by": "t27c-bootstrap@0.5.2+ea35d9b72",
"closure": [],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:bc9e7460cb2d53c2b552cc6ea1db3a87a41fea981237fca28e2e92a353d94092",
"gen_hash_rust": "sha256:d31ea1c2bfe1e7403ed63aeef7e8774461e9b122eb328ac75949f2f191ae578f",
"gen_hash_verilog": "sha256:0d8da221bea4f2da090aa8f840641d6d84e83dba26a4ed0df112cb263dd1c18c",
"gen_hash_zig": "sha256:f4fde0bf222b2cc78dd99301ea3c9f798548930830e3b85575fc504a6ce47c05",
"gen_hash_c": "sha256:dd581b1b0902f88cc5985caf5b9a6fffcb96d5e5bb136d361c39d3c907d80f94",
"gen_hash_rust": "sha256:0c02f37c6a0859250c13dcfd61998c5929c1d71c72f8fd46a726a557f99b1e67",
"gen_hash_verilog": "sha256:76412baed107fc229715114f3be64f0a6e1d7496eb563d569b32ded5ab9bf57d",
"gen_hash_zig": "sha256:711d11929e6ad2ad59beab7785ba672f5c7812eff2cc1a0aaa1e7e5ddc133ac6",
"module": "ClawModelRoute",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-18T15:12:43Z",
"sealed_at": "2026-10-19T07:01:31Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:f27cfca11fe116628decf5d0a935fc5a72db9a761ff06a85df6301f003bef24e",
"spec_hash": "sha256:4dd19b984389e06576034616ba920eb25171abc0b31fc9bde8cf728d248bd915",
"spec_path": "specs/claw/model_route.t27",
"tests": {
"failed": 0,
Expand All @@ -22,7 +22,7 @@
"vacuous": 0
},
"toolchain": {
"test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d",
"test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10",
"zig": "0.16.0"
}
}
7 changes: 3 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,10 @@ This triggers automatic branch creation for the next phase.

Load these skills when their functionality matches the task.

### Model routing (owner directive 2026-10-18)
### Model routing (owner directive 2026-10-19, replacing 2026-10-18)

The orchestrating session runs on **Opus 5.5**; every sub-task it delegates (implement, port, search,
test, docs, fix) runs on **Sonnet 5.5**, and escalates to Opus only after 2 failed attempts or on the
owner's word. The law is `specs/claw/model_route.t27`; how to follow it is the `model-routing` skill.
Every agent runs on **Sonnet 5.5** by default, the orchestrating session included; any job escalates to
**Opus 5.5** only after 2 failed attempts on the same task or on the owner's word. The law is `specs/claw/model_route.t27`; how to follow it is the `model-routing` skill.
`.claude/settings.json` sets `CLAUDE_CODE_SUBAGENT_MODEL` and each agent in `.claude/agents/` names its
model.

Expand Down
60 changes: 34 additions & 26 deletions specs/claw/model_route.t27
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
; specs/claw/model_route.t27 -- which Claude model does which job in TRI CLAW and in every agent
; session of this project: the orchestrator thinks on Opus 5.5, its sub-tasks run on Sonnet 5.5.
; Owner directive 2026-10-18: "orchestration through Opus 5.5, sub-tasks through Sonnet 5.5, it is
; cheaper; add this rule to TRI CLAW so development stays economical."
; session of this project: every role starts on Sonnet 5.5, the orchestrator included; Opus 5.5 is
; reached only on evidence (failed attempts) or on the owner's word.
; Owner directive 2026-10-19 (translated): "set Sonnet as the default model for all agents -- it is
; cheaper with the same result" (#10921). It replaces the directive of 2026-10-18 (#10493):
; "orchestration through Opus 5.5, sub-tasks through Sonnet 5.5", which kept the orchestration roles
; on Opus as "not a false economy"; the owner has since judged the result the same on Sonnet.
; --
; WHY. Epic #10066 measured one-shot spec-to-RTL work on the same ten tasks: Sonnet 5.5 passed 9 of
; 10 direct-Verilog tasks against Opus 5.5's 8, and 7 of 10 .t27 tasks against Opus's 7, at about 40%
; of Opus's cost (~$0.41 vs ~$0.99 and ~$0.77 vs ~$1.53 per task, list-price estimates). N = 10, one
; shot: the robust part is that Sonnet is not worse on bounded tasks and is much cheaper. What Opus
; is kept for is the part no sub-task sees: splitting the work, deciding what is true, and saying
; "merge" or "stop".
; was kept for until 2026-10-19 is the part no sub-task sees -- splitting the work, deciding what is
; true, saying "merge" or "stop" -- and that is now the owner's call per task, through owner_flag.
; --
; THE RULE.
; - A role is either ORCHESTRATION (orchestrate, plan, final review) or a SUB-TASK (implement,
; port, search, test, docs, fix). base_tier() maps the first to Opus and the second to Sonnet.
; - A sub-task escalates to Opus only on evidence: ESCALATE_AFTER failed Sonnet attempts on the same
; port, search, test, docs, fix). The distinction stays for records and reviews, but base_tier()
; maps every role to Sonnet.
; - Any role escalates to Opus only on evidence: ESCALATE_AFTER failed Sonnet attempts on the same
; task, or the owner's explicit flag. route() is that rule; nothing else may raise a tier.
; - An orchestration role is never routed down. A cheaper orchestrator is a false economy: its
; mistakes are paid for by every sub-task it spawns.
; - Opus without that evidence is waste, for an orchestration role as much as for a sub-task.
; - The rule names models by the aliases Claude Code's Agent tool and agent frontmatter accept
; ("opus", "sonnet") and by the full API ids, so a host that is not Claude Code (TRI CLAW's own
; chat agent, the Queen) reads the same table.
; --
; WHERE IT BINDS. Claude Code: `.claude/settings.json` sets CLAUDE_CODE_SUBAGENT_MODEL to the Sonnet
; id, the sub-task agents in `.claude/agents/` carry `model: sonnet`, and the skill
; id, every agent in `.claude/agents/` (the orchestrator `trinity.md` too) carries `model: sonnet`,
; the main session picks Sonnet 5.5 in the app's model menu, and the skill
; `.claude/skills/model-routing/SKILL.md` tells the orchestrator how to spawn and when to escalate.
; TRI CLAW's app and the Queen read this table when they pick a model (follow-up in their repos).
; ASCII only (L3). phi^2 + 1/phi^2 = 3 | TRINITY
Expand Down Expand Up @@ -68,32 +72,33 @@ pub fn is_orchestration(role : u32) -> bool {
return role <= R_REVIEW_FINAL;
}

/// The tier a role starts at. An unknown role is a sub-task: the cheap tier is the default, and an
/// orchestrator that wants Opus names its role.
/// The tier a role starts at: Sonnet for every role, known or not (owner 2026-10-19).
pub fn base_tier(role : u32) -> u32 {
if (is_orchestration(role)) { return TIER_OPUS; }
_ = role;
return TIER_SONNET;
}

/// The tier a job runs at: the base tier, raised to Opus for a sub-task only after ESCALATE_AFTER
/// failed attempts or on the owner's flag. Never lowered.
/// The tier a job runs at: the base tier, raised to Opus for any role only after ESCALATE_AFTER
/// failed attempts on the same task or on the owner's flag.
pub fn route(role : u32, failed_attempts : u32, owner_flag : bool) -> u32 {
if (base_tier(role) == TIER_OPUS) { return TIER_OPUS; }
if (owner_flag) { return TIER_OPUS; }
if (failed_attempts >= ESCALATE_AFTER) { return TIER_OPUS; }
return TIER_SONNET;
}

/// Is a sub-task running on Opus without the evidence route() asks for? The audit question for a
/// session log or a TRI CLAW run record.
/// Is a job running on Opus without the evidence route() asks for? The audit question for a session
/// log or a TRI CLAW run record, for every role.
pub fn is_waste(role : u32, tier : u32, failed_attempts : u32, owner_flag : bool) -> bool {
return tier == TIER_OPUS and route(role, failed_attempts, owner_flag) == TIER_SONNET;
}

test orchestration_roles_run_on_opus {
assert base_tier(R_ORCHESTRATE) == TIER_OPUS;
assert base_tier(R_PLAN) == TIER_OPUS;
assert base_tier(R_REVIEW_FINAL) == TIER_OPUS;
test orchestration_roles_start_on_sonnet {
assert base_tier(R_ORCHESTRATE) == TIER_SONNET;
assert base_tier(R_PLAN) == TIER_SONNET;
assert base_tier(R_REVIEW_FINAL) == TIER_SONNET;
assert is_orchestration(R_REVIEW_FINAL);
assert is_orchestration(R_IMPLEMENT) == false;
}

test sub_tasks_run_on_sonnet {
Expand All @@ -118,18 +123,21 @@ test a_sub_task_escalates_only_on_evidence {
assert route(R_SEARCH, 0, true) == TIER_OPUS;
}

test orchestration_is_never_routed_down {
assert route(R_ORCHESTRATE, 0, false) == TIER_OPUS;
assert route(R_PLAN, 0, false) == TIER_OPUS;
assert route(R_REVIEW_FINAL, 0, false) == TIER_OPUS;
test orchestration_escalates_only_on_evidence_too {
assert route(R_ORCHESTRATE, 0, false) == TIER_SONNET;
assert route(R_PLAN, 1, false) == TIER_SONNET;
assert route(R_REVIEW_FINAL, 2, false) == TIER_OPUS;
assert route(R_ORCHESTRATE, 0, true) == TIER_OPUS;
}

test negative_control_opus_on_a_fresh_sub_task_is_waste {
assert is_waste(R_PORT, TIER_OPUS, 0, false) == true;
assert is_waste(R_PORT, TIER_OPUS, 2, false) == false;
assert is_waste(R_PORT, TIER_OPUS, 0, true) == false;
assert is_waste(R_PORT, TIER_SONNET, 0, false) == false;
assert is_waste(R_PLAN, TIER_OPUS, 0, false) == false;
assert is_waste(R_PLAN, TIER_OPUS, 0, false) == true;
assert is_waste(R_ORCHESTRATE, TIER_OPUS, 0, true) == false;
assert is_waste(R_REVIEW_FINAL, TIER_OPUS, 2, false) == false;
}

invariant every_role_routes_to_a_known_tier {
Expand Down
Loading