Commit 5 of 6 for ADR-093 (Commit 4 skipped — no thinking-config callers to shim; documented in the implementation plan). * src/routing/advisor/multi-model-executor.ts: DEFAULT_ADVISOR_MODEL upgraded from 'anthropic/claude-opus-4' to 'anthropic/claude-opus-4.7'. When TinyDancerRouter sets triggerMultiModel=true (confidence < 0.80, or < 0.85 for security tasks), MultiModelExecutor.consult() now dispatches to Opus 4.7 by default via HybridRouter.chat() + OpenRouter provider. * tests/routing/advisor/multi-model-executor.test.ts, tiny-dancer-router-advisor.test.ts: update expected provider model IDs. SWE-bench Verified 87.6% (vs 80.8% Opus 4.6), SWE-bench Pro 64.3% (vs 53.4%), CursorBench 70% (vs 58%). Vendor-independence preserved — consultations still flow through HybridRouter's 7 providers per ADR-043. Verification: * 0 tsc errors * 65/65 advisor unit tests pass Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
22 KiB
ADR-093 Implementation Plan — Opus 4.7 Migration + Claude Code 2026-04 Feature Adoption
ADR: ADR-093
Branch strategy: single PR on branch adr-093-opus-4-7-migration, separate commits per logical unit; one npm release when the block is complete
Release strategy: all commits ship in one npm version bump (not per-commit releases) — per user direction 2026-04-17
Deprecation warnings: none — migrate all at once per user direction
Hard deadline: 2026-06-15 (Sonnet 4 retirement) — PR-1 and PR-2 must land before this date
Owner: AQE Team
Plan date: 2026-04-17
Guiding principles for this plan
- No unverified failure modes. Every "this might break X" gets a test or a quarantine before merge.
- Structured output, not stdout grep. Post-change verification uses
grep -ccounts andtsc --noEmitexit codes, not hand-inspection. - Small, revertable PRs. Each PR is independently revertable without breaking the next one.
- Feature-flag new behavior.
xhighand 4.7 escalation land behind env/YAML flags that can be flipped off in seconds. - No coupling of unrelated changes. Routines/Monitor/Managed Agents stay out of this plan — tracked in a future ADR-094.
Commit ordering (single PR)
Commit 1 (registry) → Commit 2 (mechanical sweep) → Commit 3 (effort + security-agent effort:max) → Commit 4 (thinking shim) → Commit 5 (escalation wiring) → Commit 6 (security pinning)
All 6 commits land on adr-093-opus-4-7-migration, merged as one PR, shipped in one npm release. Commit 3 includes per-agent effort: max frontmatter for qe-security-auditor, qe-security-scanner, qe-security-reviewer, qe-pentest-validator (decision 2 of 2026-04-17). PR-7 (telemetry recalibration) remains a future follow-up, gated on 30 days of production data — not part of this PR.
PR-1: Model registry additions (no behavior change)
Branch: adr-093-pr1-model-registry
Intent: Add 4.7 and 4.6 entries, central constants, deprecation metadata for retiring IDs. Zero runtime behavior change.
Estimated size: ~150 LOC across 2 files + tests.
Files changed
| File | Change |
|---|---|
src/shared/llm/model-registry.ts |
Add claude-opus-4-7 entry (1M context, adaptive thinking, xhigh, new tokenizer flag), claude-sonnet-4-6 entry (standard 200k context), claude-haiku-4-5-20251001 entry. Export DEFAULT_SONNET_MODEL, DEFAULT_OPUS_MODEL, DEFAULT_HAIKU_MODEL, RETIRING_MODELS (with retirement dates). Mark claude-sonnet-4-20250514 deprecationDate 2026-06-15. |
src/shared/llm/model-mapping.ts |
Add Bedrock + Vertex IDs for Opus 4.7 (anthropic.claude-opus-4-7-v1:0) and Sonnet 4.6. |
src/shared/llm/model-registry.ts |
Extend ModelCapabilities interface with supportsAdaptiveThinking: boolean, supportsEffortXHigh: boolean, tokenizerVersion: 'legacy' | 'opus-4-7'. Default existing entries to false / 'legacy'. |
tests/unit/shared/llm/model-registry.test.ts |
New: assert DEFAULT_SONNET_MODEL === 'claude-sonnet-4-6'; assert getModelCapabilities('claude-opus-4-7').contextLength === 1_000_000; assert retiring models have a deprecationDate ≤ today + 60 days. |
Open questions
- Q1: Is the canonical 4.6 model ID
claude-sonnet-4-6or a dated variant likeclaude-sonnet-4-6-20260101? Must verify againsthttps://docs.anthropic.com/en/docs/about-claude/models/all-modelsbefore merge. If dated, updateDEFAULT_SONNET_MODELliteral accordingly. - Q2: Does Bedrock publish a stable
anthropic.claude-opus-4-7-v1:0model ID, or is it regional? Must verify viaaws bedrock list-foundation-modelsbefore merge.
Verification
npm run buildpassesnpm test -- tests/unit/shared/llm/model-registry.test.tspassesgrep -c 'DEFAULT_SONNET_MODEL' src/shared/llm/model-registry.tsreturns 1 (the export)
Rollback
git revert the PR commit. No data, no config, no user-facing change — trivial revert.
PR-2: Mechanical sweep of hardcoded model IDs
Branch: adr-093-pr2-model-id-sweep
Depends on: PR-1 merged
Intent: Replace every hardcoded claude-sonnet-4-20250514, claude-opus-4-5-20251101, and claude-3-5-haiku-20241022 with imports of the central constants from PR-1. Zero runtime behavior change (constants point to the same model strings during this PR; the switch to 4.6/4.7 is configuration, not code).
Estimated size: ~60 line edits across 25+ files, plus 2 lint-rule additions, plus tests.
Files to change (confirmed from grep 2026-04-17)
src/coordination/task-executor.ts:155-159— tier mapsrc/cli/commands/llm-router.ts:501,556src/shared/llm/router/routing-rules.ts:268,303,320,337,354src/shared/llm/router/types.ts:1092,1151,1155,1474src/shared/llm/router/hybrid-router.ts:1106src/shared/llm/router/agent-router-config.ts:145,154,181,208,668,687src/shared/llm/interfaces.ts:154— type unionsrc/shared/llm/provider-manager.ts:684src/shared/llm/cost-tracker.ts:29— must add new entries for 4.6 and 4.7 with updated $/MToksrc/shared/llm/model-mapping.ts:83,113src/shared/llm/providers/claude.ts:34,322src/shared/llm/providers/bedrock.ts:115,121
Important: cost-tracker update
cost-tracker.ts:29 currently has claude-sonnet-4-20250514: {input: 3.0, output: 15.0}. Add:
claude-sonnet-4-6: {input: 3.0, output: 15.0}(unchanged pricing)claude-opus-4-7: {input: 5.0, output: 25.0, tokenizer: 'opus-4-7'}(new; tokenizer multiplier for realistic cost reporting per ADR-093 Context §1)
Lint rule additions
Add ESLint rule banning raw claude-sonnet-4-20250514 / claude-opus-4-5-20251101 string literals outside the registry itself:
// .eslintrc.js or equivalent
'no-restricted-syntax': ['error', {
selector: "Literal[value=/^claude-(sonnet-4-2025|opus-4-5-2025|3-5-haiku)/]",
message: 'Use DEFAULT_SONNET_MODEL / DEFAULT_OPUS_MODEL / DEFAULT_HAIKU_MODEL from model-registry instead of hardcoded IDs',
}]
Exception: src/shared/llm/model-registry.ts (the registry itself) and tests/fixtures/*.ts.
Verification (pre-merge must-pass)
# 1. Zero remaining hardcoded retired IDs outside the registry
npm run lint 2>&1 | grep -c 'no-restricted-syntax' # must be 0
# 2. All IDs resolvable
npm test -- tests/unit/shared/llm/ # full LLM test suite passes
# 3. Type safety
npx tsc --noEmit # zero errors
# 4. Existing integration tests still pass
npm test -- tests/integration/llm/ # MSW provider tests
Risks
- R1: A missed file — grep was run once, but new code may have landed since. Mitigation: Re-run the grep from ADR-093's Context section as the last step before commit; PR description must show grep count before/after.
- R2: Type union in
interfaces.ts:154is a string literal union — changing it may cascade. Mitigation: Expand the union rather than replace, add 4.6 and 4.7 as new members; retire 4-20250514 in a follow-up after PR-5. - R3: Cost regression in reports if tokenizer multiplier not applied. Mitigation:
cost-tracker.tsapplies the 1.0x (legacy) or 1.35x (Opus 4.7) multiplier based on registrytokenizerVersion.
Rollback
git revert. Because PR-1's constants point to the same underlying IDs the sweep is replacing, rollback is safe.
PR-3: Effort-level plumbing (xhigh fleet-wide configurable)
Branch: adr-093-pr3-effort-level
Depends on: PR-1 (registry knows which models support xhigh)
Intent: Add QE_EFFORT_LEVEL env var + config/fleet-defaults.yaml + per-agent frontmatter override. Default to xhigh fleet-wide. First real behavior change.
Estimated size: ~400 LOC across 6 files + tests + config.
Resolution chain (highest priority wins)
runtime call site override (e.g. explicit `{effort: 'max'}` on .chat())
↓
per-agent frontmatter `effort: <level>` (parsed from .claude/agents/v3/qe-*.md)
↓
`QE_EFFORT_LEVEL` env var
↓
`config/fleet-defaults.yaml` → `effort_level`
↓
hardcoded default in resolver: `xhigh`
Files changed
| File | Change |
|---|---|
src/shared/llm/interfaces.ts |
Add type EffortLevel = 'low' | 'medium' | 'high' | 'xhigh' | 'max'; add optional effort?: EffortLevel to LLMRequestOptions. |
src/shared/llm/effort-resolver.ts |
NEW. resolveEffortLevel(agentName?, runtimeOverride?): EffortLevel implementing the chain above. Exports DEFAULT_EFFORT_LEVEL = 'xhigh'. |
config/fleet-defaults.yaml |
NEW. effort_level: xhigh (plus placeholder for future fleet-wide defaults). |
src/shared/llm/providers/claude.ts |
Read options.effort; if target model has capabilities.supportsEffortXHigh and effort === 'xhigh', include effort: 'xhigh' in the API request. Silently downgrade to high if the model doesn't support xhigh (logged at debug). |
src/shared/llm/router/hybrid-router.ts |
chat() calls resolveEffortLevel() when options.effort is not passed. |
.claude/agents/v3/_schema.md or agent-loader |
Extend agent frontmatter schema to accept optional effort: <level>. |
tests/unit/shared/llm/effort-resolver.test.ts |
NEW. Tests for all 5 resolution-chain levels + invalid-value rejection + downgrade path on non-supporting model. |
Per-agent override examples (not part of this PR — documented for reviewers)
qe-security-auditor.mdaddseffort: maxin frontmatter (higher-stakes agent)qe-coverage-specialist.mdaddseffort: medium(cheap, frequent scans)- All 60 qe-* agents default to fleet
xhighif they don't declare the field
Verification
# 1. Resolver returns xhigh by default
node -e "require('./dist/shared/llm/effort-resolver').resolveEffortLevel() === 'xhigh' || process.exit(1)"
# 2. Env var override works
QE_EFFORT_LEVEL=medium node -e "require('./dist/shared/llm/effort-resolver').resolveEffortLevel() === 'medium' || process.exit(1)"
# 3. Downgrade path: resolver asks for xhigh, target is Haiku, provider receives high
npm test -- tests/unit/shared/llm/effort-resolver.test.ts
npm test -- tests/unit/shared/llm/providers/claude.test.ts
Risks
- R4:
xhighon every agent raises token spend. Mitigation: PR-3 ships with a debug log line[aqe] effort resolved to <level> for <agent>so cost anomalies can be traced. PR-7 recalibrates after 30 days of data. - R5: Per-agent frontmatter parser already runs at agent-load time; adding a field is safe but needs a test. Mitigation: Unit test loading a qe-* agent file with and without the field.
- R6: The YAML config file is new — no existing loader. Mitigation: Use an existing YAML loader if present (
yamlpackage per package.json) or fall back to env-var-only in this PR and add YAML loading in PR-3.1.
Rollback
Set QE_EFFORT_LEVEL=high (or whatever the prior effective default was) as a safety switch. Full revert is clean.
PR-4: Thinking-config compatibility shim — SKIPPED 2026-04-17
Status: skipped. Pre-commit grep of src/ for budget_tokens, thinking: {type:"enabled"}, and uses of maxThinkingTokens returned zero callers that actually set thinking config on outbound requests. The maxThinkingTokens field exists on ClaudeConfig but is unreferenced. A shim with no callers is dead code.
Revisit when the first caller that sets thinking config appears — typically in the ADR-092 advisor path if/when it starts passing thinking budgets to Opus 4.7. Track as an open concern in ADR-093 §Validation Criteria.
Original intent (preserved for history): Translate legacy {type: "enabled", budget_tokens: N} → {type: "adaptive"} when target model is claude-opus-4-7; opt-in streamed CoT via thinking.display: "summarized" where consumers exist.
Files changed
| File | Change |
|---|---|
src/shared/llm/providers/claude.ts |
Before calling Anthropic SDK, inspect request.thinking + target model. If model is 4.7 and thinking has {type:"enabled", budget_tokens:N}, rewrite to {type:"adaptive"} and log console.warn('[aqe] ADR-093 shim: translated legacy thinking config for Opus 4.7, please migrate caller at <stack trace>'). Preserve display: "summarized" if caller set it. |
tests/unit/shared/llm/providers/claude-thinking-shim.test.ts |
NEW. Table-driven test: (target model, input thinking config) → (expected API payload). Cover (4.7, enabled+budget) → adaptive; (4.7, adaptive) → passthrough; (4.6, enabled+budget) → passthrough; (4.7, nothing) → nothing. |
docs/implementation/adrs/ADR-093-opus-4-7-migration.md |
Update Phase 3 status to "Implemented" when this PR merges. |
Verification
npm test -- tests/unit/shared/llm/providers/claude-thinking-shim.test.ts
# Must cover 4 scenarios from the table above
Risks
- R7: Shim silently changes caller-visible behavior. Mitigation: warn-level log on every translation; telemetry counter in
RouterMetricsCollectorso shim usage can be tracked and call sites prioritized for direct migration. - R8: Thinking block content omitted by default on 4.7 may break callers that consume CoT. Mitigation: Audit callers of
response.thinkingin the codebase before merge:grep -r 'response\.thinking\|message\.thinking' src/. If any found, addthinking.display: "summarized"at those call sites.
Rollback
Revert PR. Callers that have started relying on the shim will see 400s on 4.7 — so this PR must stay landed once 4.7 escalation (PR-5) is live.
PR-5: Wire Opus 4.7 as ADR-092 escalation target
Branch: adr-093-pr5-advisor-escalation
Depends on: PR-1, PR-2, PR-4
Intent: When triggerMultiModel=true and complexity warrants, MultiModelExecutor dispatches to Opus 4.7 (not Opus 4 or 4.5) via HybridRouter.chat(). 4.7 becomes the default Opus advisor model.
Estimated size: ~100 LOC across 3 files + tests + config.
Files changed
| File | Change |
|---|---|
src/routing/advisor/multi-model-executor.ts |
Default advisorModel changes from prior Opus ID to DEFAULT_OPUS_MODEL (= claude-opus-4-7). |
config/fleet-defaults.yaml |
Add advisor.default_model: claude-opus-4-7. |
.claude/agents/_shared/executor-preamble.md |
No change expected; verify that the preamble works unchanged on 4.7. |
tests/integration/routing/advisor-opus-4-7.test.ts |
NEW. End-to-end: TinyDancer sets triggerMultiModel=true, MultiModelExecutor.consult() dispatches to 4.7 via MSW-mocked Anthropic provider, result passes through redaction + circuit breaker. |
Verification
# 1. Integration test with MSW mock
npm test -- tests/integration/routing/advisor-opus-4-7.test.ts
# 2. Real-API smoke test (gated on ANTHROPIC_API_KEY, runs in CI on release branches only)
AQE_E2E_ADVISOR=true npm run test:e2e -- advisor-4-7-smoke
# 3. Circuit breaker still trips at 10 calls
npm test -- tests/unit/routing/advisor/circuit-breaker.test.ts
Risks
- R9: 4.7's $5/$25 per MTok + 1.35× tokenizer makes advisor calls more expensive than projected. Mitigation: PR-5 ships with
RouterMetricsCollectorrecording per-advisor-callestimatedCostUsd; PR-7 uses that to recalibrate thresholds. - R10: 4.7's different response style (more literal, less validation-heavy per research brief) may change advisor-output parsing. Mitigation: Advisor output is consumed as free text by the executor, not parsed by regex. Verified by reading
multi-model-executor.tsconsumer code.
Rollback
Switch config/fleet-defaults.yaml advisor.default_model back to the prior Opus ID. Full revert of the PR restores prior behavior.
PR-6: Security-agent pinning until Cyber Verification approval
Branch: adr-093-pr6-security-pinning
Depends on: PR-1 (needs registry entries)
Independent of: PR-2/3/4/5 (can merge in parallel)
Intent: Pin qe-pentest-validator, qe-security-scanner, qe-security-auditor, qe-security-reviewer to Sonnet 4.6 for any escalation path; reject 4.7 for these agents until AQE_CYBER_VERIFIED=true env var is set.
Estimated size: ~80 LOC across 2 files + tests.
Files changed
| File | Change |
|---|---|
src/shared/llm/router/hybrid-router.ts |
Before dispatch: if agent name matches `^qe-(pentest |
src/routing/advisor/multi-model-executor.ts |
Apply the same pin when triggerMultiModel=true dispatches. |
tests/unit/shared/llm/router/security-pinning.test.ts |
NEW. Verify all 4 agent names hit the pin; verify AQE_CYBER_VERIFIED=true lifts the pin; verify non-security agents are unaffected. |
Verification
npm test -- tests/unit/shared/llm/router/security-pinning.test.ts
# 8 test cases: 4 agents × (pinned | unpinned)
Risks
- R11: Agent name detection by regex may false-match a future agent like
qe-security-config-validator. Mitigation: Use an explicit allow-list (SECURITY_AGENTS: readonly ['qe-pentest-validator', 'qe-security-scanner', 'qe-security-auditor', 'qe-security-reviewer']) not a regex. - R12: The pin silently downgrades the model — user may not know. Mitigation: Log once per agent per session at info level; add a
/aqe llm cyber-statusCLI subcommand that reports pin status.
Rollback
Set AQE_CYBER_VERIFIED=true once Anthropic approval is received. To fully remove the pin later, a follow-up PR with approval reference number in the commit message.
Activation condition
AQE_CYBER_VERIFIED stays false (or unset) until the Cyber Verification application (docs/security/cyber-verification-application.md) receives an approval reference number from Anthropic. Record that number in ADR-093's Validation Criteria §5 and in this PR's post-merge changelog.
PR-7: Telemetry-gated threshold recalibration (+30 days)
Branch: adr-093-pr7-recalibrate-thresholds
Depends on: PR-5 landed for 30 calendar days
Not part of initial ship. Tracked here to keep the plan closed-loop.
Intent: Use 30 days of routing_outcomes data post-4.7-escalation to recalibrate (a) ADR-082 complexity thresholds for the new tokenizer, (b) ADR-092's 10-call circuit-breaker ceiling if data shows it's under- or over-sized, (c) xhigh cost impact and per-agent downgrade recommendations.
Inputs
- SQLite
routing_outcomestable, 30-day window RouterMetricsCollectorper-agent effort-level breakdownadvisor_consultationrows from PR-5 wiring
Outputs
- A new ADR or amendment to ADR-082 with recalibrated thresholds
- A PR updating
config/fleet-defaults.yamlwith per-agent effort overrides where data justifies - A decision log entry: "xhigh default retained" or "xhigh demoted to high for N agents"
Cross-cutting concerns
CI gate additions
Add to .github/workflows/:
- On every PR in this series: lint +
tsc --noEmit+ LLM unit tests must pass - On PR-2 specifically: a custom step
grep -c 'claude-sonnet-4-20250514' src/ | grep -v 0that fails if any remain outside the registry/tests - On PR-5: the MSW-mocked integration test gates merge; the live-API smoke test runs only on
mainpost-merge
Documentation updates
Per CLAUDE.md "NEVER proactively create documentation files" — this plan does not add new documentation beyond:
- Updates to ADR-093 status as PRs land
- Updates to
v3-adrs.mdindex row status (Proposed → Implementing → Implemented) - Per-PR changelog entries in existing
CHANGELOG.md
Migration communication
Each merge notifies users via:
- PR description (outcome-focused per CLAUDE.md §PR & Git Conventions)
CHANGELOG.mdentry- Release notes on the next npm version bump
No README rewrites. No separate migration guide unless a breaking API surface is exposed to users (none of these PRs do — they all land behind config).
Timeline (proposed)
| Week of | Milestone |
|---|---|
| 2026-04-21 | PR-1 open, reviewed, merged |
| 2026-04-28 | PR-2 open, reviewed, merged |
| 2026-05-05 | PR-3 open, reviewed, merged; xhigh active in working-april |
| 2026-05-12 | PR-4 open, reviewed, merged |
| 2026-05-19 | PR-5 open, reviewed, merged; 4.7 live as escalation target |
| 2026-05-26 | PR-6 open, reviewed, merged (can move earlier; no PR-2/3/4/5 dependency) |
| 2026-05-26 → 2026-06-15 | Buffer: watch error rates, cost dashboards; file any hotfix PRs |
| 2026-06-15 | Sonnet 4 retirement. Production fleet must be on 4.6 by this date. Verify with grep + release smoke test. |
| 2026-06-19 | +30 day telemetry review cutoff → PR-7 drafted |
| 2026-07-03 | PR-7 merged if data supports; otherwise revised ADR proposed |
Sonnet 4 retirement leaves ~4 weeks of buffer after PR-5; any slip past 2026-06-01 should trigger a "critical path" escalation.
Decisions resolved 2026-04-17
- ✅ Release vehicle: one npm release for all 6 commits (not per-commit).
- ✅ Per-agent effort overrides:
effort: maxadded to the 4 security/pentest agents in Commit 3 (same commit as plumbing). - ✅ Deprecation warnings: none; migrate all at once.
- ✅ PR cadence: single PR, separate commits, merge ASAP to beat Sonnet 4 retirement.
Outstanding — non-blocking
- Cyber Verification reference number: paste into ADR-093 §Validation Criteria item 5 and the CHANGELOG entry when Anthropic responds. Application has been submitted per user update 2026-04-17.
Non-goals for this plan
- Routines, Monitor, Managed Agents, Agent SDK rename,
/ultrareview, high-res vision for qe-visual-tester — all deferred to ADR-094. - Automatic prompt caching evaluation vs ADR-088 — deferred to ADR-095.
- Any change to the 60 qe-* agent prompts themselves — out of scope; this plan only changes routing/model/effort plumbing.
- Any change to MCP tool surface — ADR-093 is pure infra.