Bridge Setup
Step-by-step setup for Slack, Discord, and Telegram bridge providers.
- Audience
- Operators running durable agent work
- Focus
- Bridges guidance shaped for scanability, day-two clarity, and operator context.
Set up a bridge in three layers:
- Install the provider extension so AGH can list it in
/api/bridges/providers. - Create a disabled workspace-scoped bridge instance with provider config and routing policy.
- Bind secrets, configure the platform webhook, then enable the bridge.
The examples use the daemon HTTP API for creation because provider config and secret bindings are API fields. The CLI bridge commands are useful for listing, inspecting, enabling, disabling, restarting, checking routes, and resolving delivery targets.
Prerequisites
- AGH daemon is running with HTTP available at
http://localhost:2123. - The workspace has a default agent, because bridge-initiated sessions use that default.
- The provider extension is installed and enabled.
- The platform can reach the provider webhook over HTTPS.
- Bridge secret values are available when you bind each slot. AGH stores them encrypted in the
vault and only exposes
secret_refmetadata afterward.
Set a workspace default agent:
agh workspace add /Users/you/src/support-bot \
--name support-bot \
--default-agent supportBuild and install local provider extensions from the repository checkout:
mkdir -p extensions/bridges/slack/bin \
extensions/bridges/discord/bin \
extensions/bridges/telegram/bin
go build -o extensions/bridges/slack/bin/slack ./extensions/bridges/slack
go build -o extensions/bridges/discord/bin/discord ./extensions/bridges/discord
go build -o extensions/bridges/telegram/bin/telegram ./extensions/bridges/telegram
agh extension install ./extensions/bridges/slack
agh extension install ./extensions/bridges/discord
agh extension install ./extensions/bridges/telegramRestart the daemon after installing extensions so the extension manager reloads the bridge provider catalog.
Confirm the providers are visible:
curl http://localhost:2123/api/bridges/providersEach provider record includes platform, extension_name, secret_slots, config_schema,
enabled, state, and health fields.
Shared instance fields
Use the same bridge shape for every platform:
| Field | Value to use |
|---|---|
scope | workspace for inbound session routing. |
workspace_id | The workspace ID returned by agh workspace list. |
platform | slack, discord, or telegram. |
extension_name | Installed extension name, usually the same as platform. |
enabled and status | Create as false and disabled, then enable after secrets are bound. |
routing_policy | Match the platform dimensions you want to isolate. |
provider_config.webhook.listen_addr | Local provider listener address. |
provider_config.webhook.path | Public URL path that the platform sends webhooks to. |
provider_config.dm | Optional allow_user_ids, allow_usernames, paired_user_ids, and paired_usernames. |
provider_config.batching | Optional inbound batching: delay_ms, split_delay_ms, split_threshold. |
Routing is the channel mapping model. AGH does not store a separate channel-to-agent map on the bridge instance. Pick the dimensions that the adapter emits for the conversation shape, and set the workspace default agent for new bridge sessions.
Bridge secret bindings use AGH-managed vault refs. The API accepts secret_value as write-only
input, stores it encrypted, and returns only the binding metadata:
curl -X PUT "http://localhost:2123/api/bridges/$BRIDGE_ID/secret-bindings/bot_token" \
-H "Content-Type: application/json" \
-d '{"secret_ref":"vault:bridges/'"$BRIDGE_ID"'/bot_token","secret_value":"xoxb-...","kind":"token"}'Restart or enable the bridge after changing secret bindings so the provider gets fresh launch material.
You can audit the stored refs without exposing their values through agh vault list --namespace bridges or Settings > Vault.
Slack
Slack bridges support Events API messages, slash commands, actions, reactions, and outbound
chat.postMessage, chat.update, and chat.delete delivery through the Slack Web API.
Slack platform setup
- Create or open a Slack app in the Slack app dashboard.
- Add a bot user and install the app to the workspace.
- Keep the Bot User OAuth token ready for the write-only binding request.
- Keep the app signing secret ready for the write-only binding request. The Slack provider verifies requests with Slack's signing-secret flow.
- Enable Event Subscriptions. Set the Request URL to your
public bridge URL, for example
https://bridge.example.com/slack/support. - Subscribe to the message events needed for your channels, private channels, DMs, or multi-person
DMs. Add Slack scopes required by those event subscriptions. Outbound replies need
chat:write. - If you use slash commands or interactivity, set those request URLs to the same bridge URL.
- Invite the app to every Slack channel where it should read and reply.
Slack AGH bridge instance
Create the bridge disabled:
curl -X POST http://localhost:2123/api/bridges \
-H "Content-Type: application/json" \
-d '{
"scope": "workspace",
"workspace_id": "ws_8f33a913d23c4fd1",
"platform": "slack",
"extension_name": "slack",
"display_name": "Slack support",
"enabled": false,
"status": "disabled",
"dm_policy": "allowlist",
"routing_policy": {
"include_peer": false,
"include_thread": true,
"include_group": true
},
"provider_config": {
"webhook": {
"listen_addr": "127.0.0.1:18081",
"path": "/slack/support"
},
"dm": {
"allow_user_ids": ["U0123456789"]
}
},
"delivery_defaults": {
"group_id": "C0123456789",
"thread_id": "1713200000.000100",
"mode": "direct-send"
}
}'Bind the required secrets:
BRIDGE_ID=brg_123
curl -X PUT "http://localhost:2123/api/bridges/$BRIDGE_ID/secret-bindings/bot_token" \
-H "Content-Type: application/json" \
-d '{"secret_ref":"vault:bridges/'"$BRIDGE_ID"'/bot_token","secret_value":"xoxb-...","kind":"token"}'
curl -X PUT "http://localhost:2123/api/bridges/$BRIDGE_ID/secret-bindings/signing_secret" \
-H "Content-Type: application/json" \
-d '{"secret_ref":"vault:bridges/'"$BRIDGE_ID"'/signing_secret","secret_value":"slack-signing-secret","kind":"secret"}'Enable the bridge:
curl -X POST "http://localhost:2123/api/bridges/$BRIDGE_ID/enable"Recommended Slack routing:
| Conversation shape | Policy |
|---|---|
| One session per Slack channel | include_peer: false, include_thread: false, include_group: true |
| One session per Slack channel thread | include_peer: false, include_thread: true, include_group: true |
| One session per Slack DM | include_peer: true, include_thread: false, include_group: false |
Discord
Discord bridges use the bot token for REST delivery and the application public key for interaction
webhook verification. The provider config can also include application_id; when set, the adapter
checks that the authenticated bot identity matches it.
Discord platform setup
- Create or open an app in the Discord Developer Portal.
- Create a bot and keep the bot token ready for the write-only binding request.
- Copy the application public key and keep it ready for the write-only binding request.
- Set the Interactions Endpoint URL
to your public bridge URL, for example
https://bridge.example.com/discord/support. - Discord validates the URL by sending a
PINGinteraction and requires Ed25519 signature validation headers. The provider handles both when the public key is bound. - Invite the bot with scopes and permissions for the surfaces you need. Common starting scopes are
botandapplications.commands; channel delivery needs permissions such as View Channel, Send Messages, and Read Message History.
Discord AGH bridge instance
Create the bridge disabled:
curl -X POST http://localhost:2123/api/bridges \
-H "Content-Type: application/json" \
-d '{
"scope": "workspace",
"workspace_id": "ws_8f33a913d23c4fd1",
"platform": "discord",
"extension_name": "discord",
"display_name": "Discord support",
"enabled": false,
"status": "disabled",
"dm_policy": "allowlist",
"routing_policy": {
"include_peer": false,
"include_thread": false,
"include_group": true
},
"provider_config": {
"application_id": "111122223333444455",
"webhook": {
"listen_addr": "127.0.0.1:18082",
"path": "/discord/support"
},
"dm": {
"allow_user_ids": ["222233334444555566"]
}
},
"delivery_defaults": {
"group_id": "666677778888999900",
"mode": "direct-send"
}
}'Bind the required secrets:
BRIDGE_ID=brg_456
curl -X PUT "http://localhost:2123/api/bridges/$BRIDGE_ID/secret-bindings/bot_token" \
-H "Content-Type: application/json" \
-d '{"secret_ref":"vault:bridges/'"$BRIDGE_ID"'/bot_token","secret_value":"discord-bot-token","kind":"token"}'
curl -X PUT "http://localhost:2123/api/bridges/$BRIDGE_ID/secret-bindings/public_key" \
-H "Content-Type: application/json" \
-d '{"secret_ref":"vault:bridges/'"$BRIDGE_ID"'/public_key","secret_value":"discord-public-key","kind":"public_key"}'Enable the bridge:
curl -X POST "http://localhost:2123/api/bridges/$BRIDGE_ID/enable"Recommended Discord routing:
| Conversation shape | Policy |
|---|---|
| One session per server channel | include_peer: false, include_thread: false, include_group: true |
| One session per server thread | include_peer: false, include_thread: true, include_group: true |
| Direct messages only | include_peer: true, include_thread: false, include_group: false |
Telegram
Telegram bridges support direct chats, groups, supergroups, and forum topics. The Bot API token is required. A webhook secret is optional, but recommended for public endpoints.
Telegram platform setup
- Create a bot with BotFather using
/newbot. - Keep the generated token ready for the write-only binding request.
- Generate a webhook secret and keep it ready for the write-only binding request.
- Create the bridge in AGH and bind both secrets.
- Register the Telegram webhook with the setWebhook
API. The URL path must match
provider_config.webhook.path. - Add the bot to the groups or forum topics where it should participate. If the bot needs every group message instead of commands, replies, and mentions, adjust the bot privacy setting in BotFather before relying on group routing.
Register the webhook after the provider endpoint is reachable. The example uses local shell variables only for the Telegram API call:
curl -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" \
-H "Content-Type: application/json" \
-d '{
"url": "https://bridge.example.com/telegram/support",
"secret_token": "'"$TELEGRAM_WEBHOOK_SECRET"'",
"drop_pending_updates": true
}'Telegram AGH bridge instance
Create the bridge disabled:
curl -X POST http://localhost:2123/api/bridges \
-H "Content-Type: application/json" \
-d '{
"scope": "workspace",
"workspace_id": "ws_8f33a913d23c4fd1",
"platform": "telegram",
"extension_name": "telegram",
"display_name": "Telegram support",
"enabled": false,
"status": "disabled",
"dm_policy": "open",
"routing_policy": {
"include_peer": false,
"include_thread": true,
"include_group": true
},
"provider_config": {
"webhook": {
"listen_addr": "127.0.0.1:18083",
"path": "/telegram/support"
},
"dm": {
"allow_usernames": ["maya_ops"]
}
},
"delivery_defaults": {
"group_id": "-1001234567890",
"thread_id": "42",
"mode": "direct-send"
}
}'Bind the required and optional secrets:
BRIDGE_ID=brg_789
curl -X PUT "http://localhost:2123/api/bridges/$BRIDGE_ID/secret-bindings/bot_token" \
-H "Content-Type: application/json" \
-d '{"secret_ref":"vault:bridges/'"$BRIDGE_ID"'/bot_token","secret_value":"telegram-bot-token","kind":"token"}'
curl -X PUT "http://localhost:2123/api/bridges/$BRIDGE_ID/secret-bindings/webhook_secret" \
-H "Content-Type: application/json" \
-d '{"secret_ref":"vault:bridges/'"$BRIDGE_ID"'/webhook_secret","secret_value":"telegram-webhook-secret","kind":"secret"}'Enable the bridge:
curl -X POST "http://localhost:2123/api/bridges/$BRIDGE_ID/enable"Recommended Telegram routing:
| Conversation shape | Policy |
|---|---|
| One session per direct chat | include_peer: true, include_thread: false, include_group: false |
| One session per group | include_peer: false, include_thread: false, include_group: true |
| One session per forum topic | include_peer: false, include_thread: true, include_group: true |
Validate the bridge
List bridge instances:
agh bridge listInspect the instance:
agh bridge get brg_123Check route creation after sending a test platform message:
agh bridge routes brg_123Dry-run a delivery target without sending to the platform:
agh bridge test-delivery brg_123 \
--message "target check" \
--group-id C0123456789 \
--mode direct-sendIf the bridge stays in auth_required or error, check these first:
| Symptom | Check |
|---|---|
auth_required after enable | Required secret binding exists, env var is set in the daemon process, and the token is valid. |
degraded with webhook invalid | Public webhook path reaches the provider listener and matches provider_config.webhook.path. |
| No route after sending a message | Instance is workspace-scoped, status is ready or degraded, and the platform event is one the adapter supports. |
| Routes exist but replies do not appear | Bot has send permissions, delivery target dimensions are valid, and bridge health does not show delivery failures. |
Use the CLI reference for command flags: