Skip to content
AGH RuntimeBridges

Telegram Setup

Create a BotFather bot, register an authenticated webhook through the AGH setup wizard, and prove topic-aware delivery.

Audience
Operators running durable agent work
Focus
Bridges guidance shaped for scanability, day-two clarity, and operator context.

This guide connects a Telegram bot to one AGH workspace. AGH uses Telegram webhooks, not long polling. The guided setup creates the disabled bridge, stores the bot token and webhook secret, and can register the webhook with Telegram before the provider begins accepting routed work.

How the Telegram bridge behaves

SurfaceAGH behavior
Inbound transportReceives Bot API webhook updates for messages, edited messages, channel posts, and edited channel posts.
AuthenticationRequires the configured X-Telegram-Bot-Api-Secret-Token when the webhook listener is enabled.
RoutingOne instance selects one shape: private chat (peer), ordinary group (group), or forum topic (group + thread). The guided default is forum-topic routing.
Reply contextUses the embedded reply_to_message snapshot first, then a bounded local cache. It does not fetch arbitrary chat history on a cache miss.
Outbound deliveryUses sendMessage, editMessageText, and deleteMessage. MarkdownV2 parse failures retry once as plain text.
Long responsesSplits after MarkdownV2 formatting at 4,096 UTF-16 code units, preserving ordered, numbered, fence-balanced continuations in the same topic.
Tool progressDefaults to new + accumulate with edits, typing actions, and reactions enabled. The final answer is sent separately in the same chat/topic.

Before you start

ValueRequiredWhere it comes from
Bot tokenyesBotFather after /newbot; format is <bot-id>:<token>.
Webhook secretyes hereOperator-provided or generated by guided setup; Telegram returns it in the webhook request header.
Public callback URLyesStable public HTTPS URL that forwards to the Telegram provider listener.
Local listener addressyesprovider_config.webhook.listen_addr or AGH_BRIDGE_TELEGRAM_LISTEN_ADDR.
AGH workspace IDyesWorkspace whose default agent receives the routed Telegram turn.
Chat administrationgroupsPermission to add the bot and, when required, change BotFather privacy mode.

Before configuring Telegram, prove that the locally built provider is installed and visible in the daemon catalog:

PROVIDER=telegram
agh extension status "$PROVIDER" -o json
curl -sS http://127.0.0.1:2123/api/bridges/providers | \
  jq --arg provider "$PROVIDER" '.providers[] | select(.extension_name == $provider)'

Continue only when the extension is enabled and the catalog row is not disabled or unhealthy. If either command has no provider, follow Install an in-tree provider first.

Do not paste the bot token into a URL stored in shell history or documentation. Guided setup sends it through the write-only binding operation.

Step 1: Create the bot in BotFather

  1. Open the verified @BotFather account in Telegram.
  2. Run /newbot.
  3. Choose the display name and unique username.
  4. Copy the bot token once and place it in your secret manager or current shell environment.
  5. Optionally use /setdescription, /setabouttext, and /setuserpic to make the bot recognizable.

The token controls the bot. If it leaks, revoke it in BotFather and replace the AGH binding before reenabling the bridge.

Step 2: Decide group privacy behavior

Telegram privacy mode changes which group messages the bot receives:

  • Keep privacy mode enabled when commands, replies, and explicit mentions are enough.
  • Disable privacy mode when the workspace agent must receive ordinary group conversation.

Use BotFather /setprivacy, select the bot, and choose the intended setting before testing a group. After a privacy change, remove and re-add the bot when Telegram requires membership refresh.

Privacy mode is a Telegram delivery filter. AGH dm_policy and route dimensions remain separate controls.

Step 3: Map the public and local endpoints

Telegram → https://bridge.example.com/telegram/support
         → reverse proxy or tunnel
         → http://127.0.0.1:18082/telegram/support

The proxy must preserve the secret-token header and the exact path. The provider accepts JSON POSTs up to its bounded request size and deduplicates update IDs.

Step 4: Run guided setup

Interactive setup prompts for the workspace, public URL, local path, routing shape, bot token, and webhook secret:

agh bridge setup telegram

The normal path creates the instance disabled, writes both secret bindings, and asks the provider to call Telegram setWebhook. The setup result includes:

  • bridge_instance_id;
  • masked secret slots;
  • webhook registration status and remediation;
  • the next verification command.

Copy the returned bridge ID:

BRIDGE_ID=brg_123

Select one routing shape

Every enabled route dimension is required on every inbound event. The wizard therefore asks for one closed routing shape and persists it as part of creation:

Wizard valueStored routing_policyAccepts
privateinclude_peer=true, group/thread falseOne-to-one bot chats
groupinclude_group=true, peer/thread falseOrdinary groups without a topic
foruminclude_group=true, include_thread=true, peer false — guided defaultForum topics

Choose the shape that you can prove immediately after setup. On a rerun with --instance, the wizard reads the stored policy and presents the matching value instead of silently resetting it.

