🦞
Examples

Multi-Agent: Channel-Based Routing

Route different channels to different agents with isolated workspaces.

This walkthrough demonstrates OpenClaw's multi-agent routing — where WhatsApp messages go to one agent and Telegram messages go to another. Each agent gets its own workspace and can run a different model.

What you'll configure:

  • Provider in WebSocket mode (live connection to the gateway)
  • Gateway with token authentication
  • Shared agent defaults
  • WhatsApp as a "personal" channel
  • Telegram as a "work" channel
  • Health check data source

Why Multi-Agent?

A single agent works fine for personal use, but as your usage grows you might want:

  • Separation of concerns — a personal assistant on WhatsApp, a coding agent on Telegram
  • Different models — use Opus for complex tasks, Sonnet for quick replies
  • Isolated workspaces — prevent work files from mixing with personal projects
  • Per-channel policies — open DMs on one channel, strict allowlists on another

Step 1 — Provider with WebSocket Mode

Unlike the basic example, this config connects to a running gateway via WebSocket. Changes are applied immediately via the config.patch RPC.

terraform {
  required_providers {
    openclaw = {
      source = "registry.terraform.io/kylemclaren/openclaw"
    }
  }
}

provider "openclaw" {
  gateway_url = var.gateway_url
  token       = var.gateway_token
}

variable "gateway_url" {
  type    = string
  default = "ws://127.0.0.1:18789"
}

variable "gateway_token" {
  type      = string
  sensitive = true
  default   = ""
}

Step 2 — Gateway with Authentication

When the gateway is reachable over a network (e.g. via Tailscale), you should enable token auth to prevent unauthorized access.

resource "openclaw_gateway" "main" {
  port        = 18789
  bind        = "loopback"
  reload_mode = "hybrid"
  auth_mode   = "token"
  auth_token  = var.gateway_token
}

The auth_token here must match the token in the provider block. Both are marked sensitive so they never appear in plan output.

Step 3 — Shared Agent Defaults

These defaults apply to all agents. Individual agents can override any of these values.

resource "openclaw_agent_defaults" "shared" {
  model_primary    = "anthropic/claude-opus-4-6"
  timeout_seconds  = 600
  thinking_default = "low"

  sandbox_mode  = "non-main"
  sandbox_scope = "agent"
}

With sandbox_scope = "agent", each agent's sandbox is isolated — one agent can't access another's files.

Step 4 — Channel Configuration

Set up WhatsApp for personal use with a pairing flow, and Telegram for work with a strict allowlist.

resource "openclaw_channel_whatsapp" "personal" {
  dm_policy    = "pairing"
  allow_from   = ["+15555550123"]
  group_policy = "allowlist"
}
variable "telegram_bot_token" {
  type      = string
  sensitive = true
}

resource "openclaw_channel_telegram" "work" {
  enabled       = true
  bot_token     = var.telegram_bot_token
  dm_policy     = "allowlist"
  allow_from    = ["tg:111222333"]
  stream_mode   = "partial"
  history_limit = 50
}

Notice the different DM policies: WhatsApp uses "pairing" (scan-to-connect), while Telegram uses "allowlist" (only pre-approved user IDs can chat).

Step 5 — Health Check

Since we're connected via WebSocket, we can use the health data source to verify the gateway is operational:

data "openclaw_health" "gw" {}

output "gateway_status" {
  value = data.openclaw_health.gw.status
}

output "gateway_version" {
  value = data.openclaw_health.gw.version
}

This is useful as a precondition — if the gateway is down, terraform plan will fail early with a clear error instead of timing out.

Apply It

Start the gateway first (since we're using WebSocket mode):

openclaw gateway --port 18789

Then apply:

terraform init
terraform apply \
  -var="gateway_token=my-secret" \
  -var="telegram_bot_token=YOUR_TOKEN"

Full Source

See examples/multi-agent/main.tf for the complete file.

Next Steps

  • Add bindings to route specific channels to specific agents
  • Add Discord with rich action permissions — see Full-Stack Example
  • Set up session reset policies for clean conversation boundaries

On this page