Skip to content

[Phase 2] Assess simplifying the sonnet->opus in-engine fallback given rate-limit equalization #1099

Closed

Description

@github-actions

Story

As a engine maintainer,
I want assess whether Sonnet 5's rate-limit equalization (Sonnet/Haiku now match Opus limits at every tier) removes the need for the in-engine sonnet->opus RPM/TPM fallback hop, and adjust the chains only if the evidence supports it,
so that the fallback chain stays exactly as long as it needs to be — a now-redundant hop can be dropped to reduce cost/latency, while the daily-subscription-cap safety net is explicitly kept.

Acceptance Criteria

  1. A written analysis states whether rate-limit equalization makes the in-engine sonnet->opus hop redundant for RPM/TPM scenarios, citing engine.sh's two-layer fallback doc (lines 12-24) and the shared daily-cap caveat (feat(dev-lead): proactive provider headroom check before engine invocation #206).
  2. The daily-subscription-cap fallback is explicitly preserved regardless of the RPM/TPM conclusion — the analysis calls out that the shared cap is NOT addressed by per-model RPM/TPM buckets.
  3. If the analysis concludes the hop is redundant, the chain edit is minimal and covered by an updated tests/dev-lead/unit/test_engine_chain.bats; if not, the chains are left unchanged and the rationale is recorded in the PR.
  4. No change weakens the cross-provider (claude->copilot->gemini) fallback path or the exit-code-2 rate-limit contract.
  5. shellcheck is clean and the dev-lead unit bats pass whether or not a chain edit is made.

Tasks / Subtasks

Dev Notes

  • engine.sh header (lines 12-24) documents the two fallback layers precisely; the in-Claude chain helps ONLY with per-model RPM/TPM buckets, never with the shared daily subscription cap (feat(dev-lead): proactive provider headroom check before engine invocation #206). Equalization is about rate LIMITS (RPM/TPM), so it can only affect layer-1 reasoning, not the daily-cap fallback.
  • This story is deliberately conditional: the correct outcome may be 'no code change, rationale recorded'. That is a valid, reviewable result — do not force a chain edit to look productive.
  • The cross-provider fallback (run_writer_with_fallback / review-batch.sh) keys off the engine layer returning exit code 2 when every in-engine model is rate-limited. Any chain trimming must keep that contract; see the is_rate_limited / exit-2 comments in engine.sh.
  • Depends on Story 3 having placed Sonnet 5 in the chain, so the assessment is over the actual post-wiring chain, not the current one.

Project Structure Notes

Analysis-first story scoped to the same engine.sh chains as Story 3; any edit is minimal and covered by the existing dev-lead unit bats. May legitimately produce no code change.

References

  • scripts/engine.sh#two-layer-fallback-doc
  • scripts/engine.sh#is_rate_limited-exit-2
  • tests/dev-lead/unit/test_engine_chain.bats

Likely target surface

  • scripts/engine.sh
  • tests/dev-lead/unit/test_engine_chain.bats

Story prepared by the BMAD Scrum Master (Bob) for epic #1095. Status: ready-for-dev.

Activity

  1. don-petry commented on Jul 8, 2026

    @don-petry
    Collaborator

    Dev-Lead Implementation Plan

    Issue: #1099 — [Phase 2] Assess simplifying the sonnet->opus in-engine fallback given rate-limit equalization

    Scope

    Analysis-first story. Assess whether Sonnet 5's rate-limit equalization (Sonnet/Haiku now match Opus limits at every tier) makes the in-engine sonnet->opus RPM/TPM fallback hop redundant. Preliminary conclusion: not redundant — equalization changes per-model bucket size, not bucket independence, and layer-1 recovery depends on independence. Chains stay unchanged; rationale recorded durably.

    Analysis (AC #1, #2)

    • The sonnet->opus hop lives in CLAUDE_ACTION_MODEL_CHAIN=claude-sonnet-4-6,claude-opus-4-8 (engine.sh:154).
    • Per the two-layer doc (engine.sh:13-22): layer-1 in-Claude chain helps ONLY with per-model TPM/RPM buckets ("each Claude model has its own TPM/RPM bucket, so swapping models often recovers"); it does NOT help the shared daily subscription cap (feat(dev-lead): proactive provider headroom check before engine invocation #206). Layer-2 cross-provider fallback (exit code 2) handles exhaustion.
    • Equalization makes each model's RPM/TPM bucket the same size; it does NOT merge them into one shared bucket. A saturated sonnet-4-6 bucket is still relieved by hopping to opus-4-8's separate bucket regardless of bucket size. => hop is NOT redundant for RPM/TPM.
    • The daily subscription cap is shared across all Claude models (feat(dev-lead): proactive provider headroom check before engine invocation #206), so no per-model hop addresses it; equalization concerns per-model rate LIMITS only, so it has no bearing on the daily-cap fallback. Layer-2 is explicitly preserved.

    Implementation Checklist

    Tests to Write

    Test Command

    bash .dev-lead/scripts/dev-lead-lint.sh (shellcheck) and bats tests/dev-lead/unit/test_engine_chain.bats

  2. don-petry commented on Jul 8, 2026

    @don-petry
    Collaborator

    Dev-Lead: Implementation Complete

    Assessment outcome

    The in-engine sonnet→opus hop is NOT redundant — chains left unchanged; rationale recorded.

    Rate-limit equalization makes each Claude model's RPM/TPM bucket the same size across tiers, but it does not merge them into one shared bucket. Layer-1 recovery (engine.sh:13-22) works because "each Claude model has its own TPM/RPM bucket" — it depends on the buckets being separate, not on them differing in size. A saturated claude-sonnet-4-6 RPM/TPM bucket is still relieved by hopping to claude-opus-4-8's independent bucket regardless of equal sizing, so the hop (CLAUDE_ACTION_MODEL_CHAIN=claude-sonnet-4-6,claude-opus-4-8, engine.sh:154) stays useful and is retained.

    Per the Dev Notes, this "no code change to the chains, rationale recorded" is the expected, valid result — no chain edit was forced to look productive.

    Acceptance criteria

    Test Results

    tests/dev-lead/unit/test_engine_chain.bats: 29/29 ok (incl. 3 new #1099 guards)
      ok 15 issue-1099: action chain retains the sonnet->opus RPM/TPM hop (not trimmed)
      ok 16 issue-1099: sonnet RPM/TPM limit still recovers via the opus hop (layer-1 intact)
      ok 17 issue-1099: daily-cap/exhaustion still yields exit 2 (cross-provider contract preserved)
    
    Full dev-lead unit suite: 695 tests.
      - Only pre-existing failure: "debug: REVIEW_MCP_DEBUG set + healthy handshake ... ::notice::"
        — confirmed flaky on the clean baseline (git stash) and unrelated to this change;
        passed in the run that included these changes. No new failures introduced.
    
    dev-lead-lint.sh: all checks passed (shellcheck on 91 files + 4 agent profiles + CODEOWNERS).
    

    Files Changed

    Phase-2 (TDD) note

    Per the documentation/behavior-preserving exception: the assessment concluded no behavioral change, so there was no red→green cycle. The added tests are regression guards that assert the (unchanged) intended behavior and pass against current code.

    Notes

    No follow-ups. The daily-subscription-cap safety net is explicitly kept; only per-model RPM/TPM reasoning was re-evaluated.

  3. added a commit that references this issue on Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    dev-leadFor dev-lead agent pickupinitiativeEpic / initiative tracking issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions