Skip to content

bug(channels): inbound routing falls to non-deterministic "first available" agent when SidecarChannelConfig has no default + empty channels allowlist accepts all (related #4926) #5294

Description

@DaBlitzStein

Summary

After migrating Telegram (and other channels) to sidecar via #5241, the SidecarChannelConfig struct has no default_agent field — only name, command, args, env, channel_type, restart/overflow knobs. The old [channels.telegram] config had default_agent = "X" which used to seed the AgentRouter.channel_defaults at boot. With sidecar, that path is gone.

Result: when an inbound message arrives on a sidecar channel, AgentRouter::resolve_with_context (crates/librefang-channels/src/router.rs:202) returns None because:

  • No binding for (channel, peer_id) set
  • No channel_defaults["telegram"]
  • No default_agent

Then resolve_or_fallback (crates/librefang-channels/src/bridge.rs:2780) tries find_agent_by_name("assistant") → typically None → falls to "first available agent" — which is whichever agent the registry happens to iterate first (non-deterministic, ID/spawn-order-dependent).

Repro

  1. Existing setup: agent fandangorodelo with channels = ["telegram"] in its manifest, configured Telegram via [[sidecar_channels]].
  2. Create a second agent (any purpose, e.g. Profesor) without restricting its channels list (defaults to channels = []).
  3. Send a Telegram message to the bot.
  4. Inbound message routes to Profesor instead of fandangorodelo because (a) router has no channel_defaults["telegram"], (b) router falls to first-available, (c) Profesor was created later but happens to win the iteration order.

Confirmed empirically in our deploy:

DEBUG run_agent_loop: Memory decision: ADD new: Has generated file at `output/horario_definitivo.md`...
  agent.name=Profesor  agent.id=600770a9-bfbc-5e93-b83d-c9e7729ca55c  session.id=9bdaf4de-...

The user wrote to fandangorodelo on Telegram; the loop ran on Profesor. File ended up in ~/.librefang/workspaces/agents/Profesor/output/.

Root cause — 3 layers

Layer A: SidecarChannelConfig (crates/librefang-types/src/config/types.rs:2155) is missing default_agent: Option<String>. The in-process TelegramConfig/DiscordConfig/etc. all have it.

Layer B: channels = [] on an agent means "accepts all channels" (no allowlist filter applied at resolve_or_fallback line 2814). This is opt-out-by-default. Sane default would be opt-in: [] = no inbound channels, agent must explicitly declare "telegram" to receive Telegram traffic.

Layer C: The "first available agent" fallback in resolve_or_fallback (around line 2829) is non-deterministic. When the router resolves nothing, the bridge falls to handle.find_agent_by_name(\"assistant\") and then to the first one in list(). Neither is a clear contract for the operator. A misconfigured deployment silently routes to a wrong agent instead of erroring loud.

Workaround applied locally

POST /api/bindings { agent: \"fandangorodelo\", match_rule: { channel: \"telegram\" } } — adds an explicit binding for all Telegram traffic to fandango. Works without restart. But this is a per-deployment manual step that shouldn't be necessary; operators creating a new agent should not silently break inbound routing for their existing agents.

Proposal

  1. Add default_agent: Option<String> to SidecarChannelConfig so sidecar parity matches in-process channel configs. Wire it into router population at boot (api/src/channel_bridge.rs:3320-3344 already has the loop for in-process channels — add the equivalent for sidecar_channels).
  2. Flip channels = [] semantics to opt-in (breaking): empty allowlist means "no inbound channels" instead of "all channels". Operators upgrading need to add explicit channel lists to existing agents. Provide a one-shot migration helper. Related to feat(channels): N:M agent-channel assignment — replace single default_agent with multi-agent routing #4926 (N:M routing).
  3. Or, at minimum: when no binding, no channel_default, no default_agent matches, return error loud instead of silently picking first-available. The current behavior is a footgun — every new agent created is a potential routing accident.

Related: #4926 (N:M agent-channel), #4961 (allowlist field — already adds the channels field but doesn't flip the default semantics).

Logs / DB snapshots available on request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/channelsMessaging channel adaptershas-prA pull request has been linked to this issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions