Documentation
Slack and Telegram
Chat-driven intake and remote approvals.
On this page
Two chat surfaces, two different jobs
Slack and Telegram both put TDE where the conversation already is, but they solve different problems. Slack is team intake: your colleagues raise things in channels and TDE turns the ones addressed to you into work. Telegram is a private remote control: your own bot, in your own chat, so you can approve a guarded action from a phone on a train.
Slack
Two apps, and only one of them is yours to install
| TDE Triage | TDE Assistant | |
|---|---|---|
| What it is | Your own Slack account for Bot and the native TDE Slack client | A workspace bot, mentioned as @tde |
| Who sets it up | You, from a project's Integrations | A Slack administrator, once, from the marketplace |
| Token | A user token (xoxp-), in your local vault | A bot token, held server-side and never delivered to a desktop |
| Scopes | User read/history/profile scopes plus chat:write | app_mentions:read, chat:write, commands, channel and group history |
| How it runs | Local Bot plus a native Rust client that talks directly to Slack | A hosted events router, in real time |
| Identity | (TDE user, team, Slack user) | (team, Slack user) mapped to a TDE user |
Triage has no bot, no Events API subscription and no workspace-wide command
surface. It sees exactly what your Slack account sees. Assistant is the
opposite: it is the visible @tde in channels, and the desktop never installs,
configures or links it.
The same Triage authorization powers TDE Slack, the native mouse-first
Desktop client. It browses channels, DMs, history and threads and lets the human
compose messages without a second login. The client talks directly to Slack's
Web API; no TDE server proxies chat traffic. Its cache and drafts are isolated
per (workspace, Slack user) on each device, while Slack remains the source of
truth. The write scopes do not let Triage Bot post automatically — only a
human submit action in TDE Slack sends a message.
Existing Triage installs created before the native client must reconnect once
to grant chat:write. When Slack reports missing_scope, TDE Slack shows a
Reconnect action. Disconnecting Triage immediately invalidates the live
client and removes that identity's local message cache.
Linking is order-independent. Because Triage already establishes
(team, Slack user), installing Assistant later backfills the eligible Triage
users, and connecting Triage after an install links immediately. There is no
second OAuth. The two are separate entitlements even though the current rollout
grants both together.
Connecting Triage
From Project → Integrations, pick Slack. TDE calls a hosted OAuth broker with your license as a bearer token, renders the returned authorization URL as a QR code in TDE Browser, and polls until Slack redirects. The license never appears in a URL, and the broker URL is validated — no embedded credentials, no query, no fragment, HTTPS unless it is loopback.
The result is stored as connector slack-triage:<team>:<user> with the team name
and Slack user id as metadata. Only a xoxp- user token is accepted; a bot token
is rejected with Slack Triage requires a user token. See
Connectors.
How a Slack message becomes a task
The triage bot runs every 15 minutes and walks every conversation your token can see, in order: DMs, group DMs, private channels, then public channels.
- The first time it sees a DM or group DM it triages a bounded window of 15 recent messages. The first time it sees a channel it only records a cursor — it never triages a channel's backlog.
- Afterwards it fetches incrementally, up to 50 messages, from the stored cursor.
- Bot posts, your own messages and blank messages are filtered out.
- An agent decides whether each new thread is a demand. There is a cap of ten agent calls per run; over budget it falls back to a keyword heuristic.
A new demand becomes a task with tracker id
slack:<team>:<channel>:<thread_root_ts>, filed into the Context stage. Later
messages in the same thread do not create a second task — while the task is
still in Context they are appended as activity comments authored slack, with
the body New Slack activity in #channel: followed by the message.
The run log lands in ~/.tde/log/bot-slack.log, ending each run with a
line counting tasks created, threads updated, conversations scanned, agent
rejections and API errors.
The @tde surface
When Assistant is installed, mentioning @tde in a channel it was invited to
starts a real-time exchange. Follow-ups in the same thread need no mention.
Direct messages are deliberately unsupported — the App Home messages tab is off.
Your device runs a small daemon that registers with TDE's events router
(https://events.tde.sh, overridable with TDE_EVENTS_URL) using your license,
receives a short-lived token that exists only in memory, and holds a WebSocket.
The router is authoritative about which device is live: only the newest
connection holds a lease, and a stale machine cannot acknowledge, post, or pass
the final board mutation guard. A registration failure with 403 means Assistant
is not entitled or not installed yet, and the daemon keeps retrying with backoff.
Each mention is classified into exactly one of four actions:
| Action | What happens |
|---|---|
reply | A read-only answer posted in the thread. No task, no approval |
read_only_run | An investigation in a provider-enforced read-only sandbox, with no connector credentials injected, posted back to the thread |
clarify | One clarification question. The board is untouched |
change | One task for the thread, carrying an explicit plan and blocked until a human approves that exact plan |
Slack text is treated as data, never as instructions. Replies are capped at 4000 bytes, plans at eight steps, thread context at 30 messages.
Approving a plan
Two surfaces reach the same atomic gate:
- Buttons. The plan is posted as Block Kit with
Aprovar planoandRejeitar. Each button carries the task and plan ids, so a stale button from an old message is an idempotent no-op. After a decision the original message is replaced through Slack's interaction callback URL — pinned tohooks.slack.com, redirects disabled — so the buttons visibly disappear. - A typed reply in the same thread, parsed by a strict whole-message matcher,
never by the model.
approve,approved,approve plan,yes approveand their Portuguese equivalents count; anything else does not.
A message that mentions a human colleague is never rewritten into a bare approval: the daemon deliberately does not strip a leading mention before parsing. And an unrecognized Slack user's mention is silently acknowledged with no reply at all, so the existence of your private TDE connection is not disclosed to the workspace.
A genuine processing error is retried twice; the third gives up with a single
message rather than looping. Everything the daemon does is logged to
~/.tde/log/slack-events.log.
Telegram
Setup
Telegram is a personal connector — one bot, one chat, one machine. It takes two values, in this order:
- A bot token from
@BotFather. TDE shape-checks it and then verifies it live withgetMe, recording the bot's username and id. - Your chat id. Message the bot first, then get the id from
@userinfobot.
TDE rejects the common mistakes explicitly: pasting the bot token again where the
chat id goes, or pasting the bot's own id instead of yours. On success it sends
TDE inbox mirror connected. and mirrors your last 20 inbox rows.
The bridge then long-polls getUpdates with a 25-second timeout, persisting its
cursor to ~/.tde/telegram_offset so a restart does not replay or skip updates.
Updates from any chat other than the configured one are dropped before they
are classified.
Message to task
Any message that is not a /command becomes a task with tracker id
telegram:<update_id>. The title is the first line, truncated to 80 characters;
the body is the whole message. It goes through the same external intake path as
Linear sync, so an agent decides the project, the agent and persona, the labels
and the priority. TDE replies 📥 Task criada: <title>.
Approvals from a phone
Two different button flows exist, and they are not the same thing.
Guard approvals fire whenever an agent action hits a guard rule:
🔐 Approval needed
run: rm -rf ./build
Guard rule: rm -rf (recursive delete)
[ ✅ Approve ] [ ❌ Deny ]Parked questions fire when an agent asks something with real options. Each option becomes a button labelled with the option's own text — never a label guessed from prose. Tapping one records the answer and re-runs the stage.
In both cases the desktop shows the same prompt, whichever surface answers first
wins, and the loser reports Already handled. A tap whose nonce no longer
matches a live request is inert. See
Human gates.
The progress mirror
Tasks whose tracker id starts with telegram: mirror stage transitions only
to your chat:
| Event | Message |
|---|---|
| Stage started | ▶️ <title>: <stage> |
| Finished | ✅ <title>: done |
| Failed | ❌ <title>: <error> |
| Blocked | ⛔ <title>: <blocker> |
Individual log lines, traces and subtask updates are deliberately not streamed — a phone would drown. This has a design consequence worth knowing when you write prompts or skills: anything the human must decide should surface as a blocker or a guarded action, not as a log line, because a log line will never reach the phone.
Bot commands
Three, all read-only:
/tasks list current board tasks by stage
/status task counts per stage
/help this message/start is an alias for /help, and /command@yourbot works too. Anything else
is Unknown command. Try /help.
The bundled skills
Two shipped skills teach agents how to work the tasks these bridges create, and they are provisioned automatically when the matching integration is configured.
slack-triage— recognizes a task whose tracker id starts withslack:or whose activity containsNew Slack activity. It tells the agent that comments keep accumulating while the task sits in Context, so the latest comments win over the description, and that the deliverable is a reviewer-ready draft reply, never a posted message.telegram-triage— recognizestelegram:tasks and, more usefully, explains the phone-side constraints above: guarded targets must be self-explaining because the approver has no context, stage names must be legible because they are all that gets mirrored, and there is no token streaming.
See Skills, prompts and deliverables.