Do not enable all three dimensions to make one instance “universal”; that policy would require peer, group, and thread IDs on every event. If the bot must serve more than one shape, create a separate bridge instance for each shape and give each instance its own callback path. See Message routing for the identity contract.

Headless setup

Strict JSON input is useful for agents and automation:

agh bridge setup telegram --json <<'JSON'
{
  "scope": "workspace",
  "workspace_id": "ws_8f33a913d23c4fd1",
  "routing_policy": {
    "include_peer": true,
    "include_group": false,
    "include_thread": false
  },
  "webhook_public_url": "https://bridge.example.com/telegram/support",
  "webhook_path": "/telegram/support",
  "bot_token": "123456:abcdefghijklmnopqrstuvwxyzABCDE",
  "webhook_secret": "replace-with-a-high-entropy-secret"
}
JSON

Strict JSON accepts the typed routing_policy, not the wizard-only routing_shape label. Use exactly one of these shapes:

{
  "include_peer": true,
  "include_group": false,
  "include_thread": false
}
{
  "include_peer": false,
  "include_group": true,
  "include_thread": false
}
{
  "include_peer": false,
  "include_group": true,
  "include_thread": true
}

The first object is private, the second is an ordinary group, and the third is a forum topic. Mixed, empty, unknown, and trailing JSON values are rejected before AGH creates or updates an instance. Secret values are masked in the result.

If webhook_secret is omitted during normal setup, AGH can generate and consume it without printing it. --reveal-generated-secrets is JSON-only and reveals only values generated during that one invocation; supplied and pre-existing secrets are never returned.

--print-only emits a safe setWebhook curl template. The template uses environment-variable placeholders rather than embedding the bot token in the URL or the secret in the command text:

agh bridge setup telegram --json --print-only < telegram-setup.json

Supply webhook_secret in the JSON input. If AGH generates it for a print-only run, add --reveal-generated-secrets; otherwise setup stops before writing anything because the external command would depend on a value the operator cannot recover.

Step 5: Verify the disabled bridge

agh bridge verify "$BRIDGE_ID" --json

Expected evidence:

  • provider.identity passes when the bot token succeeds against Telegram getMe;
  • webhook.secret passes when the secret binding exists;
  • webhook reachability is skipped while disabled.

The setup registration result proves Telegram accepted the setWebhook request. It does not prove that the callback remains reachable or that a chat is permitted by privacy and DM policy.

Step 6: Control direct-message access

Do this before enabling a private-chat bridge. An omitted or empty dm_policy normalizes to open and admits every Telegram user whose private-chat update passes webhook authentication. Use stable numeric Telegram user IDs where possible:

{
  "dm": {
    "allow_user_ids": ["123456789"],
    "allow_usernames": ["alice"],
    "paired_user_ids": [],
    "paired_usernames": []
  }
}

agh bridge create and agh bridge update do not currently expose --dm-policy. Use the Web editor or HTTP API procedure in Control direct-message access, then keep the bridge disabled until agh bridge get "$BRIDGE_ID" -o json reports the intended policy and lists.

pairing reads pre-populated paired_user_ids and paired_usernames, then falls back to allow_user_ids and allow_usernames; it does not start an interactive pairing flow. Usernames can change, so numeric IDs are the safer long-lived rule.

dm_policy applies only to private chats. BotFather privacy mode, group membership, Telegram's event delivery, and the bridge routing policy govern groups and channel posts. A group-only or forum bridge still rejects a private-chat route even when the DM policy is open.

Step 7: Enable and confirm the selected shape

agh bridge enable "$BRIDGE_ID"
agh bridge verify "$BRIDGE_ID" --json
agh bridge get "$BRIDGE_ID" -o json

The routing_policy in bridge get must exactly match the shape selected above. A mismatch is a setup failure even if provider identity checks pass. Do not continue to the provider test until this checkpoint is correct.

Step 8: Prove one real route and reply

Use only the subsection that matches this instance. Trigger one inbound event first, inspect the persisted route, and reuse its numeric provider IDs for the real outbound test.

Private chat — peer-only instance

Open the bot in Telegram, select Start, and send a short message. Then inspect the route:

agh bridge routes "$BRIDGE_ID" -o json

The route must contain peer_id and no required group/thread dimensions. Use that numeric chat ID:

agh bridge send-test "$BRIDGE_ID" \
  --message "AGH Telegram connection check" \
  --peer-id "123456789" \
  --json

The result should include a numeric Telegram remote message ID.

Ordinary group — group-only instance

  1. Add the bot to a test group.
  2. Confirm BotFather privacy mode matches the events you expect.
  3. Send an allowed command, reply, mention, or ordinary group message.
  4. Inspect agh bridge routes and confirm the route has group_id without a required thread.
agh bridge send-test "$BRIDGE_ID" \
  --message "AGH Telegram group check" \
  --group-id "-1001234567890" \
  --json

Forum topic — guided default

  1. Add the bot to a forum-enabled group.
  2. Send an allowed event inside the target topic.
  3. Inspect agh bridge routes and confirm both group_id and thread_id. Telegram's general forum topic normalizes to thread 1.

