Telegram Channel Worker
Use this skill when the user wants Telegram as a LionClaw channel.
Manual QA and Telegram-specific acceptance notes live in README.md in this
skill directory.
What you run:
- start LionClaw for background work,
- run the Telegram worker,
- let it bridge Telegram traffic into LionClaw.
Under the hood, the worker:
- receives Telegram updates through long polling by default, or through an
optional webhook receiver with Telegram secret-token verification,
- maps Telegram ids into stable refs such as
telegram:user:<id>,
telegram:chat:<id>, telegram:topic:<id>, and
telegram:message:<id>,
- posts normalized inbound facts to
/v0/channels/inbound and treats the
response as admission state, not completed work,
- claims
lc_... pairing tokens through the kernel pairing API without
starting an agent turn for the claim message,
- downloads and stages Telegram files only after the kernel returns
waiting_for_attachments,
- long-polls
/v0/channels/stream/pull for progress events,
- starts Telegram typing from kernel queue/runtime status events,
- renders long-running turns as one provisional Telegram message and edits it
on throttled progress state changes,
- renders inline buttons for safe active-turn controls such as status and stop,
- intercepts Telegram-local commands such as
/status and /stop without
stealing runtime slash commands such as /compact,
- leases provider deliveries from
/v0/channels/outbox/pull,
- sends Telegram messages from outbox leases and reports provider outcomes to
/v0/channels/outbox/report,
- submits worker health checks through
/v0/channels/health/report,
- advances its progress cursor through
/v0/channels/stream/ack.
Prerequisites
- a configured LionClaw project instance
- Telegram bot token with DM access
python3 and uv for source/development worker runs
Setup
Use the product path first:
printf 'TELEGRAM_BOT_TOKEN=...\n' > telegram.env
lionclaw connect telegram --env-file ./telegram.env
lionclaw doctor
connect telegram installs or refreshes the channel skill, stores declared
channel env in the selected instance home, and starts the background stack.
Direct worker invocation is for development and tests when you intentionally
provide the LionClaw worker environment. Polling is the default. For webhook
deployments, expose the worker's local receiver through HTTPS, set Telegram's
webhook URL with the same secret_token, and run the worker with its normal
LionClaw/Telegram env plus:
TELEGRAM_UPDATE_MODE=webhook \
TELEGRAM_WEBHOOK_SECRET_TOKEN=... \
TELEGRAM_WEBHOOK_HOST=127.0.0.1 \
TELEGRAM_WEBHOOK_PORT=8080 \
TELEGRAM_WEBHOOK_PATH=/telegram/webhook \
./skills/channel-telegram/scripts/worker
Notes
- LionClaw enforces scoped pairing (
pending_approval -> approved grant
through lionclaw channel pairing approve ...).
- The worker accepts Telegram
message, edited_message, channel_post, and
edited_channel_post updates when they contain usable text, captions, or
supported media. Inline callback_query updates are accepted only for
HMAC-scoped LionClaw button payloads generated by this worker.
- Telegram usernames and display names are metadata only. Authorization identity
uses numeric Telegram ids encoded in stable refs.
- Private chats map to
dm; group mentions and bot replies use Telegram
entities and the bot's numeric id; leading bot mentions are stripped from the
runtime-facing text and captions; forum topics map to scoped thread refs.
- Supported inbound media descriptors include photos, documents, audio, voice,
video, stickers, video notes, and animations. Telegram locations and venues
are forwarded as readable text with structured provider metadata. Provider
files are not downloaded for pending, blocked, ignored, or duplicate inbound.
Unsupported user content such as contacts, polls, dice, and paid media is
acknowledged with a clear local reply in DMs and addressed group routes, and
is not submitted to the runtime.
- Back-to-back text updates from the same Telegram route are coalesced into one
inbound turn when they arrive in the same provider batch. Telegram
media_group_id albums are coalesced before attachment staging so the
runtime sees one turn with all album files.
- Pairing invite tokens can be claimed through Telegram with
/start lc_<token>.
DM links use https://t.me/<bot_username>?start=lc_<token>; group links use
https://t.me/<bot_username>?startgroup=lc_<token> where Telegram exposes the
payload to the bot. Approved Telegram hosts can create a short-lived one-use
group link from /connections in DM; unconnected groups are instructed to use
that link path rather than exposing pc_... operator approval codes.
Connected groups authorize the destination only. The Telegram sender must also
have an approved direct host grant before group prompts, runtime slash
commands, or local group controls run.
- The worker defaults
consumer_id to telegram:<channel_id> and
start_mode=resume, so unacked progress events are replayed after worker
restart. LIONCLAW_STREAM_START_MODE accepts resume or tail.
- Telegram delivery is outbox-driven: typing comes from progress streams, final
answers come from durable outbox leases, no reasoning lane delivery.
- Telegram has its own visible command menu. In DMs,
/help, /status,
/stop, /settings, and /connections are channel-local controls. Session
mutation commands stay namespaced as /lionclaw reset and /lionclaw retry;
bare slash commands pass through to the runtime after Telegram-only addressing
syntax is removed.
Connected groups get chat-scoped /ask, /help, /status, /stop, and
/settings menu commands. In groups, /ask message strips /ask and submits
only the message body to the runtime; empty /ask opens a selective Telegram
reply prompt for the asker.
/stop uses the channel-safe active turn cancellation action with the
expected turn id guard. Bot commands explicitly targeted at a different
Telegram bot are never captured as LionClaw-local controls, even inside an
active forum topic. In groups, commands with a leading bot mention, for example
@lionclaw_bot /status, are treated like first-column bot commands; runtime
slash commands are forwarded without the leading mention or @lionclaw_bot
command target.
- Inline buttons carry compact HMAC-protected callback payloads bound to the
conversation, topic, original Telegram sender, and active turn. Stale,
wrong-route, or wrong-actor callbacks are acknowledged without mutating
runtime state.
- Fast turns only show typing. Long turns create one provisional message after a
short threshold, edit it at a throttled cadence, and delete it when the durable
outbox answer is ready. Cancelled and failed turns leave a terminal status.
Transient edit failures are retried; permanent edit failures disable editing
for that message and fall back to a normal Telegram message. Transient delete
failures are persisted under the channel runtime directory and retried after
worker restart so stale provisional messages are cleaned up.
- The worker also sets best-effort Telegram reactions on the original inbound
message as lightweight receipts: accepted, completed, stopped, or failed.
Reaction failures are ignored because Telegram reactions are not available in
every chat.
- Outbound text is sent as plain text by default. Markdown hints render to
Telegram-safe HTML only when the rendered chunks fit Telegram limits; otherwise
delivery falls back to plain text. Link previews stay disabled so local
workspace paths do not become broken Telegram links.
- Outbox attachments are sent as native Telegram media where possible
(
sendPhoto, sendVideo, sendAudio/sendVoice) and fall back to
documents by MIME type. Short text on media deliveries is used as the first
attachment caption when the rendered caption fits Telegram's limits. Native
Telegram media groups are used for compatible photo/video batches with two
to ten attachments.
- Topic
thread_ref and reply_to_ref are converted back into Telegram
delivery parameters, and each outbox lease is reported with its attempt_id.
- Runtime selection comes from the selected instance's default runtime; workers do not send
runtime_id in inbound requests.
- Polling mode stores Telegram offset in
$LIONCLAW_HOME/runtime/channels/$LIONCLAW_CHANNEL_ID/telegram.offset by
default. Webhook mode is enabled with TELEGRAM_UPDATE_MODE=webhook and
requires TELEGRAM_WEBHOOK_SECRET_TOKEN; TELEGRAM_WEBHOOK_HOST,
TELEGRAM_WEBHOOK_PORT, TELEGRAM_WEBHOOK_PATH, and
TELEGRAM_WEBHOOK_MAX_BODY_BYTES tune the local receiver.
- The worker reports health every 60 seconds by default, configurable with
LIONCLAW_HEALTH_REPORT_INTERVAL_SECS. Checks cover a fresh Telegram
getMe, polling or webhook intake health, update lag, malformed provider
updates quarantined by the current worker process, and delivery failures
observed by the current worker process.
Development Checks
Run these from the repository root when changing this skill:
uv run --project skills/channel-telegram black --check skills/channel-telegram/lionclaw_channel_telegram skills/channel-telegram/tests
uv run --project skills/channel-telegram ruff check skills/channel-telegram/lionclaw_channel_telegram skills/channel-telegram/tests
uv run --project skills/channel-telegram python -m unittest discover -s skills/channel-telegram/tests -q
1---2name: channel-telegram3description: Run and operate a Telegram channel worker for LionClaw using the kernel channel bridge APIs.4---56# Telegram Channel Worker78Use this skill when the user wants Telegram as a LionClaw channel.910Manual QA and Telegram-specific acceptance notes live in `README.md` in this11skill directory.1213What you run:14151. start LionClaw for background work,162. run the Telegram worker,173. let it bridge Telegram traffic into LionClaw.1819Under the hood, the worker:20211. receives Telegram updates through long polling by default, or through an22 optional webhook receiver with Telegram secret-token verification,232. maps Telegram ids into stable refs such as `telegram:user:<id>`,24 `telegram:chat:<id>`, `telegram:topic:<id>`, and25 `telegram:message:<id>`,263. posts normalized inbound facts to `/v0/channels/inbound` and treats the27 response as admission state, not completed work,284. claims `lc_...` pairing tokens through the kernel pairing API without29 starting an agent turn for the claim message,305. downloads and stages Telegram files only after the kernel returns31 `waiting_for_attachments`,326. long-polls `/v0/channels/stream/pull` for progress events,337. starts Telegram typing from kernel queue/runtime status events,348. renders long-running turns as one provisional Telegram message and edits it35 on throttled progress state changes,369. renders inline buttons for safe active-turn controls such as status and stop,3710. intercepts Telegram-local commands such as `/status` and `/stop` without38 stealing runtime slash commands such as `/compact`,3911. leases provider deliveries from `/v0/channels/outbox/pull`,4012. sends Telegram messages from outbox leases and reports provider outcomes to41 `/v0/channels/outbox/report`,4213. submits worker health checks through `/v0/channels/health/report`,4314. advances its progress cursor through `/v0/channels/stream/ack`.4445## Prerequisites4647- a configured LionClaw project instance48- Telegram bot token with DM access49- `python3` and `uv` for source/development worker runs5051## Setup5253Use the product path first:5455```bash56printf 'TELEGRAM_BOT_TOKEN=...\n' > telegram.env57lionclaw connect telegram --env-file ./telegram.env58lionclaw doctor59```6061`connect telegram` installs or refreshes the channel skill, stores declared62channel env in the selected instance home, and starts the background stack.6364Direct worker invocation is for development and tests when you intentionally65provide the LionClaw worker environment. Polling is the default. For webhook66deployments, expose the worker's local receiver through HTTPS, set Telegram's67webhook URL with the same `secret_token`, and run the worker with its normal68LionClaw/Telegram env plus:6970```bash71TELEGRAM_UPDATE_MODE=webhook \72TELEGRAM_WEBHOOK_SECRET_TOKEN=... \73TELEGRAM_WEBHOOK_HOST=127.0.0.1 \74TELEGRAM_WEBHOOK_PORT=8080 \75TELEGRAM_WEBHOOK_PATH=/telegram/webhook \76./skills/channel-telegram/scripts/worker77```7879## Notes8081- LionClaw enforces scoped pairing (`pending_approval` -> approved grant82 through `lionclaw channel pairing approve ...`).83- The worker accepts Telegram `message`, `edited_message`, `channel_post`, and84 `edited_channel_post` updates when they contain usable text, captions, or85 supported media. Inline `callback_query` updates are accepted only for86 HMAC-scoped LionClaw button payloads generated by this worker.87- Telegram usernames and display names are metadata only. Authorization identity88 uses numeric Telegram ids encoded in stable refs.89- Private chats map to `dm`; group mentions and bot replies use Telegram90 entities and the bot's numeric id; leading bot mentions are stripped from the91 runtime-facing text and captions; forum topics map to scoped thread refs.92- Supported inbound media descriptors include photos, documents, audio, voice,93 video, stickers, video notes, and animations. Telegram locations and venues94 are forwarded as readable text with structured provider metadata. Provider95 files are not downloaded for pending, blocked, ignored, or duplicate inbound.96 Unsupported user content such as contacts, polls, dice, and paid media is97 acknowledged with a clear local reply in DMs and addressed group routes, and98 is not submitted to the runtime.99- Back-to-back text updates from the same Telegram route are coalesced into one100 inbound turn when they arrive in the same provider batch. Telegram101 `media_group_id` albums are coalesced before attachment staging so the102 runtime sees one turn with all album files.103- Pairing invite tokens can be claimed through Telegram with `/start lc_<token>`.104 DM links use `https://t.me/<bot_username>?start=lc_<token>`; group links use105 `https://t.me/<bot_username>?startgroup=lc_<token>` where Telegram exposes the106 payload to the bot. Approved Telegram hosts can create a short-lived one-use107 group link from `/connections` in DM; unconnected groups are instructed to use108 that link path rather than exposing `pc_...` operator approval codes.109 Connected groups authorize the destination only. The Telegram sender must also110 have an approved direct host grant before group prompts, runtime slash111 commands, or local group controls run.112- The worker defaults `consumer_id` to `telegram:<channel_id>` and113 `start_mode=resume`, so unacked progress events are replayed after worker114 restart. `LIONCLAW_STREAM_START_MODE` accepts `resume` or `tail`.115- Telegram delivery is outbox-driven: typing comes from progress streams, final116 answers come from durable outbox leases, no reasoning lane delivery.117- Telegram has its own visible command menu. In DMs, `/help`, `/status`,118 `/stop`, `/settings`, and `/connections` are channel-local controls. Session119 mutation commands stay namespaced as `/lionclaw reset` and `/lionclaw retry`;120 bare slash commands pass through to the runtime after Telegram-only addressing121 syntax is removed.122 Connected groups get chat-scoped `/ask`, `/help`, `/status`, `/stop`, and123 `/settings` menu commands. In groups, `/ask message` strips `/ask` and submits124 only the message body to the runtime; empty `/ask` opens a selective Telegram125 reply prompt for the asker.126 `/stop` uses the channel-safe active turn cancellation action with the127 expected turn id guard. Bot commands explicitly targeted at a different128 Telegram bot are never captured as LionClaw-local controls, even inside an129 active forum topic. In groups, commands with a leading bot mention, for example130 `@lionclaw_bot /status`, are treated like first-column bot commands; runtime131 slash commands are forwarded without the leading mention or `@lionclaw_bot`132 command target.133- Inline buttons carry compact HMAC-protected callback payloads bound to the134 conversation, topic, original Telegram sender, and active turn. Stale,135 wrong-route, or wrong-actor callbacks are acknowledged without mutating136 runtime state.137- Fast turns only show typing. Long turns create one provisional message after a138 short threshold, edit it at a throttled cadence, and delete it when the durable139 outbox answer is ready. Cancelled and failed turns leave a terminal status.140 Transient edit failures are retried; permanent edit failures disable editing141 for that message and fall back to a normal Telegram message. Transient delete142 failures are persisted under the channel runtime directory and retried after143 worker restart so stale provisional messages are cleaned up.144- The worker also sets best-effort Telegram reactions on the original inbound145 message as lightweight receipts: accepted, completed, stopped, or failed.146 Reaction failures are ignored because Telegram reactions are not available in147 every chat.148- Outbound text is sent as plain text by default. Markdown hints render to149 Telegram-safe HTML only when the rendered chunks fit Telegram limits; otherwise150 delivery falls back to plain text. Link previews stay disabled so local151 workspace paths do not become broken Telegram links.152- Outbox attachments are sent as native Telegram media where possible153 (`sendPhoto`, `sendVideo`, `sendAudio`/`sendVoice`) and fall back to154 documents by MIME type. Short text on media deliveries is used as the first155 attachment caption when the rendered caption fits Telegram's limits. Native156 Telegram media groups are used for compatible photo/video batches with two157 to ten attachments.158- Topic `thread_ref` and `reply_to_ref` are converted back into Telegram159 delivery parameters, and each outbox lease is reported with its `attempt_id`.160- Runtime selection comes from the selected instance's default runtime; workers do not send `runtime_id` in inbound requests.161- Polling mode stores Telegram offset in162 `$LIONCLAW_HOME/runtime/channels/$LIONCLAW_CHANNEL_ID/telegram.offset` by163 default. Webhook mode is enabled with `TELEGRAM_UPDATE_MODE=webhook` and164 requires `TELEGRAM_WEBHOOK_SECRET_TOKEN`; `TELEGRAM_WEBHOOK_HOST`,165 `TELEGRAM_WEBHOOK_PORT`, `TELEGRAM_WEBHOOK_PATH`, and166 `TELEGRAM_WEBHOOK_MAX_BODY_BYTES` tune the local receiver.167- The worker reports health every 60 seconds by default, configurable with168 `LIONCLAW_HEALTH_REPORT_INTERVAL_SECS`. Checks cover a fresh Telegram169 `getMe`, polling or webhook intake health, update lag, malformed provider170 updates quarantined by the current worker process, and delivery failures171 observed by the current worker process.172173## Development Checks174175Run these from the repository root when changing this skill:176177```bash178uv run --project skills/channel-telegram black --check skills/channel-telegram/lionclaw_channel_telegram skills/channel-telegram/tests179uv run --project skills/channel-telegram ruff check skills/channel-telegram/lionclaw_channel_telegram skills/channel-telegram/tests180uv run --project skills/channel-telegram python -m unittest discover -s skills/channel-telegram/tests -q181```