sase-telegram is a plugin for sase that provides two-way Telegram integration. It sends SASE notifications to Telegram, and lets you respond to plan approvals, HITL requests, user questions, and even launch new agents — all from Telegram.
For a managed SASE install, install sase-telegram into the same uv tool environment as sase so its CLI scripts
are available to SASE's chop automation.
If SASE is already installed with uv tool install sase, open sase ace, press # for the SASE Admin Center, then go
to the Updates tab (5, or [ / ]). Highlight sase-telegram in the plugin list (j / k, or / to filter),
press i to install, and confirm the preview modal. The preview shows the exact uv command and resolved package set;
the install runs as a tracked background task and is discovered on the next sase run.
See the core SASE docs for the
Updates tab and
sase plugin commands.
uv tool install sase --with sase-telegramRepeat --with for additional plugins, for example --with sase-telegram --with sase-github. Add --force to replace
an existing tool install.
sase plugin install telegrampip install sase-telegram is only an escape hatch for non-managed or library-style environments. It is not the normal
path for a uv tool-managed SASE command.
Requires sase>=0.1.0 as a dependency (installed automatically).
Installing sase-telegram adds the following commands:
| Command | Description |
|---|---|
sase_chop_tg_outbound |
Send pending notifications to Telegram (supports --dry-run) |
sase_chop_tg_inbound |
Poll Telegram for user responses and process them |
| Type | Telegram Behavior |
|---|---|
| Plan / Epic Approval | Shows an ordered frontmatter Properties card and plan body with approval controls; epic reviews include glanceable phase counts and validated size totals when available |
| HITL Request | Shows request notes with Accept / Reject / Feedback buttons |
| User Question | Shows question with dynamic option buttons + Custom input |
| Workflow Complete | Sends a summary message with diff/chat/media attachments and a Fork copy button |
| Agent Launched | Shows provider/model label, workspace number, prompt snippet, and Fork / Wait / Kill / Retry buttons |
| Agent Killed | Confirms termination with a Redo copy button to re-launch with the same prompt |
| Error Digest | Sends error summary with digest file attachments |
| Image Generated | Sends model name and generated image inline |
- Outbound notifications — sends unread, non-silent SASE notifications to Telegram
- Rate limiting — sliding-window rate limiter prevents message flooding
- Exclusive outbound locking — prevents concurrent outbound runs from duplicating sends
- Two-step feedback — press a Feedback/Custom button, then type your response
- Agent launching — send a text message to spawn a new sase agent from Telegram
- Auto-naming — agents launched from Telegram automatically get assigned names
- xprompt expansion — agent prompts expand xprompt references (e.g.
#mentor) - Multi-model directives — use
%{%m:opus | %m:sonnet}to launch the same prompt across multiple models - Copy-text buttons — Fork, Wait, Retry, Redo, plan, and ChangeSpec buttons copy pre-filled text to your clipboard
- Photo/document handling — send photos, albums, or image documents to launch agents with visual context
- Slash commands — built-in agent-management commands plus user-defined commands from
telegram.commands, all registered withset_my_commandsso they show up in the chat input UI - Kinship views —
/showindexes clans, families, and tribes;/show <ref>opens rich agent or group status with mobile drill-down and refresh buttons - Media attachments — workflow completion attachments route static images, GIFs, videos, and PDFs through the matching Telegram send method, with GIF/video document fallback and one preferred motion-media representation per same-directory filename stem
- PDF attachments — Markdown attachments are rendered to PDF through the shared SASE renderer when possible
- Large content handling — keeps short plan properties open, makes metadata-heavy property cards expandable, and truncates oversized property values/body previews with an attachment fallback
- Message splitting — messages exceeding Telegram's 4096-character limit are automatically split
- Parse mode fallback — falls back to plain text if MarkdownV2 rendering fails
The chops are no-ops unless ~/.sase/telegram_is_enabled exists. When the flag file is absent,
sase_chop_tg_outbound and sase_chop_tg_inbound exit immediately with status 0, print nothing, and skip all heavy
imports, network calls, and locks. This lets the telegram lumberjack be configured globally (on every machine) while
only explicitly-enabled machines actually talk to Telegram.
Enable a machine with:
touch ~/.sase/telegram_is_enabledBot token sources are checked in order:
| Source | Description |
|---|---|
SASE_TELEGRAM_BOT_TOKEN |
Bot token from the environment |
~/.sase/telegram_bot_token |
Bot token file; must not be group/other-readable |
pass show telegram_sase_bot_token |
Bot token from pass |
The chat target and bot username are required separately:
| Source | Description |
|---|---|
SASE_TELEGRAM_BOT_CHAT_ID |
Chat ID to send messages to |
SASE_TELEGRAM_BOT_USERNAME |
Bot username |
| Variable | Default | Description |
|---|---|---|
SASE_TELEGRAM_RATE_LIMIT |
8/15 |
Rate limit as max_messages/window_seconds |
SASE_TELEGRAM_LAUNCH_AGENTS_DISABLED |
unset | When present with any value, inbound callbacks, feedback, and slash commands still work, but plain text/photo/image-document messages do not launch agents. |
Declare additional commands in SASE configuration. The executable is parsed into an argument vector and run directly; the inbound chop never invokes a shell.
telegram:
commands:
tasks:
description: "📋 Obsidian tasks dashboard as a PDF"
run: tg_cmd_tasks
output: pdf
timeout: 90sdescription and run are required. output is message by default and may be set to pdf; timeout defaults to
60s and accepts seconds, minutes, or hours such as 30s, 2m, or 1h. Configured commands are registered first in
deterministic name order, followed by built-ins, so they stay near the top of Telegram's command menu. Telegram clients
may show longer command lists in a scrollable sheet. Built-in names and the beads alias are reserved. See the inbound
documentation for the script-output contract.
The outbound script acquires an exclusive file lock (to prevent concurrent runs from duplicating sends), loads unsent
notifications using a high-water mark timestamp, and formats them as Telegram MarkdownV2 messages with inline
keyboards. Plan approvals present every parseable top-level frontmatter field in an ordered Properties card before
the Markdown body. Nested lists and mappings use indented multiline rows; long cards become expandable, and unusually
large values/body previews are truncated with a clear pointer to the complete plan attachment. Epic review headings
show the authored phase-sequence length at a glance (for example, Epic Review · 3 phases); the suffix is omitted when
the file or its phases metadata cannot be safely read. A validated epic also shows a separate summary such as
Phase sizes: 2 small · 1 medium · 1 large, always ordered small, medium, then large with zero buckets omitted. The
detailed phases[].size values remain visible in Properties, while legacy missing sizes normalize to small. If plan
validation or the required SASE capability is unavailable, the summary is quietly omitted without affecting delivery.
Chat
file attachments are trimmed to just the response portion, with commit messages and diffs embedded into the response
PDF when possible. Static images are sent as photos, GIFs as animations, videos as videos, and PDFs as documents; GIFs
and videos retry as documents if Telegram rejects inline media delivery. Workflow completions coalesce same-stem motion
media variants in one directory for delivery while retaining the complete underlying SASE artifact inventory.
Actionable notifications (plan approvals, HITL requests, user questions) are saved as pending actions for the inbound
script to match against.
The inbound script fetches Telegram button presses, text messages, and photo/document uploads. It processes inline keyboard callbacks (approve/run/reject/select/epic, agent controls, and bead pickers), handles two-step feedback flows (Feedback/Custom button followed by a reply or single active text response), and writes response files for sase to pick up. Text messages that don't complete a feedback flow are dispatched as follows:
- Built-in slash commands (
/list [all|<name>|<project>],/show [<agent|clan|family|@tribe>],/kill [<name>],/fork,/changes [project],/xprompts,/bead [<id>],/update) — agent and kinship status, management, ChangeSpec workflow tag lookup, xprompt catalog export, bead inspection, and SASE updates - Configured slash commands — execute the matching
telegram.commandsentry and deliver its Markdown stdout as a message or PDF - Other slash commands (
/start, unknown commands, etc.) — silently ignored - Everything else — launches a new sase agent with the message as the prompt
Agent launches expand xprompt references, support multi-model directives, and auto-assign names. Launch confirmation messages include Fork and Wait copy-text buttons plus Kill and Retry controls for quick follow-up actions.
/show opens a compact index of clans, sequential agent families, and effective tribes represented by live or recent
agents. /show <ref> resolves exact agents before clans and families, then bare tribe names; /show @<tribe> forces a
tribe lookup. The resulting HTML views include progress and status rollups, group-specific context, persisted
drill-down/refresh callbacks, and explicit truncation notes for large groups.
Photos and image documents launch agents with prompts that reference the downloaded local image path. Telegram albums are staged briefly and then launched as one prompt containing a numbered list of all downloaded image paths, so prompt image discovery can surface every file later.
Set SASE_TELEGRAM_LAUNCH_AGENTS_DISABLED on hosts that should process Telegram callbacks, feedback, and slash commands
without launching new agents from free-form text, photos, image documents, or albums. The check is presence-based, so an
empty value still disables launches; ignored launch messages are logged without a Telegram acknowledgement.
/changes lists active ChangeSpecs, excluding Submitted, Archived, and Reverted entries. Use /changes <project> to
filter by exact project name. Each result has a copy-text button for the bare workflow tag, such as #hg:foobar.
/bead lists active beads across all known SASE projects as picker buttons by running
sase bead list --status=open --status=in_progress. The explicit filters prevent the CLI's interactive closed-bead
fallback, so projects with only closed beads add no picker entries; if every project has only closed beads, Telegram
reports No active beads. /bead <id> runs sase bead show <id>, converts the output to Telegram MarkdownV2, and
sends the bead details in chat. If SASE_TELEGRAM_BEAD_PROJECT is set, bead commands are narrowed to that project
workspace. Without the override, detail lookup searches known projects and prefers the chat-scoped project remembered
from recent Telegram launch context.
/update starts the shared SASE chat update worker in a detached process and immediately replies with the worker log
path. The worker runs the built-in sase update --json engine, using SASE's normal managed-vs-dev update routing, then
ensures axe is running afterward. After the worker exits, the next inbound run sends a completion message that reports
the worker's update summary or the fallback failure exit code and includes the worker log path.
Slash command registration is cached in ~/.sase/telegram/commands_registered_ts, but the cache includes a fingerprint
of the command list so renamed commands are registered immediately after deployment.
State files are stored under ~/.sase/telegram/:
| File | Purpose |
|---|---|
pending_actions.json |
Pending notification, kill, retry, bead, and show callback context |
rate_limit.json |
Sliding-window send timestamps |
update_offset.txt |
Last processed Telegram update ID |
awaiting_feedback.json |
Active two-step feedback flow state, keyed by Telegram message |
media_groups.json |
Staged Telegram photo/image-document albums waiting for the quiet window |
last_sent_ts |
High-water mark for outbound notifications |
outbound.lock |
Exclusive lock for outbound process |
outbound_debug.log |
Diagnostic log for outbound sends |
commands_registered_ts |
Cached timestamp and fingerprint for Telegram slash command registration |
images/ |
Downloaded photos and image documents from Telegram messages |
- Python 3.12+
- sase >= 0.1.0
- python-telegram-bot >= 21.0
- pass (for bot token retrieval)
- PDF tooling supported by SASE's shared Markdown renderer (optional, for PDF generation)
just install # Install in editable mode with dev deps
just fmt # Auto-format code
just lint # Run ruff + mypy
just test # Run tests
just check # All checks (lint + test)
just build # Build distribution packages
just clean # Remove build artifactssrc/sase_telegram/
├── __init__.py # Package init
├── callback_data.py # Encode/decode inline keyboard callback data (64-byte limit)
├── custom_commands.py # Config loading, subprocess execution, and stdout metadata
├── credentials.py # Bot token sources, chat ID, and username
├── agent_format.py # Shared pure HTML formatting for agent list/detail views
├── show_entities.py # /show reference resolution and kinship index models
├── show_format.py # Pure agent/clan/family/tribe HTML renderers
├── formatting.py # Notification → Telegram MarkdownV2 formatting + inline keyboards
├── inbound.py # Pure logic: callback decoding, two-step feedback, photo handling
├── bead_format.py # Convert `sase bead` output to Markdown for Telegram rendering
├── outbound.py # High-water mark tracking, exclusive lock, unsent detection
├── pending_actions.py # Persist pending actions to JSON (24h stale cleanup)
├── rate_limit.py # Sliding-window rate limiter (configurable via env var)
├── telegram_client.py # Sync wrapper with retry/backoff and message splitting
├── pdf_convert.py # Markdown to PDF via SASE's shared renderer
├── pdf_style.css # CSS styling for rendered PDF output
└── scripts/
├── __init__.py # Re-exports inbound_main and outbound_main
├── sase_tg_outbound.py # Outbound entry point (--dry-run, --context)
└── sase_tg_inbound.py # Inbound entry point (--once, --context)
MIT