Reply-mode delivery reuses the route's topic:

agh bridge send-test "$BRIDGE_ID" \
  --message "AGH Telegram topic check" \
  --group-id "-1001234567890" \
  --thread-id "42" \
  --mode reply \
  --json

Do not substitute the group title or topic label for provider IDs.

Delivery and progress behavior

Telegram MarkdownV2 escaping happens before the provider measures the 4,096 UTF-16-unit wire limit. Long terminal answers are split on natural boundaries, with code fences closed and reopened where needed. If Telegram rejects a chunk's MarkdownV2 syntax, that chunk is retried once as plain text.

Telegram defaults to:

{
  "progress": {
    "tool_progress": "new",
    "grouping": "accumulate",
    "typing": true,
    "reactions": true
  }
}

Typing has no explicit clear operation in the Bot API. The provider stops issuing typing actions when content is delivered and closes progress state at terminal delivery.

For a quieter mobile surface:

agh bridge update "$BRIDGE_ID" \
  --delivery-progress off \
  --delivery-progress-typing=false \
  --delivery-progress-reactions=false

Configuration reference

FieldDefaultPurpose
webhook.public_urlnoneComplete public callback registered through Telegram setWebhook.
webhook.listen_addrAGH_BRIDGE_TELEGRAM_LISTEN_ADDRLocal listener shared by Telegram instances.
webhook.path/telegram/<bridge-id>Local callback path; each instance needs a unique path.
batching.delay_ms0Enables inbound batching when positive.
batching.split_delay_msdelay_msDelay after the configured batching threshold.
batching.split_threshold0Provider batching split threshold.
dm.allow_user_ids / paired_user_idsemptyStable user IDs admitted by allowlist or pairing.
dm.allow_usernames / paired_usernamesemptyUsername fallbacks; IDs are safer.

bot_token is required by the provider manifest. webhook_secret is optional at the catalog level, but the in-tree webhook listener requires it when enabled. Guided setup always establishes both.

The Telegram Bot API destination is an operator-owned process setting. Bridge configuration cannot redirect the bot token to another host.

Common operations

Rotate the bot token or webhook secret

Disable the bridge, then rerun setup against the existing instance:

agh bridge disable "$BRIDGE_ID"
agh bridge setup telegram --instance "$BRIDGE_ID"
agh bridge enable "$BRIDGE_ID"
agh bridge verify "$BRIDGE_ID" --json

Rerun setWebhook when the public URL or webhook secret changes. Telegram registration is rejected while the bridge is enabled so configuration cannot change underneath a live listener.

Inspect Telegram's webhook state

Use the Bot API getWebhookInfo through an operator-controlled secret-aware environment if setup reports a registration problem. Compare the returned URL with provider_config.webhook.public_url and inspect Telegram's last error. Do not paste the token-bearing request into tickets or logs.

Troubleshooting

SymptomWhat to check
Setup rejects the token formatCopy the full BotFather token, including numeric bot ID and colon.
provider.identity failsRevoke/reissue the BotFather token if needed, replace the binding, and rerun setup/verification.
Registration reports auth requiredThe bot token or webhook secret binding is missing or invalid; keep the bridge disabled while repairing it.
Telegram reports webhook delivery errorsCheck DNS, TLS, public path forwarding, and that the provider listener is running at the configured local address.
Provider returns unauthorized for inbound updatesConfirm setWebhook.secret_token and the AGH webhook_secret binding are identical and the proxy preserves the header.
Direct messages work but group chatter is silentCheck BotFather privacy mode, group membership, command/mention behavior, and AGH routing/DM policy.
Replies leave the forum topicInspect the route's message_thread_id and keep include_thread enabled for topic isolation.
MarkdownV2 errors appearThe provider retries the rejected chunk as plain text; inspect upstream content only if failures continue beyond that fallback.
A second Telegram instance degradesKeep one shared listener address and assign a distinct webhook.path to each instance.
Reactions fail while text succeedsTreat reactions as an optional affordance; disable them while checking Bot API permissions and target support.

Security notes

  • Treat the BotFather token as a root credential for the bot.
  • Use a high-entropy webhook secret and rotate it together on Telegram and AGH.
  • Prefer numeric user allowlists to mutable usernames.
  • Restrict the public proxy to the configured Telegram paths.
  • Never expose a token-bearing Bot API URL in logs, shell history, screenshots, or issue reports.

Completion checklist

  • The Telegram extension is installed, enabled, and healthy in the provider catalog.
  • setWebhook accepted the exact public URL and secret-token configuration.
  • Disabled identity and secret checks pass.
  • A private-chat bridge reports the intended dm_policy and sender lists before enablement.
  • Enabled public reachability passes or has an explained warning.
  • A real event of the selected shape creates a route in the intended workspace.
  • The route contains only the peer, group, or group-plus-thread dimensions selected during setup.
  • send-test returns a Telegram message ID.
  • The bridge returns to ready after one restart.

On this page