Summary
The trigger system (crons + webhooks) is currently configured but largely invisible to users in the Telegram UI. Once set up in untether.toml, there's no easy way to discover what triggers exist, which chats they target, when they last fired, or whether a run was trigger-initiated.
This issue tracks improvements to surface trigger presence, activity, and management across the Untether UI.
Current state
| Area |
What exists today |
Gap |
| Startup message |
Global count: "triggers: enabled (N webhooks, M crons)" |
Not per-chat; doesn't say which chats have triggers |
| Per-chat indicator |
Nothing |
No visual cue that a chat has active triggers |
| Trigger dispatch |
⚡ Trigger: webhook:id / 🕐 Scheduled: cron:id notification |
Silent (notify=False), no schedule/context info |
| Run footer |
Shows model/effort/permission |
Identical for trigger-initiated and manual runs |
/config menu |
Trigger mode page (all/mentions) |
No view of configured cron/webhook triggers |
/trigger command |
Sets trigger mode (all/mentions) |
Doesn't list or describe configured triggers |
| Run history |
last_fired dict — in-memory, per-cron, lost on restart |
No persistent history, no UI display |
| Cron schedule display |
Raw 5-field expression stored |
No human-friendly rendering |
| Stats |
Per-engine run counts |
No trigger vs manual breakdown |
Proposed improvements
Tier 1 — Quick wins (v0.35.2)
1a. Per-chat trigger indicator in startup/greeting
When the bot starts (or on /ping), if the current chat has triggers configured, append a line:
⏰ triggers: 1 cron (daily-review, 8am Mon–Fri Melbourne)
Requires a reverse lookup: iterate trigger_settings.crons + .webhooks, match chat_id → current chat. Lightweight, no persistence needed.
1b. Trigger-initiated run footer
When a run was started by a trigger, add to the meta line:
🏷 sonnet · plan · ⏰ cron:daily-review
or
🏷 sonnet · plan · ⚡ webhook:github-push
Pass trigger source through RunOptions or ProgressTracker.meta so format_meta_line() can render it.
1c. Human-friendly cron description
Add a describe_cron(schedule, timezone) utility that converts 0 8 * * 1-5 + Australia/Melbourne → "8:00 AM Mon–Fri (Melbourne)". Used in startup indicator, /config page, and dispatch notifications. Keep it simple — cover common patterns, fall back to raw expression for complex ones.
Tier 2 — Discovery (v0.35.2–0.35.3)
2a. /config → Triggers sub-page
Add a triggers info page to the /config menu showing:
- Active crons: id, schedule (human-friendly), timezone, project, engine
- Active webhooks: id, path, auth type, project, engine
- Per-chat filtering: only show triggers targeting this chat (or all if in default chat)
Read-only view — editing stays in untether.toml. This gives users a way to see what's configured without SSH.
2b. Enhanced dispatch notification
Improve the cron/webhook notification messages:
⏰ Scheduled: cron:daily-review
📅 8:00 AM Mon–Fri (Melbourne) · project: myapp
Add schedule context and project info. Still silent (notify=False), but more informative when scrolling history.
Tier 3 — History & stats (v0.35.3+)
3a. Persistent trigger run history
Track last_fired_at (UTC timestamp) per trigger in a JSON state file (similar to chat_prefs pattern). Show in /config triggers page:
⏰ daily-review — 8:00 AM Mon–Fri (Melbourne)
Last run: today 8:00 AM · next: tomorrow 8:00 AM
3b. Trigger activity in /stats
Add trigger-initiated run counts alongside engine counts:
Runs today: 5 (3 manual, 2 triggered)
cron:daily-review — 1 run
webhook:github-push — 1 run
Extend DayBucket in session_stats.py with an optional source field.
3c. Trigger run notifications
Optional: when a trigger fires and completes, send a brief completion notification (configurable). Useful for webhook-triggered runs that complete while the user isn't watching.
Key files
| File |
Relevance |
src/untether/telegram/loop.py:143-157 |
Startup message construction |
src/untether/markdown.py:312-394 |
Footer/meta line formatting |
src/untether/triggers/dispatcher.py:30-86 |
Trigger dispatch notifications |
src/untether/triggers/cron.py |
Cron scheduler + matching |
src/untether/triggers/settings.py |
CronConfig/WebhookConfig models |
src/untether/telegram/commands/config.py:924-988 |
/config trigger mode page |
src/untether/telegram/commands/trigger.py |
/trigger command (mode only) |
src/untether/session_stats.py |
Run stats tracking |
src/untether/telegram/chat_prefs.py |
Per-chat state storage |
Design notes
- No new dependencies — human-friendly cron description is a simple pattern-matching function
- Read-only UI — trigger config stays in
untether.toml; Telegram shows info, doesn't edit
- Backward compatible — all new UI elements are additive; no existing behaviour changes
- Per-chat awareness requires iterating trigger configs to find matching
chat_id — cheap at startup, could be cached in TransportRuntime
Summary
The trigger system (crons + webhooks) is currently configured but largely invisible to users in the Telegram UI. Once set up in
untether.toml, there's no easy way to discover what triggers exist, which chats they target, when they last fired, or whether a run was trigger-initiated.This issue tracks improvements to surface trigger presence, activity, and management across the Untether UI.
Current state
⚡ Trigger: webhook:id/🕐 Scheduled: cron:idnotificationnotify=False), no schedule/context info/configmenu/triggercommandlast_fireddict — in-memory, per-cron, lost on restartProposed improvements
Tier 1 — Quick wins (v0.35.2)
1a. Per-chat trigger indicator in startup/greeting
When the bot starts (or on
/ping), if the current chat has triggers configured, append a line:Requires a reverse lookup: iterate
trigger_settings.crons + .webhooks, matchchat_id→ current chat. Lightweight, no persistence needed.1b. Trigger-initiated run footer
When a run was started by a trigger, add to the meta line:
or
Pass trigger source through
RunOptionsorProgressTracker.metasoformat_meta_line()can render it.1c. Human-friendly cron description
Add a
describe_cron(schedule, timezone)utility that converts0 8 * * 1-5+Australia/Melbourne→"8:00 AM Mon–Fri (Melbourne)". Used in startup indicator,/configpage, and dispatch notifications. Keep it simple — cover common patterns, fall back to raw expression for complex ones.Tier 2 — Discovery (v0.35.2–0.35.3)
2a.
/config→ Triggers sub-pageAdd a triggers info page to the
/configmenu showing:Read-only view — editing stays in
untether.toml. This gives users a way to see what's configured without SSH.2b. Enhanced dispatch notification
Improve the cron/webhook notification messages:
Add schedule context and project info. Still silent (
notify=False), but more informative when scrolling history.Tier 3 — History & stats (v0.35.3+)
3a. Persistent trigger run history
Track
last_fired_at(UTC timestamp) per trigger in a JSON state file (similar tochat_prefspattern). Show in/configtriggers page:3b. Trigger activity in
/statsAdd trigger-initiated run counts alongside engine counts:
Extend
DayBucketinsession_stats.pywith an optionalsourcefield.3c. Trigger run notifications
Optional: when a trigger fires and completes, send a brief completion notification (configurable). Useful for webhook-triggered runs that complete while the user isn't watching.
Key files
src/untether/telegram/loop.py:143-157src/untether/markdown.py:312-394src/untether/triggers/dispatcher.py:30-86src/untether/triggers/cron.pysrc/untether/triggers/settings.pysrc/untether/telegram/commands/config.py:924-988/configtrigger mode pagesrc/untether/telegram/commands/trigger.py/triggercommand (mode only)src/untether/session_stats.pysrc/untether/telegram/chat_prefs.pyDesign notes
untether.toml; Telegram shows info, doesn't editchat_id— cheap at startup, could be cached inTransportRuntime