Skip to content

Commit a7929ab

Browse files
osolmazonutc
andauthored
Discord: thread bindings idle + max-age lifecycle (#27845) (thanks @osolmaz)
* refactor discord thread bindings to idle and max-age lifecycle * fix: migrate legacy thread binding expiry and reduce hot-path disk writes * refactor: remove remaining thread-binding ttl legacy paths * fix: harden thread-binding lifecycle persistence * Discord: fix thread binding types in message/reply paths * Infra: handle win32 unknown inode in file identity checks * Infra: relax win32 guarded-open identity checks * Config: migrate threadBindings ttlHours to idleHours * Revert "Infra: relax win32 guarded-open identity checks" This reverts commit de94126. * Revert "Infra: handle win32 unknown inode in file identity checks" This reverts commit 96fc5dd. * Discord: re-read live binding state before sweep unbind * fix: add changelog note for thread binding lifecycle update (#27845) (thanks @osolmaz) --------- Co-authored-by: Onur Solmaz <[email protected]>
1 parent 0fb7add commit a7929ab

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1650
-396
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
88

99
- Highlight: External Secrets Management introduces a full `openclaw secrets` workflow (`audit`, `configure`, `apply`, `reload`) with runtime snapshot activation, strict `secrets apply` target-path validation, safer migration scrubbing, ref-only auth-profile support, and dedicated docs. (#26155) Thanks @joshavant.
1010
- ACP/Thread-bound agents: make ACP agents first-class runtimes for thread sessions with `acp` spawn/send dispatch integration, acpx backend bridging, lifecycle controls, startup reconciliation, runtime cleanup, and coalesced thread replies. (#23580) thanks @osolmaz.
11+
- Discord/Thread bindings: replace fixed TTL lifecycle with inactivity (`idleHours`, default 24h) plus optional hard `maxAgeHours` lifecycle controls, and add `/session idle` + `/session max-age` commands for focused thread-bound sessions. (#27845) Thanks @osolmaz.
1112
- Agents/Routing CLI: add `openclaw agents bindings`, `openclaw agents bind`, and `openclaw agents unbind` for account-scoped route management, including channel-only to account-scoped binding upgrades, role-aware binding identity handling, plugin-resolved binding account IDs, and optional account-binding prompts in `openclaw channels add`. (#27195) thanks @gumadeiras.
1213
- Codex/WebSocket transport: make `openai-codex` WebSocket-first by default (`transport: "auto"` with SSE fallback), keep explicit per-model/runtime transport overrides, and add regression coverage + docs for transport selection.
1314
- Onboarding/Plugins: let channel plugins own interactive onboarding flows with optional `configureInteractive` and `configureWhenConfigured` hooks while preserving the generic fallback path. (#27191) thanks @gumadeiras.

docs/channels/discord.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,8 @@ Default slash command settings:
642642
- `/focus <target>` bind current/new thread to a subagent/session target
643643
- `/unfocus` remove current thread binding
644644
- `/agents` show active runs and binding state
645-
- `/session ttl <duration|off>` inspect/update auto-unfocus TTL for focused bindings
645+
- `/session idle <duration|off>` inspect/update inactivity auto-unfocus for focused bindings
646+
- `/session max-age <duration|off>` inspect/update hard max age for focused bindings
646647

647648
Config:
648649

@@ -651,14 +652,16 @@ Default slash command settings:
651652
session: {
652653
threadBindings: {
653654
enabled: true,
654-
ttlHours: 24,
655+
idleHours: 24,
656+
maxAgeHours: 0,
655657
},
656658
},
657659
channels: {
658660
discord: {
659661
threadBindings: {
660662
enabled: true,
661-
ttlHours: 24,
663+
idleHours: 24,
664+
maxAgeHours: 0,
662665
spawnSubagentSessions: false, // opt-in
663666
},
664667
},

docs/experiments/plans/acp-thread-bound-agents.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ Add independent ACP dispatch kill switch:
638638

639639
- `/focus <sessionKey>` continues to support ACP targets
640640
- `/unfocus` keeps current semantics
641-
- `/session ttl` remains the top level TTL override
641+
- `/session idle` and `/session max-age` replace the old TTL override
642642

643643
## Phased rollout
644644

docs/gateway/configuration-reference.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,8 @@ WhatsApp runs through the gateway's web channel (Baileys Web). It starts automat
268268
},
269269
threadBindings: {
270270
enabled: true,
271-
ttlHours: 24,
271+
idleHours: 24,
272+
maxAgeHours: 0,
272273
spawnSubagentSessions: false, // opt-in for sessions_spawn({ thread: true })
273274
},
274275
voice: {
@@ -303,8 +304,9 @@ WhatsApp runs through the gateway's web channel (Baileys Web). It starts automat
303304
- Bot-authored messages are ignored by default. `allowBots: true` enables them (own messages still filtered).
304305
- `maxLinesPerMessage` (default 17) splits tall messages even when under 2000 chars.
305306
- `channels.discord.threadBindings` controls Discord thread-bound routing:
306-
- `enabled`: Discord override for thread-bound session features (`/focus`, `/unfocus`, `/agents`, `/session ttl`, and bound delivery/routing)
307-
- `ttlHours`: Discord override for auto-unfocus TTL (`0` disables)
307+
- `enabled`: Discord override for thread-bound session features (`/focus`, `/unfocus`, `/agents`, `/session idle`, `/session max-age`, and bound delivery/routing)
308+
- `idleHours`: Discord override for inactivity auto-unfocus in hours (`0` disables)
309+
- `maxAgeHours`: Discord override for hard max age in hours (`0` disables)
308310
- `spawnSubagentSessions`: opt-in switch for `sessions_spawn({ thread: true })` auto thread creation/binding
309311
- `channels.discord.ui.components.accentColor` sets the accent color for Discord components v2 containers.
310312
- `channels.discord.voice` enables Discord voice channel conversations and optional auto-join + TTS overrides.
@@ -1374,7 +1376,8 @@ See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for preceden
13741376
},
13751377
threadBindings: {
13761378
enabled: true,
1377-
ttlHours: 24, // default auto-unfocus TTL for thread-bound sessions (0 disables)
1379+
idleHours: 24, // default inactivity auto-unfocus in hours (`0` disables)
1380+
maxAgeHours: 0, // default hard max age in hours (`0` disables)
13781381
},
13791382
mainKey: "main", // legacy (runtime always uses "main")
13801383
agentToAgent: { maxPingPongTurns: 5 },
@@ -1411,7 +1414,8 @@ See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for preceden
14111414
- `highWaterBytes`: optional target after budget cleanup. Defaults to `80%` of `maxDiskBytes`.
14121415
- **`threadBindings`**: global defaults for thread-bound session features.
14131416
- `enabled`: master default switch (providers can override; Discord uses `channels.discord.threadBindings.enabled`)
1414-
- `ttlHours`: default auto-unfocus TTL in hours (`0` disables; providers can override)
1417+
- `idleHours`: default inactivity auto-unfocus in hours (`0` disables; providers can override)
1418+
- `maxAgeHours`: default hard max age in hours (`0` disables; providers can override)
14151419

14161420
</Accordion>
14171421

docs/gateway/configuration.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@ When validation fails:
184184
dmScope: "per-channel-peer", // recommended for multi-user
185185
threadBindings: {
186186
enabled: true,
187-
ttlHours: 24,
187+
idleHours: 24,
188+
maxAgeHours: 0,
188189
},
189190
reset: {
190191
mode: "daily",
@@ -196,7 +197,7 @@ When validation fails:
196197
```
197198

198199
- `dmScope`: `main` (shared) | `per-peer` | `per-channel-peer` | `per-account-channel-peer`
199-
- `threadBindings`: global defaults for thread-bound session routing (Discord supports `/focus`, `/unfocus`, `/agents`, and `/session ttl`).
200+
- `threadBindings`: global defaults for thread-bound session routing (Discord supports `/focus`, `/unfocus`, `/agents`, `/session idle`, and `/session max-age`).
200201
- See [Session Management](/concepts/session) for scoping, identity links, and send policy.
201202
- See [full reference](/gateway/configuration-reference#session) for all fields.
202203

docs/help/faq.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,13 +1050,13 @@ Basic flow:
10501050
- Spawn with `sessions_spawn` using `thread: true` (and optionally `mode: "session"` for persistent follow-up).
10511051
- Or manually bind with `/focus <target>`.
10521052
- Use `/agents` to inspect binding state.
1053-
- Use `/session ttl <duration|off>` to control auto-unfocus.
1053+
- Use `/session idle <duration|off>` and `/session max-age <duration|off>` to control auto-unfocus.
10541054
- Use `/unfocus` to detach the thread.
10551055

10561056
Required config:
10571057

1058-
- Global defaults: `session.threadBindings.enabled`, `session.threadBindings.ttlHours`.
1059-
- Discord overrides: `channels.discord.threadBindings.enabled`, `channels.discord.threadBindings.ttlHours`.
1058+
- Global defaults: `session.threadBindings.enabled`, `session.threadBindings.idleHours`, `session.threadBindings.maxAgeHours`.
1059+
- Discord overrides: `channels.discord.threadBindings.enabled`, `channels.discord.threadBindings.idleHours`, `channels.discord.threadBindings.maxAgeHours`.
10601060
- Auto-bind on spawn: set `channels.discord.threadBindings.spawnSubagentSessions: true`.
10611061

10621062
Docs: [Sub-agents](/tools/subagents), [Discord](/channels/discord), [Configuration Reference](/gateway/configuration-reference), [Slash commands](/tools/slash-commands).

docs/tools/acp-agents.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ When thread bindings are enabled for a channel adapter, ACP sessions can be boun
6868
- OpenClaw binds a thread to a target ACP session.
6969
- Follow-up messages in that thread route to the bound ACP session.
7070
- ACP output is delivered back to the same thread.
71-
- Unfocus/close/archive/TTL expiry removes the binding.
71+
- Unfocus/close/archive/idle-timeout or max-age expiry removes the binding.
7272

7373
Thread binding support is adapter-specific. If the active channel adapter does not support thread bindings, OpenClaw returns a clear unsupported/unavailable message.
7474

@@ -272,7 +272,8 @@ Thread binding config is channel-adapter specific. Example for Discord:
272272
session: {
273273
threadBindings: {
274274
enabled: true,
275-
ttlHours: 24,
275+
idleHours: 24,
276+
maxAgeHours: 0,
276277
},
277278
},
278279
channels: {

docs/tools/slash-commands.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ Text + native (when enabled):
7878
- `/context [list|detail|json]` (explain “context”; `detail` shows per-file + per-tool + per-skill + system prompt size)
7979
- `/export-session [path]` (alias: `/export`) (export current session to HTML with full system prompt)
8080
- `/whoami` (show your sender id; alias: `/id`)
81-
- `/session ttl <duration|off>` (manage session-level settings, such as TTL)
81+
- `/session idle <duration|off>` (manage inactivity auto-unfocus for focused thread bindings)
82+
- `/session max-age <duration|off>` (manage hard max-age auto-unfocus for focused thread bindings)
8283
- `/subagents list|kill|log|info|send|steer|spawn` (inspect, control, or spawn sub-agent runs for the current session)
8384
- `/acp spawn|cancel|steer|close|status|set-mode|set|cwd|permissions|timeout|model|reset-options|doctor|install|sessions` (inspect and control ACP runtime sessions)
8485
- `/agents` (list thread-bound agents for this session)
@@ -125,7 +126,7 @@ Notes:
125126
- `/usage` controls the per-response usage footer; `/usage cost` prints a local cost summary from OpenClaw session logs.
126127
- `/restart` is enabled by default; set `commands.restart: false` to disable it.
127128
- Discord-only native command: `/vc join|leave|status` controls voice channels (requires `channels.discord.voice` and native commands; not available as text).
128-
- Discord thread-binding commands (`/focus`, `/unfocus`, `/agents`, `/session ttl`) require effective thread bindings to be enabled (`session.threadBindings.enabled` and/or `channels.discord.threadBindings.enabled`).
129+
- Discord thread-binding commands (`/focus`, `/unfocus`, `/agents`, `/session idle`, `/session max-age`) require effective thread bindings to be enabled (`session.threadBindings.enabled` and/or `channels.discord.threadBindings.enabled`).
129130
- ACP command reference and runtime behavior: [ACP Agents](/tools/acp-agents).
130131
- `/verbose` is meant for debugging and extra visibility; keep it **off** in normal use.
131132
- Tool failure summaries are still shown when relevant, but detailed failure text is only included when `/verbose` is `on` or `full`.

docs/tools/subagents.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ These commands work on channels that support persistent thread bindings. See **T
3030
- `/focus <subagent-label|session-key|session-id|session-label>`
3131
- `/unfocus`
3232
- `/agents`
33-
- `/session ttl <duration|off>`
33+
- `/session idle <duration|off>`
34+
- `/session max-age <duration|off>`
3435

3536
`/subagents info` shows run metadata (status, timestamps, session id, transcript path, cleanup).
3637

@@ -95,26 +96,26 @@ When thread bindings are enabled for a channel, a sub-agent can stay bound to a
9596

9697
### Thread supporting channels
9798

98-
- Discord (currently the only supported channel): supports persistent thread-bound subagent sessions (`sessions_spawn` with `thread: true`), manual thread controls (`/focus`, `/unfocus`, `/agents`, `/session ttl`), and adapter keys `channels.discord.threadBindings.enabled`, `channels.discord.threadBindings.ttlHours`, and `channels.discord.threadBindings.spawnSubagentSessions`.
99+
- Discord (currently the only supported channel): supports persistent thread-bound subagent sessions (`sessions_spawn` with `thread: true`), manual thread controls (`/focus`, `/unfocus`, `/agents`, `/session idle`, `/session max-age`), and adapter keys `channels.discord.threadBindings.enabled`, `channels.discord.threadBindings.idleHours`, `channels.discord.threadBindings.maxAgeHours`, and `channels.discord.threadBindings.spawnSubagentSessions`.
99100

100101
Quick flow:
101102

102103
1. Spawn with `sessions_spawn` using `thread: true` (and optionally `mode: "session"`).
103104
2. OpenClaw creates or binds a thread to that session target in the active channel.
104105
3. Replies and follow-up messages in that thread route to the bound session.
105-
4. Use `/session ttl` to inspect/update auto-unfocus TTL.
106+
4. Use `/session idle` to inspect/update inactivity auto-unfocus and `/session max-age` to control the hard cap.
106107
5. Use `/unfocus` to detach manually.
107108

108109
Manual controls:
109110

110111
- `/focus <target>` binds the current thread (or creates one) to a sub-agent/session target.
111112
- `/unfocus` removes the binding for the current bound thread.
112113
- `/agents` lists active runs and binding state (`thread:<id>` or `unbound`).
113-
- `/session ttl` only works for focused bound threads.
114+
- `/session idle` and `/session max-age` only work for focused bound threads.
114115

115116
Config switches:
116117

117-
- Global default: `session.threadBindings.enabled`, `session.threadBindings.ttlHours`
118+
- Global default: `session.threadBindings.enabled`, `session.threadBindings.idleHours`, `session.threadBindings.maxAgeHours`
118119
- Channel override and spawn auto-bind keys are adapter-specific. See **Thread supporting channels** above.
119120

120121
See [Configuration Reference](/gateway/configuration-reference) and [Slash commands](/tools/slash-commands) for current adapter details.

src/agents/acp-spawn.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {
1717
import {
1818
formatThreadBindingDisabledError,
1919
formatThreadBindingSpawnDisabledError,
20-
resolveThreadBindingSessionTtlMsForChannel,
20+
resolveThreadBindingIdleTimeoutMsForChannel,
21+
resolveThreadBindingMaxAgeMsForChannel,
2122
resolveThreadBindingSpawnPolicy,
2223
} from "../channels/thread-bindings-policy.js";
2324
import { loadConfig } from "../config/config.js";
@@ -329,7 +330,12 @@ export async function spawnAcpDirect(
329330
introText: resolveThreadBindingIntroText({
330331
agentId: targetAgentId,
331332
label: params.label || undefined,
332-
sessionTtlMs: resolveThreadBindingSessionTtlMsForChannel({
333+
idleTimeoutMs: resolveThreadBindingIdleTimeoutMsForChannel({
334+
cfg,
335+
channel: preparedBinding.channel,
336+
accountId: preparedBinding.accountId,
337+
}),
338+
maxAgeMs: resolveThreadBindingMaxAgeMsForChannel({
333339
cfg,
334340
channel: preparedBinding.channel,
335341
accountId: preparedBinding.accountId,

0 commit comments

Comments
 (0)