· 5 min read
Routing v1 shipped with one rule the users loved and one rule they hated.
The one they loved: "describe the problem in plain words, we pick the specialist." A junior engineer who did not know we had a debug-detective agent would type "this is broken" and get senior-grade bug triage.
The one they hated: the agent re-announced itself on every follow-up. "You asked a follow-up — routing to debug-detective." Three turns in: "Still routing to debug-detective." At turn six the user wrote "stop telling me who you are" and the thread was ruined.
The announcement was correct the first time. Every repeat was noise.
Routing v2 ships eight dimensions. The one that fixes this is Dimension 2 — session-state awareness. Also known as sticky routing.
The rule
Once you route to an agent in a session, stay in that agent's voice for follow-ups unless:
- The next message contains a stronger signal for a different agent
- The user says "back to normal", "stop routing", "just answer me"
- The task has clearly concluded
Never re-announce the same routing. Sticky equals quiet.
Why this is harder than it sounds
The naive implementation is a session variable: "last_agent = debug-detective". Any follow-up skips the announcement. That is wrong.
Three cases the naive version mishandles:
Case 1 — a stronger signal for a different agent
User routed to debug-detective for a NullPointerException. Three turns in they paste a stack trace from a different service, with "and now this one is leaking memory in prod." That is incident-commander territory. The sticky session says "stay in debug-detective"; the message says "re-route to a different specialist loudly, this is a new problem."
Our rule: on every new turn, re-evaluate signals. If the new signal's strength > the sticky agent's strength + threshold, re-route and announce. Threshold matters — small signal differences keep the session calm, large ones override.
Case 2 — same agent, different sub-skill
User routed to code-reviewer for a TypeScript file. Then pastes a Python file. The agent is still code-reviewer by name, but the per-language reviewer is different. Our routing picks typescript-reviewer on the first paste and python-reviewer on the second. Both are flavours of code-reviewer. Announcement should fire — the language dispatch is a real handoff — but the parent intent (code review) is unchanged.
Our rule: announce language dispatch as a chained line, not a cold introduction. "Still code-review — switching to python-reviewer for this file." The user sees continuity, not a reset.
Case 3 — task concluded, user moves on
User routed to tdd-guide, test passes, user says "great, now add a loading spinner." The old session is done. Sticky says "stay in tdd-guide"; the message says "that was a different request type entirely, we are in UX now."
Our rule: when the current message carries a different Priority tier from the sticky agent's tier (see the priority tiers in intent-routing.md), the sticky lock is broken and we re-evaluate from scratch. Priority 6 (testing) → Priority 3 (UX) is a priority jump; it breaks the lock.
The announcement itself
When we do announce, Dimension 6 (announcement style adapts to terse mode) kicks in. If the user has typed /terse this session:
→ debug-detectiveThat is the entire announcement. No tip line. No reasoning sentence.
If terse is off:
→ Routing to debug-detective (stack trace + frustration signals match the bug-tracking pattern)
[response]
Tip: next time you can type "use debug-detective" to invoke me directly.The tip line was the second-most-complained-about element in Routing v1. We keep it only for new users (first three routing announcements in a session). After that it is on, but suppressed.
Where this came from
Session logs. We looked at the 50 longest ECC sessions from July 2026 and tagged every point where the user explicitly said "stop" / "yeah I know" / "move on". 73% of those tags happened within one turn of a redundant routing announcement.
The agent was not routing wrong. The announcement was literally the thing being complained about.
Sticky routing removes the irritant without removing the signal. First announcement teaches the user who they are talking to. Silence after that respects that they already know.
What you cannot do
- Never silently route on a cold session. The first announcement is the user's informed consent.
- Never stay silent through a Priority jump. If the user went from "bug" to "deploy", they need to know a different agent is answering.
- Never let sticky survive user explicit override.
use <agent>or@<agent>always wins and always announces.
The code
The dispatcher is 60 lines in scripts/router/dispatch.js. Every turn:
- Compute signal strength per agent for the new message
- If sticky agent is still the top choice within threshold, continue silently
- If a different agent dominates by threshold, announce and switch
- If priority tier jumped, announce and switch regardless of strength
- If user explicitly invoked, override and announce
No ML, no embeddings, no vector store. Rules + a threshold the user can tune in ~/.kodelythecc/router.json.
Read the full dispatcher logic in Intent Routing v2 — specifically Dimension 2 and 6.
The right amount of routing-announcement noise is one line per real handoff. Not one per follow-up.