diff --git a/scripts/aw.sh b/scripts/aw.sh index da22e566c..0f6dbe166 100755 --- a/scripts/aw.sh +++ b/scripts/aw.sh @@ -90,8 +90,8 @@ for field in ("name", "trigger", "engine", "permissions"): # Validate engine — warn on unknown but don't fail (new models can be added without a code change) known_engines = { - "claude-sonnet-4-6", "claude-haiku-4-5-20251001", "claude-opus-4-7", - "claude-haiku-4-5", + "claude-sonnet-4-6", "claude-sonnet-5-0", "claude-haiku-4-5-20251001", + "claude-opus-4-7", "claude-haiku-4-5", } engine = fm.get("engine", "") if engine and engine not in known_engines: diff --git a/scripts/engine.sh b/scripts/engine.sh index f59d91706..292522efe 100644 --- a/scripts/engine.sh +++ b/scripts/engine.sh @@ -125,7 +125,7 @@ set_engine_config() { ENGINE_AUDIT_MODEL="claude-fable-5" ENGINE_ACTION_MODEL="claude-sonnet-4-6" ENGINE_SINGLE_MODEL="claude-fable-5" - ENGINE_LABEL="triage: haiku 4.5 → deep: opus 4.8 + duck: o4-mini → audit: fable 5" + ENGINE_LABEL="triage: haiku 4.5 [sonnet 4.6 → sonnet 5] → deep: opus 4.8 [sonnet 4.6 → sonnet 5] + duck: o4-mini → audit: fable 5" ENGINE_SINGLE_LABEL="single-reviewer mode: fable 5" # Cross-engine rubber duck: use Copilot when Claude is primary DUCK_ENGINE="copilot" @@ -140,8 +140,16 @@ set_engine_config() { # - adaptive thinking only; budget_tokens/temperature/top_p/top_k removed # - omit thinking param entirely (disabled returns 400 on fable-5) # - min cacheable prefix: fable-5 = 2048 tok, opus-4-8 = 4096 tok - CLAUDE_TRIAGE_MODEL_CHAIN="${CLAUDE_TRIAGE_MODEL_CHAIN:-claude-haiku-4-5-20251001,claude-sonnet-4-6}" - CLAUDE_DEEP_MODEL_CHAIN="${CLAUDE_DEEP_MODEL_CHAIN:-claude-opus-4-8,claude-sonnet-4-6}" + # Sonnet 5 candidate (#1098): claude-sonnet-5-0 is appended AFTER the + # sonnet-4-6 slot in the triage and deep chains as a non-default fallback + # candidate — it earns its own TPM/RPM bucket for the layer-1 rate-limit + # walk without displacing the current primaries (haiku triage / opus deep) + # or the sonnet-4-6 fallback ordering. It is NOT the fleet default; a + # promotion to primary rides Story 5's canary rings. The daily cap is + # shared across Claude models (#206), so this hop only helps per-model RPM/ + # TPM, not the subscription cap. Action tier is out of scope (#1098). + CLAUDE_TRIAGE_MODEL_CHAIN="${CLAUDE_TRIAGE_MODEL_CHAIN:-claude-haiku-4-5-20251001,claude-sonnet-4-6,claude-sonnet-5-0}" + CLAUDE_DEEP_MODEL_CHAIN="${CLAUDE_DEEP_MODEL_CHAIN:-claude-opus-4-8,claude-sonnet-4-6,claude-sonnet-5-0}" CLAUDE_AUDIT_MODEL_CHAIN="${CLAUDE_AUDIT_MODEL_CHAIN:-claude-fable-5,claude-opus-4-8,claude-opus-4-7}" CLAUDE_ACTION_MODEL_CHAIN="${CLAUDE_ACTION_MODEL_CHAIN:-claude-sonnet-4-6,claude-opus-4-8}" CLAUDE_SINGLE_MODEL_CHAIN="${CLAUDE_SINGLE_MODEL_CHAIN:-claude-fable-5,claude-opus-4-8,claude-opus-4-7}" diff --git a/tests/dev-lead/unit/test_engine_chain.bats b/tests/dev-lead/unit/test_engine_chain.bats index d4976f973..eb9a2877c 100644 --- a/tests/dev-lead/unit/test_engine_chain.bats +++ b/tests/dev-lead/unit/test_engine_chain.bats @@ -185,6 +185,43 @@ _source_engine() { grep -q "claude-opus-4-8" "$MODEL_RECORD" } +# ── Sonnet 5 candidate wiring (#1098) ───────────────────────────────────────── +# Sonnet 5 is appended as a FALLBACK CANDIDATE after claude-sonnet-4-6 in the +# triage and deep chains (not a replacement, not the fleet default). The action +# tier is deliberately out of scope. + +@test "sonnet-5 candidate: triage chain default appends claude-sonnet-5-0 after sonnet-4-6" { + _source_engine "claude" + [ "$CLAUDE_TRIAGE_MODEL_CHAIN" = "claude-haiku-4-5-20251001,claude-sonnet-4-6,claude-sonnet-5-0" ] +} + +@test "sonnet-5 candidate: deep chain default appends claude-sonnet-5-0 after sonnet-4-6" { + _source_engine "claude" + [ "$CLAUDE_DEEP_MODEL_CHAIN" = "claude-opus-4-8,claude-sonnet-4-6,claude-sonnet-5-0" ] +} + +@test "sonnet-5 candidate: deep opus-4-8 + sonnet-4-6 both rate-limited → claude-sonnet-5-0 reached" { + _source_engine "claude" + # Both existing deep models throttle; the walk must reach the new candidate. + export STUB_ENGINE_EXIT_BY_MODEL="claude-opus-4-8=1|claude-sonnet-4-6=1|claude-sonnet-5-0=0" + export STUB_ENGINE_RESPONSE_BY_MODEL="claude-opus-4-8=429 too many requests|claude-sonnet-4-6=429 too many requests|claude-sonnet-5-0=sonnet 5 did the work" + + run run_agentic "$TEST_PROMPT" "claude-opus-4-8" "deep" + + [ "$status" -eq 0 ] + grep -q "claude-opus-4-8" "$MODEL_RECORD" + grep -q "claude-sonnet-4-6" "$MODEL_RECORD" + grep -q "claude-sonnet-5-0" "$MODEL_RECORD" + [[ "$output" == *"sonnet 5 did the work"* ]] +} + +@test "sonnet-5 candidate: action chain default is NOT touched (scope guard)" { + _source_engine "claude" + # Action tier is out of scope for #1098 — must stay sonnet-4-6 → opus-4-8. + [ "$CLAUDE_ACTION_MODEL_CHAIN" = "claude-sonnet-4-6,claude-opus-4-8" ] + [[ "$CLAUDE_ACTION_MODEL_CHAIN" != *"claude-sonnet-5"* ]] +} + # ── Gemini/Copilot unchanged: no chain applied ──────────────────────────────── @test "gemini: in-engine chain uses gemini model (not claude)" {