Environment
- Repo / commit: NVIDIA-NeMo/Switchyard @
3acf3d86f10fead9f434b858dbbcf01a660b8db0
- Any classifier route (
deterministic, stage_router) behind a coding-agent client
Description
The classifier can make a confident routing decision without ever seeing the user's
request.
_summarize_request serializes the request to JSON, then truncates from the tail at
max_request_chars (default 16,000). Message content is serialized in order, and
coding agents put their own context — project instructions, skill catalogs, hook
output — before the typed text. So the newest user turn is the last thing in and
the first thing cut.
Steps to Reproduce
- Point Claude Code at a classifier route in a repo that has a
CLAUDE.md.
- Ask something the routing policy maps to a specific target.
Actual Result
The user message is 24,597 chars against a 16,000 budget:
summary : 15,982 chars
truncated : True
QUESTION VISIBLE : False
DECISION : target=opus confidence=0.95
The classifier routed boilerplate at 0.95 confidence. Reproduced by running
upstream _summarize_request, _condense_body, and trim_messages standalone at
stock defaults.
Expected Result
The request being routed is never truncated. A classifier that cannot see the query
should abstain, not answer confidently.
Suggested Fix
Render the query outside the truncation budget, and bound only the history:
Route the request in <query>. <history> is background.
<history>…bounded by max_request_chars…</history>
<query>…always complete…</query>
Two related improvements we needed alongside it:
- Skip
<system-reminder> blocks in the classifier's view. They are harness
context, not user intent, and were 24 KB of the 24.5 KB above. Harnesses that do
not emit them are unaffected.
- Make the first-user anchor optional (
include_first_user: false).
trim_messages always keeps the first user message. That is right for harnesses
that put task framing there, but for a coding-agent CLI each turn is an
independent request, and the opening question anchors every later routing
decision to its topic. We measured 3/8 correct with the anchor versus 7/8 without.
max_request_chars and recent_turn_window are already configurable in YAML — the
issue is not exposure but that no budget is safe when the query itself can be cut.
A larger default would suit large-context classifiers, but only the structural fix
removes the failure.
Environment
3acf3d86f10fead9f434b858dbbcf01a660b8db0deterministic,stage_router) behind a coding-agent clientDescription
The classifier can make a confident routing decision without ever seeing the user's
request.
_summarize_requestserializes the request to JSON, then truncates from the tail atmax_request_chars(default 16,000). Message content is serialized in order, andcoding agents put their own context — project instructions, skill catalogs, hook
output — before the typed text. So the newest user turn is the last thing in and
the first thing cut.
Steps to Reproduce
CLAUDE.md.Actual Result
The user message is 24,597 chars against a 16,000 budget:
The classifier routed boilerplate at 0.95 confidence. Reproduced by running
upstream
_summarize_request,_condense_body, andtrim_messagesstandalone atstock defaults.
Expected Result
The request being routed is never truncated. A classifier that cannot see the query
should abstain, not answer confidently.
Suggested Fix
Render the query outside the truncation budget, and bound only the history:
Two related improvements we needed alongside it:
<system-reminder>blocks in the classifier's view. They are harnesscontext, not user intent, and were 24 KB of the 24.5 KB above. Harnesses that do
not emit them are unaffected.
include_first_user: false).trim_messagesalways keeps the first user message. That is right for harnessesthat put task framing there, but for a coding-agent CLI each turn is an
independent request, and the opening question anchors every later routing
decision to its topic. We measured 3/8 correct with the anchor versus 7/8 without.
max_request_charsandrecent_turn_windoware already configurable in YAML — theissue is not exposure but that no budget is safe when the query itself can be cut.
A larger default would suit large-context classifiers, but only the structural fix
removes the failure.