Skip to main content
Drive an in-chat status through a run’s lifecycle — from your own bot, channel adapter, or gateway — with a single transport-agnostic controller.

Quick Start

1

Reactions only

2

Reactions + label fallback + custom thresholds


How It Works

The controller renders through a reaction OR a label, chosen from platform capabilities plus which callbacks you injected. on_phase(...) reports progress and resets the watchdog; tick(elapsed_s) flushes debounced phases and arms the stall signal. Phases and stall states map to fixed emoji and labels. Intermediate phases debounce by debounce_ms (default 700); the latest pending phase wins and flushes on the next tick. Terminal phases (DONE/ERROR) render immediately and clear any pending phase or stall signal.

StallWatchdog standalone

StallWatchdog maps elapsed-since-progress seconds to a StallState and needs no controller.
If hard_s < soft_s, hard_s is clamped up to soft_s and a warning is logged — SOFT becomes unreachable. Keep hard_s >= soft_s.

Configuration Options

Every option comes straight from RunStatusController.__init__. The controller is enabled only when enabled=True and at least one of set_status_reaction / edit_status_label is present. Pick reaction vs label rendering per this decision flow. RunStatusController also exposes phase (last phase passed to on_phase, possibly un-rendered) and watchdog (the underlying StallWatchdog, so you can reset() it on external progress signals).

User interaction flow

A user sends a message and watches one emoji move through the run without reading any reply text.

Best Practices

If a real tool call takes 40s (web scraping, image generation), set stall_soft_s to 45–60s so users don’t see ⏳ while everything is healthy.
Any phase change is treated as progress and resets the stall watchdog automatically — you rarely need to call watchdog.reset() yourself.
Pass now= a controllable monotonic clock to test debounce and stall behavior deterministically without real sleeps.
Supply set_status_reaction and edit_status_label so channels without reactions degrade to a label automatically, driven by caps.

Bot Status Reactions

The one-flag TelegramBot(status_reactions=True) opt-in

Bot Platform Adapter

Build a custom channel adapter that wires this controller