docspluginstransports

Transports

Transports are how you reach your agent — through an app you already use. Pick Telegram or Slack, switch it on, and the agent meets you there.

Ways to reach your agent

Your agent doesn't have an app of its own to download. Instead it shows up inside tools you already keep open — a Telegram chat, your inbox, a Slack message. Each of those connections is a transport. Switch on as many as you like; the same agent answers across all of them, and picks up the thread wherever you left it.

  • Telegram — chat with your agent one-on-one or pull it into a group. The quickest to set up.
  • Slack — message your agent in a Slack DM. Natural if your team already lives there.

Turning one on

Each app needs one credential from its side — a bot token from Telegram, a pair of tokens from Slack. You add that credential to your server through the admin dashboard, and it ties the app to a specific agent. From then on, messaging the app is messaging the agent. The page for each transport above walks through getting its credential, step by step.

The rest of this page is the builder's view — how a transport is defined, registered, and configured under the hood.

The contract

A transport is a value returned by defineTransport(). It declares a name, a Zod configSchema that validates its routes.json slice, a create() factory, and an admin descriptor that drives its form in the dashboard. See Creating a transport for the full contract — the runtime interface, address namespaces, and how a transport registers with the server.

Config & credentials

Per-transport configuration lives in the server's routes.json, keyed by transport name. Each value is an array of route entries, and each entry's address field binds the transport to one agent — adding or removing an entry is the only enablement switch. There is no separate per-agent toggle.

routes.json

json · per-transport credentials & bindings
{
  "telegram": [
    {
      "address": "assistant",
      "token": "123456:ABC-DEF...",
      "channel": "default"
    }
  ],
  "slack": [
    {
      "address": "assistant",
      "botToken": "xoxb-...",
      "appToken": "xapp-..."
    }
  ]
}

Three fields are common to every bundled transport's entry. The rest — credentials, allowlists — are transport-specific and documented on each leaf page.

FieldTypeDefaultEffect
addressstringrequiredCanonical agent address this entry routes to. Resolved against the server's address book at startup.
channelstringChannel preset for conversations this transport opens. Falls back to the agent's default channel when omitted.
streamingbooleantrueWhen true, responses stream with live edit-in-place. Set false to drop preview frames and deliver one sealed message. No admin field yet — hand-edit routes.json to flip it.
🔒 SECURITY
Transports have no separate secrets file. Unlike extensions, credentials live as fields inside the routes.json entry itself. The admin form masks the credential inputs, but on disk they sit in the same JSON as the routing — treat routes.json as a secret.

Lifecycle & hot-reload

At startup the server walks the registry against routes.json, validates each slice through the transport's schema, constructs the instances, and calls connect(). A failure in one transport is logged and skipped — it doesn't block the others. Editing routes.json triggers a reconcile: new transports connect before the old ones disconnect, so there's no outbound gap.

Authoring your own

For building a new transport — Discord, SMS, a custom webhook bridge — see Creating a transport.