Summary
Auto-routing (resolve_assistant_target) only engages when the channel's resolved agent is literally named "assistant".
Any other front-door agent silently bypasses intent classification — even when an operator has explicitly opted into per-channel auto_route via #2150.
The AutoRouteStrategy gate added in #2150 is already the intended on/off switch; the hardcoded name check duplicates it and over-constrains it.
Where (main @ dceae897a — line numbers may drift)
crates/librefang-kernel/src/kernel/assistant_routing.rs:190 — resolve_assistant_target early-returns if entry.name != "assistant" { return Ok(agent_id); }, which runs before the match ctx.auto_route { … } gate at :210. So the strategy gate is unreachable for any agent not named "assistant".
assistant_routing.rs:133 — llm_classify_intent treats a classification result of "assistant" as "handle it yourself". This sentinel-in-output is legitimate and separate from the entry gate above.
assistant_routing.rs:394 — the metadata route path also special-cases template != "assistant".
Why it's a problem
- Routing capability is keyed on a magic string instead of a config flag/capability.
- To use LLM or metadata dispatch, an operator must rename their front-door agent to
"assistant" and demote the real agent to a specialist — an awkward inversion.
- Setting
auto_route = "sticky_ttl" (or "explicit_only") on a channel whose default agent is named anything else is silently inert — no warning, no log; the operator's config simply does nothing.
- Only one dispatcher concept is possible per deployment (the single
"assistant" name).
Minimal repro (synthetic)
- Configure a channel with
default_agent = "concierge" (any name ≠ "assistant").
- Set that channel's
auto_route = "sticky_ttl".
- Define two routable specialist templates with distinct descriptions.
- Send a message that clearly matches one specialist's description.
Expected: classifier routes the message to the matching specialist.
Actual: resolve_assistant_target returns at :190; classification never runs; the message is handled by concierge.
Proposal
Gate purely on AutoRouteStrategy (plus the existing routable-agent allowlist), not on the literal entry name:
- Drop the
entry.name != "assistant" precondition at :190. AutoRouteStrategy::Off (the default for all channels) already provides the bypass that preserves legacy behaviour.
- Keep
"assistant" as a sentinel only in the classifier output (:133) — i.e. "no specialization needed, the current agent handles it". That is distinct from the entry gate and stays.
- When
auto_route != Off and the classifier output resolves back to the current agent, no-op (avoid a self-route).
- Optional: emit a
WARN when auto_route is configured on a channel but routing is structurally unreachable, so misconfiguration is not silent.
Backwards-compatible: the default auto_route = Off preserves today's behaviour for every existing deployment.
The only behavioural change is that an agent named something other than "assistant" can now opt into routing.
Related
Summary
Auto-routing (
resolve_assistant_target) only engages when the channel's resolved agent is literally named"assistant".Any other front-door agent silently bypasses intent classification — even when an operator has explicitly opted into per-channel
auto_routevia #2150.The
AutoRouteStrategygate added in #2150 is already the intended on/off switch; the hardcoded name check duplicates it and over-constrains it.Where (main @
dceae897a— line numbers may drift)crates/librefang-kernel/src/kernel/assistant_routing.rs:190—resolve_assistant_targetearly-returnsif entry.name != "assistant" { return Ok(agent_id); }, which runs before thematch ctx.auto_route { … }gate at:210. So the strategy gate is unreachable for any agent not named"assistant".assistant_routing.rs:133—llm_classify_intenttreats a classification result of"assistant"as "handle it yourself". This sentinel-in-output is legitimate and separate from the entry gate above.assistant_routing.rs:394— the metadata route path also special-casestemplate != "assistant".Why it's a problem
"assistant"and demote the real agent to a specialist — an awkward inversion.auto_route = "sticky_ttl"(or"explicit_only") on a channel whose default agent is named anything else is silently inert — no warning, no log; the operator's config simply does nothing."assistant"name).Minimal repro (synthetic)
default_agent = "concierge"(any name ≠"assistant").auto_route = "sticky_ttl".Expected: classifier routes the message to the matching specialist.
Actual:
resolve_assistant_targetreturns at:190; classification never runs; the message is handled byconcierge.Proposal
Gate purely on
AutoRouteStrategy(plus the existing routable-agent allowlist), not on the literal entry name:entry.name != "assistant"precondition at:190.AutoRouteStrategy::Off(the default for all channels) already provides the bypass that preserves legacy behaviour."assistant"as a sentinel only in the classifier output (:133) — i.e. "no specialization needed, the current agent handles it". That is distinct from the entry gate and stays.auto_route != Offand the classifier output resolves back to the current agent, no-op (avoid a self-route).WARNwhenauto_routeis configured on a channel but routing is structurally unreachable, so misconfiguration is not silent.Backwards-compatible: the default
auto_route = Offpreserves today's behaviour for every existing deployment.The only behavioural change is that an agent named something other than
"assistant"can now opt into routing.Related
auto_routestrategy gate this issue builds on.