Skip to content
DocsHuman bridges

Connect agents

Human bridges

nats-hub is a communication layer, not a chat app. Humans join the bus through bridge workers — thin adapters that translate between NATS envelopes and a human-facing transport (Telegram, SMS, Email, Slack, Discord, Postiz, …).

Every bridge is the same shape. Only the transport import differs.

The two directions

text
Human → NATS:   human sends message → envelope published to inbox.<agent>
NATS  → Human:   envelope on inbox.<bridge> → forwarded to the human's chat

The bus stays agnostic: agents never know they're talking to a human. They just see envelopes on inbox.<identity>.

Email is built as a plugin with a gate in the middle (agents get their own addresses, and spam never wakes them): see EMAIL_PLUGIN.md.

Template: telegram_bridge.py

telegram_bridge.py is the reference implementation. It:

  1. Connects to NATS and subscribes to channel.inbox.<identity>.
  2. On each envelope → forwards extract_text(payload) to the Telegram chat (bot.send_message). This is the NATS → Human direction.
  3. Polls Telegram for inbound messages and publishes each as an envelope to inbox.<recipient-agent>. This is the Human → NATS direction.

Dry-run mode (no credentials needed)

Without the --telegram-token, the bridge runs in --dry-run mode: it logs every message it would send instead of calling the API. This lets you verify the NATS wiring end-to-end without a Telegram bot token:

bash
# terminal 1 — router
./target/debug/hub-server --db-path nats_hub.db

# terminal 2 — bridge (dry-run)
python3 telegram_bridge.py --identity human-bridge-telegram --dry-run

# terminal 3 — send a message that should reach the human
./target/debug/hub-publish --channel inbox.human-bridge-telegram \
    --to human-bridge-telegram --from agentA --message "Hello human"
# → bridge logs: [dry-run] → Telegram chat None: Hello human

Live mode

bash
export TG_TOKEN="<bot token>"
python3 telegram_bridge.py \
    --identity human-bridge-telegram \
    --chat-id 123456789 \
    --recipient orchestrator

Discord bridge: discord_bridge.py

discord_bridge.py is the same shape applied to Discord. It subscribes to channel.inbox.<identity>, forwards envelopes to a Discord channel (channel.send), and publishes inbound channel messages to inbox.<recipient-agent> via hub.send.<channel>.

It is an optional dependency bridge: it needs discord.py (pip install discord.py) only in live mode. Dry-run works without the SDK — the import is wrapped in try/except, so the script still loads and exercises the NATS wiring with no Discord credentials and no discord.py installed.

Note: the bot must have the Message Content Intent enabled on the Discord Developer Portal, and the intent is requested at startup (intents.message_content = True).

Dry-run mode (no credentials needed)

bash
# terminal 1 — router
./target/debug/hub-server --db-path nats_hub.db

# terminal 2 — bridge (dry-run; no token, no discord.py required)
python3 discord_bridge.py --identity human-bridge-discord --dry-run

# terminal 3 — send a message that should reach the human
./target/debug/hub-publish --channel inbox.human-bridge-discord \
    --to human-bridge-discord --from agentA --message "Hello human"
# → bridge logs: [dry-run] → Discord channel None: Hello human

Live mode

bash
export DISCORD_BOT_TOKEN="<bot token>"
export DISCORD_CHANNEL_ID="123456789012345678"
python3 discord_bridge.py \
    --identity human-bridge-discord \
    --recipient orchestrator

Live mode requires discord.py:

bash
pip install discord.py

If discord.py is missing and --dry-run is not set, the bridge exits with an install hint rather than crashing inside the import.

Adding another bridge (SMS, Email, Slack, …)

Copy telegram_bridge.py and swap the transport:

TransportInbound (Human→NATS)Outbound (NATS→Human)
Telegrampython-telegram-bot pollingbot.send_message
SMS (Twilio)Twilio webhook → publishTwilio REST messages.create
EmailIMAP idle → publishSMTP sendmail
Slackslack-bolt socket modeclient.chat_postMessage
Discorddiscord.py on_messagechannel.send
PostizPostiz webhook → publishpostiz post

Each bridge:

  • subscribes to channel.inbox.<identity>,
  • extracts a string from the payload (text / message / content / prompt),
  • forwards to the human,
  • and publishes inbound human messages to inbox.<recipient>.

No changes to the Rust bus are required — bridges are pure adapters.