feat(stage_router): corroborative tanh-scored routing with windowed self-clearing signals#68
Draft
sabhatinas wants to merge 12 commits into
Draft
feat(stage_router): corroborative tanh-scored routing with windowed self-clearing signals#68sabhatinas wants to merge 12 commits into
sabhatinas wants to merge 12 commits into
Conversation
WalkthroughThe stage router now separates ChangesStage router behavior
Estimated code review effort: 3 (Moderate) | ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
sabhatinas
force-pushed
the
sabhatinas/stage-router-scorer-corroborative-tanh
branch
from
July 16, 2026 17:08
c43105a to
8c8c303
Compare
…elf-clearing signals
…ring signals; window=5
sabhatinas
force-pushed
the
sabhatinas/stage-router-scorer-corroborative-tanh
branch
from
July 17, 2026 19:13
0279579 to
220c474
Compare
…fault, configurable)
…ons were real loops; spurious Opus bias)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reworks the Python
stage_routerscorer / picker / signals into a clean, corroborative, honestly-documented router, then adds two follow-on capabilities on top: context-compaction handling and optional tier-transition handoff notes. Decisions are corroborative across two axes, signals self-clear over a recent window, and the confidence score spreads smoothly across the range.Tracked in Linear: SWITCH-939 (scorer refactor), SWITCH-983 (compaction), SWITCH-982 (handoff notes).
1. Signal + scorer refactor (SWITCH-939)
write_intensity,edit_intensity,tests_passed-as-dim,pure_bash_intensity,planning_active,stuck_exploring/no_progress).spinningandexploringare now mutually exclusive (spinning= deep pure churn;exploring= deep read/plan without producing).exploringis promoted to a full escalation latch — trace-verified that treating it as neutral routed struggling agents to the weak tier and lost the task.severity,spinning,exploring) → CAPABLE (+); PROGRESS (recent_production_intensity) → EFFICIENT (−).tanh(gain·raw),confidence = |score|. Droppedno_error_streak(a clean-result streak was cancelling the windowed error signal and releasing escalations too early).signals-to-clear = threshold / unit. The real dial is smooth: t≈0.3 escalates on ~1 signal, ~0.5 needs ~1.5, ~0.7 needs 2 corroborating; reachable range(0, ~0.76)for the two axes.capable_first/efficient_firstdiffer only in the low-confidence default tier; both driven by the same scorer. Removed the oldef_escalatepath (it had bypassed the scorer's strong side). Decision-source order:no_signal → override (CRITICAL severity) → tests_passed (settled-run, severity-gated) → dimensions → llm-classifier → fall_open.DEFAULT_RECENT_WINDOW3→5 for stickiness;detect_tests_passedhonours the window; trace-mined error patterns from ~1006 local trajectories (added permission-denied, TypeError/KeyError, segfault, linker/compile, npm/cargo/go build failures, "file does not exist", etc. at correct severity).2. Context-compaction handling (SWITCH-983)
Auto-compaction resets the stateless per-request signals, de-escalating hard tasks back to the weak tier at the worst moment (observed:
video-processingstrong-share 87%→0%,sam-cell-seg48%→18% after the compaction marker).compacted: boolonToolResultSignal;DimensionCollectorscans the full history for the compaction marker, so it self-latches (the summary stays pinned in the prefix every subsequent turn)._apply_overridesforces (and holds) CAPABLE whencompacted.3.
repeated_cmd_ratio— added then removed (honest)Added a
repeated_cmd_ratioWRONG signal (catch a weak model looping one command), then removed it from the scorer after an ablation over 178 task-runs found 0/71 of its escalations were real loops: itsmax_count / windownormalisation makes a single Bash call read as ratio 0.20, firing a low-grade CAPABLE bias on any shell turn (~8% spurious Opus, no accuracy payoff). Still computed for observability; re-add only with amax_count ≥ 3gate.4. Optional tier-transition handoff notes (SWITCH-982)
HandoffNoteInjectorappends a short ephemeral guidance note to the outgoing request on a weak↔strong transition, so the model taking over knows why. Off by default, fully configurable (HandoffNoteConfig). Ephemeral (never persisted → notes don't accumulate), transition-gated (stays out of the cache prefix on steady-state turns), truthful escalation gate, API-correct injection for Anthropic Messages (append atextblock after the trailingtool_result) and OpenAI Chat. Prototype in the v1 Python path; not yet A/B-tested.Validation
stage_routerunit tests pass (scorer, pickers, dimensions, handoff notes), plus Rust signal tests;ruff+mypyclean.efficient_first. Weak-tier accuracy/cost trade-offs (and the finding that no weak tier beats native Opus on this benchmark) are tracked in SWITCH-951; this PR is the routing machinery, not a benchmark-win claim.Out of scope (follow-up)
stage_routerprofile (crates/switchyard-components-v2/src/profiles/stage_router/) for parity — intentionally separate.🤖 Generated with Claude Code