diff --git a/docs/channels/discord.md b/docs/channels/discord.md index 5f789a382a6..d725b5c2edd 100644 --- a/docs/channels/discord.md +++ b/docs/channels/discord.md @@ -627,6 +627,49 @@ Default slash command settings: + + Discord can bind a thread to a session target so follow-up messages in that thread keep routing to the same session (including subagent sessions). + + Commands: + + - `/focus ` bind current/new thread to a subagent/session target + - `/unfocus` remove current thread binding + - `/agents` show active runs and binding state + - `/session ttl ` inspect/update auto-unfocus TTL for focused bindings + + Config: + +```json5 +{ + session: { + threadBindings: { + enabled: true, + ttlHours: 24, + }, + }, + channels: { + discord: { + threadBindings: { + enabled: true, + ttlHours: 24, + spawnSubagentSessions: false, // opt-in + }, + }, + }, +} +``` + + Notes: + + - `session.threadBindings.*` sets global defaults. + - `channels.discord.threadBindings.*` overrides Discord behavior. + - `spawnSubagentSessions` must be true to auto-create/bind threads for `sessions_spawn({ thread: true })`. + - If thread bindings are disabled for an account, `/focus` and related thread binding operations are unavailable. + + See [Sub-agents](/tools/subagents) and [Configuration Reference](/gateway/configuration-reference). + + + Per-guild reaction notification mode: diff --git a/docs/concepts/session-tool.md b/docs/concepts/session-tool.md index b44d892be54..ebac95dbe55 100644 --- a/docs/concepts/session-tool.md +++ b/docs/concepts/session-tool.md @@ -151,7 +151,10 @@ Parameters: - `label?` (optional; used for logs/UI) - `agentId?` (optional; spawn under another agent id if allowed) - `model?` (optional; overrides the sub-agent model; invalid values error) +- `thinking?` (optional; overrides thinking level for the sub-agent run) - `runTimeoutSeconds?` (default 0; when set, aborts the sub-agent run after N seconds) +- `thread?` (default false; request thread-bound routing for this spawn when supported by the channel/plugin) +- `mode?` (`run|session`; defaults to `run`, but defaults to `session` when `thread=true`; `mode="session"` requires `thread=true`) - `cleanup?` (`delete|keep`, default `keep`) Allowlist: @@ -168,6 +171,7 @@ Behavior: - Sub-agents default to the full tool set **minus session tools** (configurable via `tools.subagents.tools`). - Sub-agents are not allowed to call `sessions_spawn` (no sub-agent → sub-agent spawning). - Always non-blocking: returns `{ status: "accepted", runId, childSessionKey }` immediately. +- With `thread=true`, channel plugins can bind delivery/routing to a thread target (Discord support is controlled by `session.threadBindings.*` and `channels.discord.threadBindings.*`). - After completion, OpenClaw runs a sub-agent **announce step** and posts the result to the requester chat channel. - If the assistant final reply is empty, the latest `toolResult` from sub-agent history is included as `Result`. - Reply exactly `ANNOUNCE_SKIP` during the announce step to stay silent. diff --git a/docs/experiments/plans/thread-bound-subagents.md b/docs/experiments/plans/thread-bound-subagents.md deleted file mode 100644 index 8663ab55efc..00000000000 --- a/docs/experiments/plans/thread-bound-subagents.md +++ /dev/null @@ -1,338 +0,0 @@ ---- -summary: "Discord thread bound subagent sessions with plugin lifecycle hooks, routing, and config kill switches" -owner: "onutc" -status: "implemented" -last_updated: "2026-02-21" -title: "Thread Bound Subagents" ---- - -# Thread Bound Subagents - -## Overview - -This feature lets users interact with spawned subagents directly inside Discord threads. - -Instead of only waiting for a completion summary in the parent session, users can move into a dedicated thread that routes messages to the spawned subagent session. Replies are sent in-thread with a thread bound persona. - -The implementation is split between channel agnostic core lifecycle hooks and Discord specific extension behavior. - -## Goals - -- Allow direct thread conversation with a spawned subagent session. -- Keep default subagent orchestration channel agnostic. -- Support both automatic thread creation on spawn and manual focus controls. -- Provide predictable cleanup on completion, kill, timeout, and thread lifecycle changes. -- Keep behavior configurable with global defaults plus channel and account overrides. - -## Out of scope - -- New ACP protocol features. -- Non Discord thread binding implementations in this document. -- New bot accounts or app level Discord identity changes. - -## What shipped - -- `sessions_spawn` supports `thread: true` and `mode: "run" | "session"`. -- Spawn flow supports persistent thread bound sessions. -- Discord thread binding manager supports bind, unbind, TTL sweep, and persistence. -- Plugin hook lifecycle for subagents: - - `subagent_spawning` - - `subagent_spawned` - - `subagent_delivery_target` - - `subagent_ended` -- Discord extension implements thread auto bind, delivery target override, and unbind on end. -- Text commands for manual control: - - `/focus` - - `/unfocus` - - `/agents` - - `/session ttl` -- Global and Discord scoped enablement and TTL controls, including a global kill switch. - -## Core concepts - -### Spawn modes - -- `mode: "run"` - - one task lifecycle - - completion announcement flow -- `mode: "session"` - - persistent thread bound session - - supports follow up user messages in thread - -Default mode behavior: - -- if `thread: true` and mode omitted, mode defaults to `"session"` -- otherwise mode defaults to `"run"` - -Constraint: - -- `mode: "session"` requires `thread: true` - -### Thread binding target model - -Bindings are generic targets, not only subagents. - -- `targetKind: "subagent" | "acp"` -- `targetSessionKey: string` - -This allows the same routing primitive to support ACP/session bindings as well. - -### Thread binding manager - -The manager is responsible for: - -- binding or creating threads for a session target -- unbinding by thread or by target session -- managing webhook reuse and recent unbound webhook echo suppression -- TTL based unbind and stale thread cleanup -- persistence load and save - -## Architecture - -### Core and extension boundary - -Core (`src/agents/*`) does not directly depend on Discord routing internals. - -Core emits lifecycle intent through plugin hooks. - -Discord extension (`extensions/discord/src/subagent-hooks.ts`) implements Discord specific behavior: - -- pre spawn thread bind preparation -- completion delivery target override to bound thread -- unbind on subagent end - -### Plugin hook flow - -1. `subagent_spawning` - - before run starts - - can block spawn with `status: "error"` - - used to prepare thread binding when `thread: true` -2. `subagent_spawned` - - post run registration event -3. `subagent_delivery_target` - - completion routing override hook - - can redirect completion delivery to bound Discord thread origin -4. `subagent_ended` - - cleanup and unbind signal - -### Account ID normalization contract - -Thread binding and routing state must use one canonical account id abstraction. - -Specification: - -- Introduce a shared account id module (proposed: `src/routing/account-id.ts`) and stop defining local normalizers. -- Expose two explicit helpers: - - `normalizeAccountId(value): string` - - returns canonical, defaulted id (current default is `default`) - - use for map keys, manager registration and lookup, persistence keys, routing keys - - `normalizeOptionalAccountId(value): string | undefined` - - returns canonical id when present, `undefined` when absent - - use for inbound optional context fields and merge logic -- Do not implement ad hoc account normalization in feature modules. - - This includes `trim`, `toLowerCase`, or defaulting logic in local helper functions. -- Any map keyed by account id must only accept canonical ids from shared helpers. -- Hook payloads and delivery context should carry raw optional account ids, and normalize at module boundaries only. - -Migration guardrails: - -- Replace duplicate normalizers in routing, reply payload, command context, and provider helpers with shared helpers. -- Add contract tests that assert identical normalization behavior across: - - route resolution - - thread binding manager lookup - - reply delivery target filtering - - command run context merge - -### Persistence and state - -Binding state path: - -- `${stateDir}/discord/thread-bindings.json` - -Record shape contains: - -- account, channel, thread -- target kind and target session key -- agent label metadata -- webhook id/token -- boundBy, boundAt, expiresAt - -State is stored on `globalThis` to keep one shared registry across ESM and Jiti loader paths. - -## Configuration - -### Effective precedence - -For Discord thread binding options, account override wins, then channel, then global session default, then built in fallback. - -- account: `channels.discord.accounts..threadBindings.` -- channel: `channels.discord.threadBindings.` -- global: `session.threadBindings.` - -### Keys - -| Key | Scope | Default | Notes | -| ------------------------------------------------------- | --------------- | --------------- | ----------------------------------------- | -| `session.threadBindings.enabled` | global | `true` | master default kill switch | -| `session.threadBindings.ttlHours` | global | `24` | default auto unfocus TTL | -| `channels.discord.threadBindings.enabled` | channel/account | inherits global | Discord override kill switch | -| `channels.discord.threadBindings.ttlHours` | channel/account | inherits global | Discord TTL override | -| `channels.discord.threadBindings.spawnSubagentSessions` | channel/account | `false` | opt in for `thread: true` spawn auto bind | - -### Runtime effect of enable switch - -When effective `enabled` is false for a Discord account: - -- provider creates a noop thread binding manager for runtime wiring -- no real manager is registered for lookup by account id -- inbound bound thread routing is effectively disabled -- completion routing overrides do not resolve bound thread origins -- `/focus`, `/unfocus`, and thread binding specific operations report unavailable -- `thread: true` spawn path returns actionable error from Discord hook layer - -## Flow and behavior - -### Spawn with `thread: true` - -1. Spawn validates mode and permissions. -2. `subagent_spawning` hook runs. -3. Discord extension checks effective flags: - - thread bindings enabled - - `spawnSubagentSessions` enabled -4. Extension attempts auto bind and thread creation. -5. If bind fails: - - spawn returns error - - provisional child session is deleted -6. If bind succeeds: - - child run starts - - run is registered with spawn mode - -### Manual focus and unfocus - -- `/focus ` - - Discord only - - resolves subagent or session target - - binds current or created thread to target session -- `/unfocus` - - Discord thread only - - unbinds current thread - -### Inbound routing - -- Discord preflight checks current thread id against thread binding manager. -- If bound, effective session routing uses bound target session key. -- If not bound, normal routing path is used. - -### Outbound routing - -- Reply delivery checks whether current session has thread bindings. -- Bound sessions deliver to thread via webhook aware path. -- Unbound sessions use normal bot delivery. - -### Completion routing - -- Core completion flow calls `subagent_delivery_target`. -- Discord extension returns bound thread origin when it can resolve one. -- Core merges hook origin with requester origin and delivers completion. - -### Cleanup - -Cleanup occurs on: - -- completion -- error or timeout completion path -- kill and terminate paths -- TTL expiration -- archived or deleted thread probes -- manual `/unfocus` - -Cleanup behavior includes unbind and optional farewell messaging. - -## Commands and user UX - -| Command | Purpose | -| ---------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------- | --------------- | ------------------------------------------- | -| `/subagents spawn [--model] [--thinking]` | spawn subagent; may be thread bound when `thread: true` path is used | -| `/focus ` | manually bind thread to subagent or session | -| `/unfocus` | remove binding from current thread | -| `/agents` | list active agents and binding state | -| `/session ttl ` | update TTL for focused thread binding | - -Notes: - -- `/session ttl` is currently Discord thread focused behavior. -- Thread intro and farewell text are generated by thread binding message helpers. - -## Failure handling and safety - -- Spawn returns explicit errors when thread binding cannot be prepared. -- Spawn failure after provisional bind attempts best effort unbind and session delete. -- Completion logic prevents duplicate ended hook emission. -- Retry and expiry guards prevent infinite completion announce retry loops. -- Webhook echo suppression avoids unbound webhook messages being reprocessed as inbound turns. - -## Module map - -### Core orchestration - -- `src/agents/subagent-spawn.ts` -- `src/agents/subagent-announce.ts` -- `src/agents/subagent-registry.ts` -- `src/agents/subagent-registry-cleanup.ts` -- `src/agents/subagent-registry-completion.ts` - -### Discord runtime - -- `src/discord/monitor/provider.ts` -- `src/discord/monitor/thread-bindings.manager.ts` -- `src/discord/monitor/thread-bindings.state.ts` -- `src/discord/monitor/thread-bindings.lifecycle.ts` -- `src/discord/monitor/thread-bindings.messages.ts` -- `src/discord/monitor/message-handler.preflight.ts` -- `src/discord/monitor/message-handler.process.ts` -- `src/discord/monitor/reply-delivery.ts` - -### Plugin hooks and extension - -- `src/plugins/types.ts` -- `src/plugins/hooks.ts` -- `extensions/discord/src/subagent-hooks.ts` - -### Config and schema - -- `src/config/types.base.ts` -- `src/config/types.discord.ts` -- `src/config/zod-schema.session.ts` -- `src/config/zod-schema.providers-core.ts` -- `src/config/schema.help.ts` -- `src/config/schema.labels.ts` - -## Test coverage highlights - -- `extensions/discord/src/subagent-hooks.test.ts` -- `src/discord/monitor/thread-bindings.ttl.test.ts` -- `src/discord/monitor/thread-bindings.shared-state.test.ts` -- `src/discord/monitor/reply-delivery.test.ts` -- `src/discord/monitor/message-handler.preflight.test.ts` -- `src/discord/monitor/message-handler.process.test.ts` -- `src/auto-reply/reply/commands-subagents-focus.test.ts` -- `src/auto-reply/reply/commands-session-ttl.test.ts` -- `src/agents/subagent-registry.steer-restart.test.ts` -- `src/agents/subagent-registry-completion.test.ts` - -## Operational summary - -- Use `session.threadBindings.enabled` as the global kill switch default. -- Use `channels.discord.threadBindings.enabled` and account overrides for selective enablement. -- Keep `spawnSubagentSessions` opt in for thread auto spawn behavior. -- Use TTL settings for automatic unfocus policy control. - -This model keeps subagent lifecycle orchestration generic while giving Discord a full thread bound interaction path. - -## Related plan - -For channel agnostic SessionBinding architecture and scoped iteration planning, see: - -- `docs/experiments/plans/session-binding-channel-agnostic.md` - -ACP remains a next step in that plan and is intentionally not implemented in this shipped Discord thread-bound flow. diff --git a/docs/gateway/configuration-reference.md b/docs/gateway/configuration-reference.md index 3f25baf6380..b11ea7a37aa 100644 --- a/docs/gateway/configuration-reference.md +++ b/docs/gateway/configuration-reference.md @@ -235,6 +235,11 @@ WhatsApp runs through the gateway's web channel (Baileys Web). It starts automat accentColor: "#5865F2", }, }, + threadBindings: { + enabled: true, + ttlHours: 24, + spawnSubagentSessions: false, // opt-in for sessions_spawn({ thread: true }) + }, voice: { enabled: true, autoJoin: [ @@ -264,6 +269,10 @@ WhatsApp runs through the gateway's web channel (Baileys Web). It starts automat - Guild slugs are lowercase with spaces replaced by `-`; channel keys use the slugged name (no `#`). Prefer guild IDs. - Bot-authored messages are ignored by default. `allowBots: true` enables them (own messages still filtered). - `maxLinesPerMessage` (default 17) splits tall messages even when under 2000 chars. +- `channels.discord.threadBindings` controls Discord thread-bound routing: + - `enabled`: Discord override for thread-bound session features (`/focus`, `/unfocus`, `/agents`, `/session ttl`, and bound delivery/routing) + - `ttlHours`: Discord override for auto-unfocus TTL (`0` disables) + - `spawnSubagentSessions`: opt-in switch for `sessions_spawn({ thread: true })` auto thread creation/binding - `channels.discord.ui.components.accentColor` sets the accent color for Discord components v2 containers. - `channels.discord.voice` enables Discord voice channel conversations and optional auto-join + TTS overrides. - `channels.discord.streaming` is the canonical stream mode key. Legacy `streamMode` and boolean `streaming` values are auto-migrated. @@ -1222,6 +1231,10 @@ See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for preceden maxEntries: 500, rotateBytes: "10mb", }, + threadBindings: { + enabled: true, + ttlHours: 24, // default auto-unfocus TTL for thread-bound sessions (0 disables) + }, mainKey: "main", // legacy (runtime always uses "main") agentToAgent: { maxPingPongTurns: 5 }, sendPolicy: { @@ -1245,6 +1258,9 @@ See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for preceden - **`mainKey`**: legacy field. Runtime now always uses `"main"` for the main direct-chat bucket. - **`sendPolicy`**: match by `channel`, `chatType` (`direct|group|channel`, with legacy `dm` alias), `keyPrefix`, or `rawKeyPrefix`. First deny wins. - **`maintenance`**: `warn` warns the active session on eviction; `enforce` applies pruning and rotation. +- **`threadBindings`**: global defaults for thread-bound session features. + - `enabled`: master default switch (providers can override; Discord uses `channels.discord.threadBindings.enabled`) + - `ttlHours`: default auto-unfocus TTL in hours (`0` disables; providers can override) diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index bdc1d5b1a85..e367b4caf0d 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -182,6 +182,10 @@ When validation fails: { session: { dmScope: "per-channel-peer", // recommended for multi-user + threadBindings: { + enabled: true, + ttlHours: 24, + }, reset: { mode: "daily", atHour: 4, @@ -192,6 +196,7 @@ When validation fails: ``` - `dmScope`: `main` (shared) | `per-peer` | `per-channel-peer` | `per-account-channel-peer` + - `threadBindings`: global defaults for thread-bound session routing (Discord supports `/focus`, `/unfocus`, `/agents`, and `/session ttl`). - See [Session Management](/concepts/session) for scoping, identity links, and send policy. - See [full reference](/gateway/configuration-reference#session) for all fields. diff --git a/docs/help/faq.md b/docs/help/faq.md index 5b19415165b..e60329e86c6 100644 --- a/docs/help/faq.md +++ b/docs/help/faq.md @@ -1038,6 +1038,26 @@ cheaper model for sub-agents via `agents.defaults.subagents.model`. Docs: [Sub-agents](/tools/subagents). +### How do thread-bound subagent sessions work on Discord + +Use thread bindings. You can bind a Discord thread to a subagent or session target so follow-up messages in that thread stay on that bound session. + +Basic flow: + +- Spawn with `sessions_spawn` using `thread: true` (and optionally `mode: "session"` for persistent follow-up). +- Or manually bind with `/focus `. +- Use `/agents` to inspect binding state. +- Use `/session ttl ` to control auto-unfocus. +- Use `/unfocus` to detach the thread. + +Required config: + +- Global defaults: `session.threadBindings.enabled`, `session.threadBindings.ttlHours`. +- Discord overrides: `channels.discord.threadBindings.enabled`, `channels.discord.threadBindings.ttlHours`. +- Auto-bind on spawn: set `channels.discord.threadBindings.spawnSubagentSessions: true`. + +Docs: [Sub-agents](/tools/subagents), [Discord](/channels/discord), [Configuration Reference](/gateway/configuration-reference), [Slash commands](/tools/slash-commands). + ### Cron or reminders do not fire What should I check Cron runs inside the Gateway process. If the Gateway is not running continuously, diff --git a/docs/tools/index.md b/docs/tools/index.md index 85405633096..88b2ee6bccd 100644 --- a/docs/tools/index.md +++ b/docs/tools/index.md @@ -464,7 +464,7 @@ Core parameters: - `sessions_list`: `kinds?`, `limit?`, `activeMinutes?`, `messageLimit?` (0 = none) - `sessions_history`: `sessionKey` (or `sessionId`), `limit?`, `includeTools?` - `sessions_send`: `sessionKey` (or `sessionId`), `message`, `timeoutSeconds?` (0 = fire-and-forget) -- `sessions_spawn`: `task`, `label?`, `agentId?`, `model?`, `runTimeoutSeconds?`, `cleanup?` +- `sessions_spawn`: `task`, `label?`, `agentId?`, `model?`, `thinking?`, `runTimeoutSeconds?`, `thread?`, `mode?`, `cleanup?` - `session_status`: `sessionKey?` (default current; accepts `sessionId`), `model?` (`default` clears override) Notes: @@ -475,6 +475,10 @@ Notes: - `sessions_send` waits for final completion when `timeoutSeconds > 0`. - Delivery/announce happens after completion and is best-effort; `status: "ok"` confirms the agent run finished, not that the announce was delivered. - `sessions_spawn` starts a sub-agent run and posts an announce reply back to the requester chat. + - Supports one-shot mode (`mode: "run"`) and persistent thread-bound mode (`mode: "session"` with `thread: true`). + - If `thread: true` and `mode` is omitted, mode defaults to `session`. + - `mode: "session"` requires `thread: true`. + - Discord thread-bound flows depend on `session.threadBindings.*` and `channels.discord.threadBindings.*`. - Reply format includes `Status`, `Result`, and compact stats. - `Result` is the assistant completion text; if missing, the latest `toolResult` is used as fallback. - Manual completion-mode spawns send directly first, with queue fallback and retry on transient failures (`status: "ok"` means run finished, not that announce delivered). diff --git a/docs/tools/slash-commands.md b/docs/tools/slash-commands.md index 4d58fb5a437..7d9bb616642 100644 --- a/docs/tools/slash-commands.md +++ b/docs/tools/slash-commands.md @@ -124,6 +124,7 @@ Notes: - `/usage` controls the per-response usage footer; `/usage cost` prints a local cost summary from OpenClaw session logs. - `/restart` is enabled by default; set `commands.restart: false` to disable it. - Discord-only native command: `/vc join|leave|status` controls voice channels (requires `channels.discord.voice` and native commands; not available as text). +- 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`). - `/verbose` is meant for debugging and extra visibility; keep it **off** in normal use. - `/reasoning` (and `/verbose`) are risky in group settings: they may reveal internal reasoning or tool output you did not intend to expose. Prefer leaving them off, especially in group chats. - **Fast path:** command-only messages from allowlisted senders are handled immediately (bypass queue + model). diff --git a/docs/tools/subagents.md b/docs/tools/subagents.md index 3022d551921..5c2549e4426 100644 --- a/docs/tools/subagents.md +++ b/docs/tools/subagents.md @@ -3,6 +3,7 @@ summary: "Sub-agents: spawning isolated agent runs that announce results back to read_when: - You want background/parallel work via the agent - You are changing sessions_spawn or sub-agent tool policy + - You are implementing or troubleshooting thread-bound subagent sessions title: "Sub-Agents" --- @@ -22,6 +23,13 @@ Use `/subagents` to inspect or control sub-agent runs for the **current session* - `/subagents steer ` - `/subagents spawn [--model ] [--thinking ]` +Discord thread binding controls: + +- `/focus ` +- `/unfocus` +- `/agents` +- `/session ttl ` + `/subagents info` shows run metadata (status, timestamps, session id, transcript path, cleanup). ### Spawn behavior @@ -40,6 +48,7 @@ Use `/subagents` to inspect or control sub-agent runs for the **current session* - compact runtime/token stats - `--model` and `--thinking` override defaults for that specific run. - Use `info`/`log` to inspect details and output after completion. +- `/subagents spawn` is one-shot mode (`mode: "run"`). For persistent thread-bound sessions, use `sessions_spawn` with `thread: true` and `mode: "session"`. Primary goals: @@ -69,8 +78,40 @@ Tool params: - `model?` (optional; overrides the sub-agent model; invalid values are skipped and the sub-agent runs on the default model with a warning in the tool result) - `thinking?` (optional; overrides thinking level for the sub-agent run) - `runTimeoutSeconds?` (default `0`; when set, the sub-agent run is aborted after N seconds) +- `thread?` (default `false`; when `true`, requests channel thread binding for this sub-agent session) +- `mode?` (`run|session`) + - default is `run` + - if `thread: true` and `mode` omitted, default becomes `session` + - `mode: "session"` requires `thread: true` - `cleanup?` (`delete|keep`, default `keep`) +## Discord thread-bound sessions + +When thread bindings are enabled, a sub-agent can stay bound to a Discord thread so follow-up user messages in that thread keep routing to the same sub-agent session. + +Quick flow: + +1. Spawn with `sessions_spawn` using `thread: true` (and optionally `mode: "session"`). +2. OpenClaw creates or binds a Discord thread to that session target. +3. Replies and follow-up messages in that thread route to the bound session. +4. Use `/session ttl` to inspect/update auto-unfocus TTL. +5. Use `/unfocus` to detach manually. + +Manual controls: + +- `/focus ` binds the current thread (or creates one) to a sub-agent/session target. +- `/unfocus` removes the binding for the current Discord thread. +- `/agents` lists active runs and binding state (`thread:` or `unbound`). +- `/session ttl` only works for focused Discord threads. + +Config switches: + +- Global default: `session.threadBindings.enabled`, `session.threadBindings.ttlHours` +- Discord override: `channels.discord.threadBindings.enabled`, `channels.discord.threadBindings.ttlHours` +- Spawn auto-bind opt-in: `channels.discord.threadBindings.spawnSubagentSessions` + +See [Discord](/channels/discord), [Configuration Reference](/gateway/configuration-reference), and [Slash commands](/tools/slash-commands). + Allowlist: - `agents.list[].subagents.allowAgents`: list of agent ids that can be targeted via `agentId` (`["*"]` to allow any). Default: only the requester agent.