Skip to content

Thread-aware tier routing and mid-turn escalation (#663) - #674

Open
rockfordlhotka wants to merge 1 commit into
mainfrom
issue-663/thread-aware-tier-routing
Open

rockfordlhotka wants to merge 1 commit into
mainfrom
issue-663/thread-aware-tier-routing

Conversation

@rockfordlhotka

Copy link
Copy Markdown
Member

Closes #663. Part of #671.

Problem

In a 30-minute interactive work session, 22 of 26 user turns went to Low (gpt-5.4-mini). Two examples:

  • "figure out a way to update the doc" is 34 characters, just over the 30-character limit for the active-thread override.
  • "what are the key features of the MCP version 2 spec" scored 0.000.

The Low turns did the destructive work. High was never used.

Changes

  • Thread-aware floor (KeywordTierSelector):
    • On an established thread, a user turn is at least Balanced unless it is a pure acknowledgement (ConversationalSignals.IsTrivialAck: "ok", "thanks", 👍…). The 30-character limit is gone.
    • A turn inherits the highest tier from the session's last 3 turns within 30 minutes (SessionTierHistory). The history stores each turn's own tier, so an inherited High fades.
    • The floor is Balanced while one of the session's subagents is still running.
  • Research-question floor: a question of 6 or more words with a technical marker (acronym, version, spec/protocol/API/SDK/RFC) lifts Low to Balanced.
  • Retired low signals:
    • "what is/what's/what are/tell me about/look up/show me/i think/i was thinking" are removed from the compiled defaults.
    • They are also filtered out of the hot-reloaded tier-selector.json at load, with a warning, so dream tuning can't bring them back.
  • Mid-turn escalation:
    • Applies only to user turns, opted in through RunAsync(allowTierEscalation: true).
    • After a side-effecting tool call (ToolSideEffects classifies by the verb in the tool name, including the inner tool name of mcp_invoke_tool) or 2 tool errors, the rest of the loop runs on Balanced. Native path: TierEscalatingChatClient wraps Low's inner client. Text path: the effective tier is chosen per iteration.
    • Logs Tier escalated Low→Balanced mid-turn: <reason>.
  • Explainable routing: rule= appears on the routing log line and is recorded in TierRoutingEntry (additive fields).
  • Docs: routing-dream.md and docs/agent-host.md are updated so the routing dream doesn't fight the thread rules.

Deploy note

Copy the PVC file /data/agent/routing-dream.md over from the seed; init only seeds files that are missing. No tier-selector.json change is needed.

Tests

New: ThreadAwareTierRoutingTests, which includes a replay of the production session's messages, plus ConversationalSignalsTests, SessionTierHistoryTests, TierEscalationTests and TierEscalationEndToEndTests (the real AgentLoopRunner, native and text paths). Some existing KeywordTierSelectorTests rows were updated for the new rules. Full suite: 0 failures.

🤖 Generated with Claude Code

)

Production (2026-10-09): 22 of 26 user turns in a deck/research session went to
Low, and those Low turns did the damage (answered research from prior knowledge,
rewrote a file from a partial read). Keyword scoring saw only the message text.

Thread-aware tier floor (KeywordTierSelector, user messages only):
- Active-thread floor: on an established thread any non-trivial follow-up routes
  at least Balanced. The 30-char length gate (which a 34-char "figure out a way
  to update the doc" slipped past) and the matched-high-keyword gate are gone.
- Inherited tier: a turn routes at least the highest tier the session's last 3
  turns earned on their own (30-min recency). New SessionTierHistory singleton
  (bounded, expiring, thread-safe, in-memory) records each turn's IntrinsicTier
  so inheritance decays instead of perpetuating itself; mid-turn escalations
  raise the turn's entry; subagent results record Balanced; ClearContext resets.
- Active-subagent floor: Balanced while the session has a running subagent.
- Research-question floor: a question naming a technical subject (acronym,
  version, spec/protocol/API word) lifts Low to Balanced; trivia does not.
- Pure acknowledgements/greetings ("thanks", "ok", "got it", emoji) are exempt
  from the floors and may stay Low (ConversationalSignals.IsTrivialAck).

Low-signal research guard: "what is", "what's", "tell me about", "look up",
"show me", "i think", "i was thinking" removed from the compiled low signals,
and a compiled retired set filters them (plus "what are") from the merged list
even when the hot-reloaded tier-selector.json names them, with a warning.

Mid-turn escalation: AgentLoopRunner.RunAsync(allowTierEscalation) binds a
per-run TierEscalationContext for Low runs. A side-effecting tool call (verb in
the tool name, inner tool_name for mcp_invoke_tool; agent bookkeeping exempt —
ToolSideEffects) or 2 tool errors escalates to Balanced. The text loop and outer
native calls request EffectiveTier(tier); inside a native FICC loop that started
on Low, TierEscalatingChatClient (wired under the Low tier's FICC) redirects the
remaining iterations to the Balanced client. Logs "Tier escalated Low→Balanced
mid-turn: <reason>". UserMessageHandler opts in unless LLM:FixedTier pins the tier.

Explainable routing: TierClassification.Rule (TierRoutingRules), logged as
rule= with recentMaxTier/activeSubagent; TierRoutingEntry gains RoutingRule,
EscalatedTier, EscalationReason (additive, optional).

routing-dream.md and docs/agent-host.md describe the new rules and the retired
low signals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tier routing: interactive work threads land on Low; route from thread state, escalate on side effects

1 participant