Quick Start
Test with Long Task
Send a long-running request like “research solar energy trends” to your bot, then immediately send another message. You’ll get instant feedback instead of silence.
How It Works
Fixed in PR #1980 (release after 2026-06-19): earlier releases wired the interrupt controller to the wrong attribute, so
/stop and busy_mode="interrupt" silently had no effect. Queued follow-ups were surfaced in metadata but never drained — upgrade to pick up the fix.run_timeout is exceeded (default 300 seconds), BotSessionManager raises BotRunTimeout and cancels the in-flight run. Timeout failures are not pushed to the DLQ, so slow agents do not retry in a loop.
Choosing a Busy Mode
| Mode | When to Use | Behavior |
|---|---|---|
queue | Research bots, task completion important | Messages queued, processed in order after current task |
interrupt | Interactive chat, latest intent matters | Cancels current task, starts new one immediately |
steer | Real-time collaboration (experimental) | Injects messages into running task (fallback to queue) |
Configuration Options
Configure run control throughBotConfig or directly with bot constructors:
| Option | Type | Default | Description |
|---|---|---|---|
busy_mode | str | "queue" | Policy for mid-run messages. One of "queue", "interrupt", "steer". |
busy_ack | str | "⏳ {action} — will be considered next" | Template for busy acknowledgment. {action} is replaced with "noted" (queued) or "added to pending request" (merged). |
run_timeout | float | 300.0 | Maximum seconds a single agent run may take before it is cancelled with BotRunTimeout. Set to 0 or a negative value to disable. Applies to both streaming (agent.astart) and non-streaming (agent.chat) paths. |
Run metadata
chat_with_run_control returns {"response": str, "metadata": dict}:
| Field | Type | Description |
|---|---|---|
run_control | bool | Always True when run control was active. |
decision | str | Initial decision: "RUN_NOW", "QUEUED", or "INTERRUPTED". |
completed | bool | True after the run and drain loop finish cleanly. |
run_generation | int | Generation counter for race protection. |
pending_processed | list[str] | Queued follow-ups drained after the initial run (each truncated to 100 chars). Present only when at least one was processed. |
interrupted | bool | True if the run was cancelled mid-way. |
reason | str | Cancellation reason (when interrupted is True). |
Programmatic cancellation
BotSessionManager exposes cancellation for admin hooks or custom integrations:
| Method | Returns | Description |
|---|---|---|
cancel_run(user_id, reason="user_cancel") | bool | True if an active run existed and was signalled |
get_active_runs() | list[str] | Storage keys with runs currently in progress |
BotConfig API Reference
Full configuration options for bot settings
Common Patterns
Long Research Bot (Queue Mode)
Interactive Chat Bot (Interrupt Mode)
Using /stop Mid-Task
When a bot is processing a long request:Best Practices
When to Use Queue vs Interrupt vs Steer
When to Use Queue vs Interrupt vs Steer
Use queue mode for bots that do important work users shouldn’t lose — research bots, analysis tools, content generators. Users get acknowledgments but work continues.Use interrupt mode for conversational bots where the latest message reflects user intent — chat assistants, Q&A bots, real-time helpers.Use steer mode (experimental) for collaborative scenarios where users provide guidance during long tasks. Currently falls back to queue mode.
How /stop Finds the Right Cancel Path
How /stop Finds the Right Cancel Path
/stop works on stock bots without setting busy_mode. The handler tries SessionRunControl.stop() first when run control is enabled, then falls back to BotSessionManager.cancel_run() on the default session manager. Enable run control (busy_mode) when you also want mid-run pending-message handling — not as a prerequisite for /stop.Race Protection via run_generation
Race Protection via run_generation
Each run gets a unique generation number. When a run finishes, it only clears the session state if its generation matches the current one. This prevents cancelled runs from overwriting fresh state when they complete.
Cleaning Up Stale Sessions
Cleaning Up Stale Sessions
Use
SessionRunControl.cleanup_stale_sessions(max_age_seconds=3600) to clean up old sessions. This prevents memory leaks in long-running bots and removes abandoned user sessions.Custom integrators: use agent.interrupt_controller
Custom integrators: use agent.interrupt_controller
If you embed
BotSessionManager.chat_with_run_control() in a custom bot, attach interrupt controllers to agent.interrupt_controller (the public attribute on Agent). An earlier underscore-prefixed name was unreliable and made /stop a no-op for custom integrations. The built-in bots (TelegramBot, etc.) handle this attachment automatically.Related
Bot Commands
Built-in chat commands including /stop
Messaging Bots
Complete guide to Telegram, Discord, Slack bots

