#15244: feat: Thread-session binding — route sub-agent I/O through platform threads
channel: msteams
channel: slack
app: web-ui
gateway
agents
stale
size: XL
Cluster:
Session Management Enhancements
## Summary
Implements thread-session binding for OpenClaw, allowing spawned sub-agents to have their I/O routed through platform threads (Slack, Discord, etc.).
Closes #15217
## What This Adds
### Phase 1: Core Registry
- `ThreadBinding` type and `ThreadBindingRegistry` class with bidirectional mapping (thread → sessions, session → thread)
- `buildThreadKey()` / `parseThreadKey()` for normalized thread identity
- Session store schema extended with optional `threadBinding` field
- Registry auto-rebuilds on session store load, syncs on save
### Phase 2: Spawn Integration + Inbound Routing
- `sessions_spawn` tool gains `threadBinding` parameter with two modes:
- `mode: "bind"` — bind to existing thread (requires `threadId`)
- `mode: "create"` — post initial message to channel, use response as thread root
- `resolveSessionKeyWithBinding()` — checks registry before falling back to existing suffix-based routing
- Slack inbound handler updated to use registry-aware routing
- Lifecycle helpers: `bindSessionToThread()`, `unbindSessionFromThread()`, `findSessionsByThread()`, `getSessionThreadBinding()`
### Phase 3: Output Delivery + Cross-Agent Communication
- Reply dispatcher checks `threadBinding` for delivery routing:
- `thread-only` — deliver to bound thread only
- `thread+announcer` — deliver to both thread and requester
- `announcer-only` — deliver to requester only
- `sessions_send` gains `threadKey` parameter for thread-scoped routing (fanout to all bound sessions)
- `sessions_broadcast` tool for broadcasting to all agents in a thread
- Graceful fallback when thread delivery fails
## Design Decisions
- **Dual routing**: Registry lookup (O(1)) first, falls back to existing suffix-based computed keys — fully backward compatible
- **Multi-session bindings**: Multiple agents can bind to one thread (parallel fanout)
- **UUID session keys**: Bound sessions use standard UUID keys, binding stored in session data (not key suffix)
- **Platform-agnostic**: Registry is channel-plugin agnostic; platform-specific thread creation in Phase 5
## Files Changed
| File | Change |
|------|--------|
| `src/config/thread-registry.ts` | NEW — Registry, types, lifecycle helpers |
| `src/config/sessions/types.ts` | `threadBinding` field on SessionEntry |
| `src/config/sessions/store.ts` | Registry rebuild on load, sync on save |
| `src/routing/session-key.ts` | `resolveSessionKeyWithBinding()` |
| `src/slack/monitor/message-handler/prepare.ts` | Registry-aware inbound routing |
| `src/agents/tools/sessions-spawn-tool.ts` | `threadBinding` parameter |
| `src/agents/tools/sessions-send-tool.ts` | `threadKey` parameter |
| `src/agents/tools/sessions-broadcast-tool.ts` | NEW — Thread broadcast tool |
| `src/auto-reply/reply/dispatch-from-config.ts` | Binding-aware delivery routing |
## Tests
- 45 new tests across registry, session key resolution, and lifecycle helpers
- All existing tests pass (373 total across 49 files)
- No type errors
## What's Next (not in this PR)
- **Phase 4**: Platform abstraction (`ThreadOperations` interface per channel plugin)
- **Phase 5**: Config schema additions, feature flag, documentation
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR adds a persistent thread↔session binding registry (`src/config/thread-registry.ts`) and wires it into session-store load/save so thread bindings can be looked up in O(1) at runtime. Routing is updated to prefer explicit bindings (`resolveSessionKeyWithBinding()`), with Slack inbound handling now consulting the registry before falling back to legacy suffix-based thread keys. It also extends session tools: `sessions_spawn` can bind or create a thread binding on spawn, `sessions_send` can fan out by `threadKey`, and a new `sessions_broadcast` tool broadcasts to all sessions bound to a thread.
Main issue to address before merge: reply delivery via `dispatchReplyFromConfig` gates thread routing on `threadBinding.to`, but the binding persisted by the registry/spawn path currently doesn’t set `to`, so thread-bound sessions will never actually route replies to the thread (they’ll continue using announcer/dispatcher behavior). Additionally, both thread fanout tools currently lose which session failed when a send rejects, making partial/error results non-actionable.
<h3>Confidence Score: 3/5</h3>
- This PR is close to mergeable but has a functional gap in thread-bound reply delivery that should be fixed first.
- Core registry + inbound routing changes are straightforward, but the new reply dispatcher thread-routing path is effectively disabled because persisted bindings don’t include the destination (`to`). That means the flagship feature (routing outputs to platform threads) won’t work as intended. Also, fanout tooling reports failures without identifying the failing session, which will complicate operations/debugging.
- src/auto-reply/reply/dispatch-from-config.ts, src/agents/tools/sessions-send-tool.ts, src/agents/tools/sessions-broadcast-tool.ts
<sub>Last reviewed commit: 37d3b29</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
#12075: feat(browser): session-aware context isolation for multi-agent brow...
by xiaoyaner0201 · 2026-02-08
78.8%
#19083: Slack: preserve per-thread context and consistent thread replies
by jkimbo · 2026-02-17
78.8%
#22982: fix: prevent stale threadId from routing subagent announces to wron...
by unboxed-ai · 2026-02-21
78.6%
#5935: fix(slack): persist thread starter body across thread messages
by thisischappy · 2026-02-01
77.4%
#16223: feat(slack): sticky thread routing — bypass @mention in active threads
by iamfuntime · 2026-02-14
77.4%
#10686: fix(slack): use thread-level sessions for channels to prevent conte...
by pablohrcarvalho · 2026-02-06
77.2%
#19403: feat(slack): add dm.threadSession option for per-message thread ses...
by Vasiliy-Bondarenko · 2026-02-17
76.7%
#22433: Slack: fix thread context loss after session reset
by stgarrity · 2026-02-21
75.8%
#9006: fix: streaming UI, session locks, routing performance, plugin sandb...
by facundollamas2007 · 2026-02-04
75.4%
#9181: feat(slack): add per-channel thread config override
by halbotley · 2026-02-05
75.4%