#20078: feat(session): Add channelGroups config(optional config) for shared cross-channel sessions
channel: discord
channel: whatsapp-web
size: S
## Summary
- **Problem:** When the same agent is bound to multiple Discord/Slack channels, each channel creates an isolated session (`agent:<id>:discord:channel:<channelId>`). This causes context fragmentation (agent contradicts itself across channels), concurrent write conflicts on shared files, and no real-time context sharing between channels.
- **Why it matters:** Multi-agent setups with topic-based channels (#planning, #dev, #art, #qa) have no way to selectively merge channel sessions. The only workaround is `session.scope: "global"` which merges everything including DMs — too blunt.
- **What changed:** Implements `session.channelGroups` (Option A from #19929) — a `Record<string, string[]>` config under `session` that maps a group name to channel identifiers. Grouped channels share a single session key (`agent:<id>:discord:channel:<groupName>`), serializing all messages through one queue.
- **What did NOT change:** DM routing (`dmScope`), thread isolation (threads stay separate unless explicitly listed), reply routing back to originating channel, session persistence format, and all behavior for ungrouped channels.
## Change Type (select all)
- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
## Scope (select all touched areas)
- [x] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [x] Integrations
- [ ] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
## Linked Issue/PR
- Closes #19929
## User-visible / Behavior Changes
- **New config option:** `session.channelGroups` — optional `Record<string, string[]>` under `session` config block.
- Channels listed in the same group resolve to a shared session key (`agent:<id>:discord:channel:<groupName>`) instead of individual per-channel keys.
- Queue serializes all grouped channel messages through one session, preventing concurrent write conflicts.
- Replies still route back to the originating channel (agent sees all channels' messages, responses go to correct channel).
- Ungrouped channels behave exactly as before (no default change).
- Config example:
```json
{
"session": {
"channelGroups": {
"studio-unified": [
"discord:channel:123456789",
"discord:channel:987654321",
"discord:channel:555555555"
]
}
}
}
```
## Security Impact (required)
- New permissions/capabilities? `No`
- Secrets/tokens handling changed? `No`
- New/changed network calls? `No`
- Command/tool execution surface changed? `No`
- Data access scope changed? `No` — channels grouped together share session history, but this is explicitly opt-in via config. No data is exposed without operator intent.
## Repro + Verification
### Environment
- OS: Ubuntu 24.04 (x64)
- Runtime/container: Node v25.5.0
- Model/provider: N/A (routing-layer change)
- Integration/channel: Discord
- Relevant config: `session.channelGroups` with 2+ Discord channels mapped to one group
### Steps
1. Configure `session.channelGroups` with two Discord channel IDs under one group name.
2. Send a message from Channel A to an agent.
3. Send a message from Channel B to the same agent.
### Expected
- Both messages land in the same session (`agent:<id>:discord:channel:<groupName>`).
- Messages from unlisted channels still get their own per-channel session.
### Actual
- Confirmed via unit tests
## Evidence
- [x] Failing test/log before + passing after
- [x] Trace/log snippets
**Tests (7 total, all passing):**
`src/routing/session-key.continuity.test.ts` (5 tests):
- Grouped channels resolve to shared session key
- Unlisted channels keep default per-channel key
- DM vs Channel distinction preserved with both `dmScope` modes
- Empty/invalid IDs handled safely
`src/config/config.session-channel-groups.validation.test.ts` (2 tests):
- Valid `channelGroups` config passes Zod validation
- Invalid shapes rejected by Zod schema
```
✓ src/routing/session-key.continuity.test.ts (5 tests) 12ms
✓ src/config/config.session-channel-groups.validation.test.ts (2 tests) 20ms
Test Files 2 passed (2)
Tests 7 passed (7)
```
Build: clean (`pnpm build` exit 0, no type errors).
## Human Verification (required)
- Verified scenarios: Grouped channels share session key; unlisted channels unaffected; DM routing unchanged; empty peer IDs handled.
- Edge cases checked: Empty group names skipped; non-array values in group ignored; channel identifiers matched with and without `channel:` prefix notation.
- What you did **not** verify: Full integration test with live Discord gateway
## Compatibility / Migration
- Backward compatible? `Yes` — `channelGroups` is optional and defaults to undefined (no behavior change).
- Config/env changes? `Yes` — new optional `session.channelGroups` key accepted in config.
- Migration needed? `No` — purely additive.
## Failure Recovery (if this breaks)
- How to disable/revert this change quickly: Remove `session.channelGroups` from config → all channels revert to per-channel sessions immediately.
- Files/config to restore: Only the `session` block in `openclaw.json`
- Known bad symptoms: Multiple channels unexpectedly sharing context → check `channelGroups` config for unintended channel listings.
## Risks and Mitigations
- **Risk:** Grouped channels accumulate session history faster (N channels × messages into one session), potentially hitting context limits sooner. *But it is intended by User*
- **Mitigation:** This is expected and intentional — same tradeoff as `session.scope: "global"` but scoped to selected channels only. Operators choose which channels to group.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Implements `session.channelGroups` — a new optional `Record<string, string[]>` config under `session` that maps a group name to provider-scoped channel identifiers. Channels listed in the same group resolve to a shared session key (`agent:<id>:<channel>:channel:<groupName>`) instead of individual per-channel keys, enabling context sharing across related channels (e.g., topic-based Discord channels like #planning, #dev, #art).
- Adds `resolveChannelGroupPeerId()` in `src/routing/session-key.ts` — the core resolution logic that matches channel IDs against configured groups, mirroring the existing `resolveLinkedPeerId` pattern for `identityLinks`
- Propagates `channelGroups` through all session key call sites: Discord message handling, threading, outbound session routing, WhatsApp broadcast, and the central `resolveAgentRoute` entry point
- Adds Zod validation schema, TypeScript types, UI labels, and help text for the new config field
- Includes 7 passing tests covering grouped/ungrouped channels and schema validation
- Backward compatible: `channelGroups` is optional and defaults to undefined, preserving existing per-channel session behavior
- All channel integrations (Telegram, Slack, Signal, etc.) are covered through the shared `buildBaseSessionKey` and `resolveAgentRoute` functions
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge — it is a purely additive, opt-in feature with no behavior change when unconfigured.
- The implementation is clean, well-tested, and follows established patterns (mirrors identityLinks). All session key call sites across every channel integration properly propagate channelGroups. The new config field is optional with no default behavior change. The core resolveChannelGroupPeerId logic is correct — verified by tracing through test cases. No security, logic, or syntax issues found.
- No files require special attention.
<sub>Last reviewed commit: b758c06</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#19403: feat(slack): add dm.threadSession option for per-message thread ses...
by Vasiliy-Bondarenko · 2026-02-17
81.1%
#9051: fix(sessions): respect dmScope config in CLI agent commands
by benleavett · 2026-02-04
80.6%
#15744: fix: allow cross-agent session path validation
by scottgl9 · 2026-02-13
79.4%
#6850: fix: support direct channel:account:peer format in session key extr...
by toboto · 2026-02-02
79.3%
#8134: feat(session): add per-group session reset configuration
by tylerliu612 · 2026-02-03
79.2%
#15176: fix(sessions): allow channel-routed session IDs and cross-agent paths
by cathrynlavery · 2026-02-13
78.8%
#7868: Default DM sessions to per-channel scope (avoid webchat contention)
by Smile232323 · 2026-02-03
77.6%
#23464: feat(synology-chat): add group/channel support
by druide67 · 2026-02-22
77.6%
#16061: fix(sessions): tolerate invalid sessionFile metadata
by haoyifan · 2026-02-14
77.5%
#19362: feat(discord): clean up sessions when channel is deleted
by chubes4 · 2026-02-17
77.5%