🦞
Examples

Full-Stack: Every Resource Type

A comprehensive config exercising all 18 resources — agents, channels, plugins, skills, hooks, cron, and tools.

This walkthrough is the kitchen-sink example — it uses every resource type the provider offers. Use it as a reference for any resource you need, or as a starting point for a production-grade deployment.

What you'll configure:

  • Gateway with Tailscale exposure
  • Two agents ("home" and "work") with identity customization
  • Bindings to route channels to agents
  • Five chat channels (WhatsApp, Telegram, Discord, Slack, Signal)
  • Session lifecycle with daily resets
  • Message handling with acknowledgment reactions
  • A plugin, a skill, webhooks, cron, and tools

Variables

This config uses several sensitive variables for bot tokens and API keys. Define them in a terraform.tfvars file or pass them via -var:

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

variable "telegram_bot_token" {
  type      = string
  sensitive = true
}

variable "discord_bot_token" {
  type      = string
  sensitive = true
}

variable "slack_bot_token" {
  type      = string
  sensitive = true
}

variable "slack_app_token" {
  type      = string
  sensitive = true
}

variable "gemini_api_key" {
  type      = string
  sensitive = true
}

Gateway with Tailscale

Expose the gateway over your tailnet with tailscale_mode = "serve". This makes it accessible to other devices on your Tailscale network without opening ports to the public internet.

resource "openclaw_gateway" "main" {
  port           = 18789
  bind           = "loopback"
  reload_mode    = "hybrid"
  tailscale_mode = "serve"
}

Two Agents with Identities

Define a "home" agent (default) and a "work" agent with different models and sandbox settings. The identity_* fields control how the agent presents itself in chat.

resource "openclaw_agent" "home" {
  agent_id      = "home"
  default_agent = true
  name          = "Molty"
  workspace     = "~/.openclaw/workspace-home"
  model         = "anthropic/claude-opus-4-6"

  identity_name  = "Molty"
  identity_emoji = "\ud83e\udd9e"
  identity_theme = "helpful space lobster"

  mention_patterns = ["@openclaw", "molty"]
}

resource "openclaw_agent" "work" {
  agent_id  = "work"
  name      = "Work Agent"
  workspace = "~/.openclaw/workspace-work"
  model     = "anthropic/claude-sonnet-4-5"

  sandbox_mode  = "all"
  sandbox_scope = "session"

  tools_profile = "coding"
  tools_deny    = ["canvas"]
}

Key differences:

Home AgentWork Agent
ModelOpus (max capability)Sonnet (fast + capable)
Sandboxnon-main (inherited)all (everything sandboxed)
ToolsDefault profilecoding profile, canvas denied
IdentityCustom name + emojiPlain

Agent Routing with Bindings

Bindings connect channels to agents. Here, WhatsApp goes to the home agent and Telegram goes to the work agent:

resource "openclaw_binding" "home_wa" {
  agent_id         = openclaw_agent.home.agent_id
  match_channel    = "whatsapp"
  match_account_id = "personal"
}

resource "openclaw_binding" "work_tg" {
  agent_id      = openclaw_agent.work.agent_id
  match_channel = "telegram"
}

Notice how agent_id references the agent resource directly — Terraform handles the dependency ordering automatically.

Five Channels

WhatsApp and Telegram

resource "openclaw_channel_whatsapp" "main" {
  dm_policy          = "pairing"
  allow_from         = ["+15555550123"]
  send_read_receipts = true
  group_policy       = "allowlist"
}

resource "openclaw_channel_telegram" "main" {
  enabled       = true
  bot_token     = var.telegram_bot_token
  dm_policy     = "pairing"
  allow_from    = ["tg:123456789"]
  stream_mode   = "partial"
  reply_to_mode = "first"
  history_limit = 50
}

Discord with Actions

Discord supports granular action permissions — reactions, threads, pins, and search can each be toggled independently:

resource "openclaw_channel_discord" "main" {
  enabled        = true
  token          = var.discord_bot_token
  dm_policy      = "pairing"
  allow_from     = ["steipete", "1234567890123"]
  history_limit  = 20
  reply_to_mode  = "off"

  actions_reactions = true
  actions_messages  = true
  actions_threads   = true
  actions_pins      = true
  actions_search    = true
}

Slack (Socket Mode)

Slack requires both a bot token and an app token for Socket Mode:

resource "openclaw_channel_slack" "main" {
  enabled        = true
  bot_token      = var.slack_bot_token
  app_token      = var.slack_app_token
  dm_policy      = "pairing"
  allow_from     = ["U123", "U456"]
  history_limit  = 50
  reply_to_mode  = "off"
  reaction_notifications = "own"
}

Signal

resource "openclaw_channel_signal" "main" {
  enabled                = true
  dm_policy              = "pairing"
  reaction_notifications = "own"
  history_limit          = 50
}

Session Lifecycle

Control when conversations reset. Daily resets at 4 AM keep context fresh, and users can trigger a reset manually with /new or /reset:

resource "openclaw_session" "config" {
  dm_scope           = "per-channel-peer"
  reset_mode         = "daily"
  reset_at_hour      = 4
  reset_idle_minutes = 120
  reset_triggers     = ["/new", "/reset"]
}

dm_scope = "per-channel-peer" means each user on each channel gets their own session — your WhatsApp conversation doesn't share context with your Telegram one.

Message Handling

Configure how the gateway processes messages — acknowledgment reactions, queue behavior, and debounce timing:

resource "openclaw_messages" "config" {
  response_prefix     = "\ud83e\udd9e"
  ack_reaction        = "\ud83d\udc40"
  ack_reaction_scope  = "group-mentions"
  queue_mode          = "collect"
  queue_debounce_ms   = 1000
  queue_cap           = 20
  inbound_debounce_ms = 2000
}
  • ack_reaction — the agent reacts with eyes when it starts processing a message
  • queue_mode = "collect" — if multiple messages arrive quickly, they're collected into a single agent prompt
  • inbound_debounce_ms = 2000 — waits 2 seconds for additional messages before processing

Automation: Plugins, Skills, Hooks, Cron

Plugin

resource "openclaw_plugin" "voice_call" {
  plugin_id = "voice-call"
  enabled   = true
  config_json = jsonencode({
    provider = "twilio"
  })
}

Skill

resource "openclaw_skill" "nano_banana" {
  skill_name = "nano-banana-pro"
  enabled    = true
  api_key    = var.gemini_api_key
}

Webhooks

resource "openclaw_hook" "ingress" {
  enabled             = true
  token               = "shared-webhook-secret"
  path                = "/hooks"
  default_session_key = "hook:ingress"
}

Cron

resource "openclaw_cron" "config" {
  enabled             = true
  max_concurrent_runs = 2
  session_retention   = "24h"
}

Tool Access Control

Lock down which tools agents can use. The coding profile is a sensible preset, and you can layer on specific denials:

resource "openclaw_tools" "config" {
  profile          = "coding"
  deny             = ["canvas"]
  elevated_enabled = true
  browser_enabled  = true
}

Data Sources

Round it out with data sources for verification:

data "openclaw_config" "current" {}
data "openclaw_health" "gw" {}

output "config_hash" {
  value = data.openclaw_config.current.hash
}

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

Apply It

terraform init
terraform apply -var-file="secrets.tfvars"

Where secrets.tfvars contains:

telegram_bot_token = "123456:ABC-DEF..."
discord_bot_token  = "MTIzNDU2..."
slack_bot_token    = "xoxb-..."
slack_app_token    = "xapp-..."
gemini_api_key     = "AIza..."

Full Source

See examples/full-stack/main.tf for the complete file.

On this page