Description
The Discord and Slack channel adapters store and look up "default agent per channel" using keys that don't include the invoking user. As a result, /agent <name> writes overwrite each other across users on a shared channel, and reads pick up whichever value was written most recently — regardless of who's asking. Two distinct user-visible symptoms:
- Cross-user collisions on shared channels. When two users share a channel (e.g.
#general), the second user's /agent <name> overwrites the first user's default for that channel — silently. The first user's next mention routes to the wrong agent.
- Defaults bleed across channels. A user setting
/agent assistant in #general ends up affecting agent resolution in other channels for the same platform installation.
Context: every message that targets the bot is @mention-prefixed (there's only one bot per OpenFang daemon). The bug isn't about implicit routing — the mention is always present. The bug is about which underlying agent the bot dispatches to once mentioned.
Root cause (for triage)
In openfang/crates/openfang-channels/src/bridge.rs, the router's default-agent map needs to be keyed on (user_id, channel_id) on both the read and write paths to give each user their own per-channel default.
On main, both paths key on sender.platform_id, which the Discord/Slack adapters populate with the channel/conversation ID (it's needed downstream for the send path). The sender_user_id() helper existed but only the rate-limit/authz paths used it — routing reads and the four set_user_default writes didn't, and neither did the two broadcast lookups. Result: every per-user default ends up stored and looked up under the channel ID, so writes from different users on the same channel collide and reads return whoever wrote last.
The fix is localized to bridge.rs. Happy to send a PR if this is welcome — branch is fix/router-user-id-keying on my fork.
Expected Behavior
/agent <name> should set a default agent that is scoped to the invoking user on the current channel, with these properties:
- User-scoped: User A's default on
#general is independent of User B's default on #general. User B's /agent does not overwrite User A's.
- Channel-scoped: A user's default on
#general does not affect resolution on #other-channel. Each (user, channel) pair holds its own default.
- Read path matches write path: the resolved default for a given mention is the same key that was written by the same user's last
/agent on the same channel.
Steps to Reproduce
On main (pre-fix), in a workspace with at least two users sharing one channel:
Repro A — cross-user collision:
- As User A in
#general: /agent assistant → confirmed set.
- As User B in
#general: /agent coder → confirmed set.
- As User A in
#general: <@bot> hello.
- Expected: routes to
assistant (User A's default).
- Actual: routes to
coder (User B's /agent clobbered User A's).
Repro B — cross-channel bleed:
- As User A in
#general: /agent assistant.
- As User A in
#other-channel: <@bot> hello.
- Expected: uses
#other-channel's own default (or no default).
- Actual: picks up
assistant from #general.
OpenFang Version
0.6.0
Operating System
macOS (Apple Silicon)
Logs / Screenshots
Reproducible without logs — both repros above are deterministic on a clean install. Happy to attach a daemon log capture of the collision if a triager requests one.
Description
The Discord and Slack channel adapters store and look up "default agent per channel" using keys that don't include the invoking user. As a result,
/agent <name>writes overwrite each other across users on a shared channel, and reads pick up whichever value was written most recently — regardless of who's asking. Two distinct user-visible symptoms:#general), the second user's/agent <name>overwrites the first user's default for that channel — silently. The first user's next mention routes to the wrong agent./agent assistantin#generalends up affecting agent resolution in other channels for the same platform installation.Root cause (for triage)
In
openfang/crates/openfang-channels/src/bridge.rs, the router's default-agent map needs to be keyed on(user_id, channel_id)on both the read and write paths to give each user their own per-channel default.On
main, both paths key onsender.platform_id, which the Discord/Slack adapters populate with the channel/conversation ID (it's needed downstream for the send path). Thesender_user_id()helper existed but only the rate-limit/authz paths used it — routing reads and the fourset_user_defaultwrites didn't, and neither did the two broadcast lookups. Result: every per-user default ends up stored and looked up under the channel ID, so writes from different users on the same channel collide and reads return whoever wrote last.The fix is localized to
bridge.rs. Happy to send a PR if this is welcome — branch isfix/router-user-id-keyingon my fork.Expected Behavior
/agent <name>should set a default agent that is scoped to the invoking user on the current channel, with these properties:#generalis independent of User B's default on#general. User B's/agentdoes not overwrite User A's.#generaldoes not affect resolution on#other-channel. Each(user, channel)pair holds its own default./agenton the same channel.Steps to Reproduce
On
main(pre-fix), in a workspace with at least two users sharing one channel:Repro A — cross-user collision:
#general:/agent assistant→ confirmed set.#general:/agent coder→ confirmed set.#general:<@bot> hello.assistant(User A's default).coder(User B's/agentclobbered User A's).Repro B — cross-channel bleed:
#general:/agent assistant.#other-channel:<@bot> hello.#other-channel's own default (or no default).assistantfrom#general.OpenFang Version
0.6.0
Operating System
macOS (Apple Silicon)
Logs / Screenshots
Reproducible without logs — both repros above are deterministic on a clean install. Happy to attach a daemon log capture of the collision if a triager requests one.