Claw Switchboard

v1.0.0

Deterministic rules-based router for Telegram bots in OpenClaw.

claw-switchboard·runtime claw-switchboard·by @powerarchi
Code Pluginsource linkedCommunity code plugin. Review compatibility and verification before install.

README

Claw Switchboard

claw-switchboard is an OpenClaw channel plugin for running one Telegram bot with deterministic routing.

It is designed for this pattern:

  • one Telegram bot
  • no receptionist LLM
  • rules decide where a message goes
  • targets can be OpenClaw agents or external services

What It Does

claw-switchboard can route a single chat across multiple targets:

  • an OpenClaw general agent
  • a coding agent
  • an image backend
  • any other HTTP or static target you define

It supports:

  • Telegram Bot API long polling
  • deterministic routing by commands, prefixes, start keywords, regex, hasPhoto, and hasText
  • per-chat mode persistence
  • explicit mode switching commands
  • dynamic target labels in user-facing switch messages
  • OpenClaw agent targets
  • HTTP service targets
  • static text and static photo targets

Workspace Compatibility

claw-switchboard does not require multiple workspaces.

It works in all of these layouts:

  • single workspace, single agent
  • single workspace, multiple agents
  • multiple workspaces, multiple agents

The plugin routes by target and session, not by a requirement that each target must have a separate workspace.

Use multiple workspaces only when you want stronger separation between agents.

Route Priority

Incoming messages are resolved in this order:

  1. Explicit control command, such as /s, /s mode, /s code
  2. Strong route match from routes
  3. Active persisted target for the current chat
  4. fallbackTarget

This means:

  • a clear image request can override an active coding chat
  • follow-up messages can stay on the current agent
  • users can always force a switch with a command

Config Shape

The plugin is configured under:

{
  channels: {
    "claw-switchboard": {
    }
  }
}

Core fields:

  • enabled
  • botToken
  • defaultAgentId
  • fallbackTarget
  • sessionPrefix
  • polling
  • modeCommands
  • targets
  • routes

Example Config

This example shows:

  • general -> main OpenClaw agent
  • coding -> dedicated coding agent
  • image -> external HTTP image backend
{
  channels: {
    "claw-switchboard": {
      enabled: true,
      botToken: "YOUR_BOT_TOKEN",
      defaultAgentId: "main",
      fallbackTarget: "general",
      sessionPrefix: "claw-switchboard",
      polling: {
        timeoutSeconds: 25,
        retryDelayMs: 3000
      },
      modeCommands: {
        command: "s",
        clear: ["auto", "default", "clear"],
        status: ["mode", "status"],
        targets: {
          coding: ["code", "coding"],
          general: ["general"],
          image: ["image", "img"]
        }
      },
      targets: {
        general: {
          type: "agent",
          label: "General Assistant",
          persistMode: true,
          agentId: "main",
          provider: "openai-codex",
          model: "gpt-5.4-mini"
        },
        coding: {
          type: "agent",
          label: "Coding Assistant",
          persistMode: true,
          agentId: "coding",
          provider: "openai-codex",
          model: "gpt-5.3-codex"
        },
        image: {
          type: "http",
          label: "Image Service",
          persistMode: false,
          clearModeOnUse: true,
          url: "http://127.0.0.1:8080/generate",
          responseFormat: "switchboard-v1",
          timeoutMs: 30000
        }
      },
      routes: [
        {
          id: "image",
          priority: 200,
          match: {
            commands: ["image", "img", "imagine"],
            prefixes: ["image:", "img:", "imagine:", "draw:"],
            keywords: ["draw a", "draw me", "generate an image", "create an image"]
          },
          target: "image"
        },
        {
          id: "coding",
          priority: 120,
          match: {
            commands: ["code", "debug"],
            prefixes: ["code:", "coding:", "debug:"],
            keywords: ["write code", "fix bug", "debug", "python", "typescript", "fastapi"]
          },
          target: "coding"
        }
      ]
    }
  }
}

Sample Configurations

Sample 1: Single Workspace, One Agent, One Image Service

Use this when you have:

  • one OpenClaw agent
  • one shared workspace
  • one external image backend
{
  agents: {
    defaults: {
      model: {
        primary: "openai-codex/gpt-5.4-mini"
      },
      workspace: "/opt/openclaw/workspace"
    }
  },
  channels: {
    telegram: {
      enabled: false
    },
    "claw-switchboard": {
      enabled: true,
      botToken: "YOUR_BOT_TOKEN",
      defaultAgentId: "main",
      fallbackTarget: "general",
      modeCommands: {
        command: "s",
        clear: ["auto", "default", "clear"],
        status: ["mode", "status"],
        targets: {
          general: ["general"],
          image: ["image", "img"]
        }
      },
      targets: {
        general: {
          type: "agent",
          label: "General Assistant",
          persistMode: true,
          agentId: "main",
          provider: "openai-codex",
          model: "gpt-5.4-mini"
        },
        image: {
          type: "http",
          label: "Image Service",
          persistMode: false,
          clearModeOnUse: true,
          url: "http://127.0.0.1:8080/generate",
          responseFormat: "switchboard-v1"
        }
      },
      routes: [
        {
          id: "image",
          priority: 200,
          match: {
            commands: ["image", "img", "imagine"],
            prefixes: ["image:", "img:", "imagine:", "draw:"],
            keywords: ["draw a", "draw me", "generate an image", "create an image"]
          },
          target: "image"
        }
      ]
    }
  }
}

Sample 2: Single Workspace, Two Agents

Use this when you want:

  • one shared workspace
  • one general agent
  • one coding agent
  • no workspace split
{
  agents: {
    defaults: {
      model: {
        primary: "openai-codex/gpt-5.4-mini"
      },
      workspace: "/opt/openclaw/workspace"
    },
    list: [
      {
        id: "coding",
        model: {
          primary: "openai-codex/gpt-5.3-codex"
        }
      }
    ]
  },
  channels: {
    telegram: {
      enabled: false
    },
    "claw-switchboard": {
      enabled: true,
      botToken: "YOUR_BOT_TOKEN",
      defaultAgentId: "main",
      fallbackTarget: "general",
      modeCommands: {
        command: "s",
        clear: ["auto", "default", "clear"],
        status: ["mode", "status"],
        targets: {
          coding: ["code", "coding"],
          general: ["general"],
          image: ["image", "img"]
        }
      },
      targets: {
        general: {
          type: "agent",
          label: "General Assistant",
          persistMode: true,
          agentId: "main",
          provider: "openai-codex",
          model: "gpt-5.4-mini"
        },
        coding: {
          type: "agent",
          label: "Coding Assistant",
          persistMode: true,
          agentId: "coding",
          provider: "openai-codex",
          model: "gpt-5.3-codex"
        },
        image: {
          type: "http",
          label: "Image Service",
          persistMode: false,
          clearModeOnUse: true,
          url: "http://127.0.0.1:8080/generate",
          responseFormat: "switchboard-v1"
        }
      },
      routes: [
        {
          id: "image",
          priority: 200,
          match: {
            commands: ["image", "img", "imagine"],
            prefixes: ["image:", "img:", "imagine:", "draw:"],
            keywords: ["draw a", "draw me", "generate an image", "create an image"]
          },
          target: "image"
        },
        {
          id: "coding",
          priority: 120,
          match: {
            commands: ["code", "debug"],
            prefixes: ["code:", "coding:", "debug:"],
            keywords: ["write code", "fix bug", "debug", "python", "typescript", "fastapi"]
          },
          target: "coding"
        }
      ]
    }
  }
}

Sample 3: Multiple Workspaces, Two Agents

Use this when you want:

  • one general workspace
  • one coding workspace
  • stronger separation between agents
{
  agents: {
    defaults: {
      model: {
        primary: "openai-codex/gpt-5.4-mini"
      },
      workspace: "/opt/openclaw/workspace-main"
    },
    list: [
      {
        id: "main",
        workspace: "/opt/openclaw/workspace-main",
        model: {
          primary: "openai-codex/gpt-5.4-mini"
        }
      },
      {
        id: "coding",
        workspace: "/opt/openclaw/workspace-coding",
        model: {
          primary: "openai-codex/gpt-5.3-codex"
        }
      }
    ]
  },
  channels: {
    telegram: {
      enabled: false
    },
    "claw-switchboard": {
      enabled: true,
      botToken: "YOUR_BOT_TOKEN",
      defaultAgentId: "main",
      fallbackTarget: "general",
      modeCommands: {
        command: "s",
        clear: ["auto", "default", "clear"],
        status: ["mode", "status"],
        targets: {
          coding: ["code", "coding"],
          general: ["general"],
          image: ["image", "img"]
        }
      },
      targets: {
        general: {
          type: "agent",
          label: "General Assistant",
          persistMode: true,
          agentId: "main",
          provider: "openai-codex",
          model: "gpt-5.4-mini"
        },
        coding: {
          type: "agent",
          label: "Coding Assistant",
          persistMode: true,
          agentId: "coding",
          provider: "openai-codex",
          model: "gpt-5.3-codex"
        },
        image: {
          type: "http",
          label: "Image Service",
          persistMode: false,
          clearModeOnUse: true,
          url: "http://127.0.0.1:8080/generate",
          responseFormat: "switchboard-v1"
        }
      },
      routes: [
        {
          id: "image",
          priority: 200,
          match: {
            commands: ["image", "img", "imagine"],
            prefixes: ["image:", "img:", "imagine:", "draw:"],
            keywords: ["draw a", "draw me", "generate an image", "create an image"]
          },
          target: "image"
        },
        {
          id: "coding",
          priority: 120,
          match: {
            commands: ["code", "debug"],
            prefixes: ["code:", "coding:", "debug:"],
            keywords: ["write code", "fix bug", "debug", "python", "typescript", "fastapi"]
          },
          target: "coding"
        }
      ]
    }
  }
}

Target Types

agent

Routes a message to an OpenClaw agent.

Useful fields:

  • label
  • agentId
  • provider
  • model
  • persistMode
  • clearModeOnUse
  • stateless
  • timeoutMs
  • sessionKeyPrefix

agent targets run through the host OpenClaw runtime. Per-target gatewayUrl and gatewayToken overrides are not supported.

By default, agent targets are stateless. claw-switchboard does not pass a persistent per-chat sessionFile to the embedded agent and generates a one-time session key for each request. That prevents the plugin from creating long-lived per-chat sessions/*.jsonl transcript files and avoids reusing prior chat context through the same session key.

Depending on the host OpenClaw runtime, a stateless request may still create a short-lived file under sessions/<agentId>/ephemeral/ while the run is in progress. Those files are treated as temporary runtime artifacts and are deleted after the request completes.

Set stateless: false only when you explicitly want OpenClaw's persistent per-chat session behavior for that target. In that mode, sessionKeyPrefix applies again and the plugin will create a per-chat sessions/*.jsonl file for the target.

http

Routes a message to an external HTTP service.

Useful fields:

  • label
  • url
  • method
  • headers
  • timeoutMs
  • responseFormat
  • persistMode
  • clearModeOnUse

responseFormat controls which response shape is preferred first during parsing. The plugin still falls back to the other supported shape for compatibility.

For switchboard-v1, the plugin sends a JSON payload that includes:

  • top-level targetId, routeId, reason, text, prompt
  • nested input.chatId, input.messageId, input.text, input.prompt
  • raw update
  • raw message

static-text

Returns a fixed text response.

static-photo

Returns a fixed local photo or base64 photo.

Mode Persistence

Two target fields control whether a routed message becomes the chat's active mode:

  • persistMode
  • clearModeOnUse

In practice:

  • persistMode: true means "keep this target as the active mode after this request"
  • clearModeOnUse: true means "clear the previous active mode when this target is used"

You can think of them as:

  • persistMode controls what happens after success
  • clearModeOnUse controls what happens to the previous mode before the request runs

persistMode

When persistMode is true, a successful request to that target becomes the active mode for the chat.

That means:

  • follow-up messages stay on that target
  • the chat keeps using that target until the user switches away or a stronger route match takes over

Typical use:

  • general assistant
  • coding assistant
  • any target that should own a continuing conversation

Example:

general: {
  type: "agent",
  label: "General Assistant",
  persistMode: true,
  clearModeOnUse: false
}

Result:

  • message routes to general
  • general becomes the active mode
  • the next follow-up stays on general unless the user switches or a stronger route match wins

clearModeOnUse

When clearModeOnUse is true, the plugin clears the previously active mode before using this target.

That means:

  • the current request still goes to this target
  • but the previous mode is not kept afterward
  • the next unrelated message falls back to normal routing unless another target persists itself

Typical use:

  • image generation
  • one-off utility services
  • temporary service calls that should not own the conversation

Example:

image: {
  type: "http",
  label: "Image Service",
  persistMode: false,
  clearModeOnUse: true
}

Result:

  • message routes to image
  • any previous active mode is cleared
  • image does not become the new active mode
  • the next unrelated message falls back to normal routing

Recommended Combinations

Multi-shot conversational target

{
  persistMode: true,
  clearModeOnUse: false
}

Use this for targets that should keep the conversation:

  • general agent
  • coding agent

One-shot service target

{
  persistMode: false,
  clearModeOnUse: true
}

Use this for targets that should handle one request and then release the chat:

  • image generation
  • conversion services
  • one-off automations

Stateless target without forced clear

{
  persistMode: false,
  clearModeOnUse: false
}

Use this only if you want a target to stay stateless without explicitly clearing the current mode.

In most cases, one-shot service target is the safer choice.

Quick Decision Guide

Use persistMode: true when:

  • users will probably send follow-up messages to the same target
  • you want the chat to stay with the same assistant

Use clearModeOnUse: true when:

  • the target is a one-off tool or service
  • you do not want the previous active mode to keep controlling the chat afterward

Typical patterns:

  • general or coding: persistMode: true, clearModeOnUse: false
  • image one-shot service: persistMode: false, clearModeOnUse: true
  • image multi-shot mode: persistMode: true, clearModeOnUse: false

Telegram Commands

The command prefix is configurable with:

modeCommands.command

If you set:

command: "s"

then claw-switchboard adds these switchboard commands:

  • /s
  • /s mode
  • /s code
  • /s general
  • /s image

The Telegram command menu also keeps OpenClaw's native commands such as /model, /models, /status, /help, /new, and /reset.

Meaning:

  • /s
    • return to default switchboard routing
  • /s mode
    • show the current mode and runtime
  • /s code
    • lock the chat to the configured coding target
  • /s general
    • lock the chat to the configured general target
  • /s image
    • send this chat into the configured image target

Important:

  • /new and /reset use OpenClaw's native session behavior
  • use /s or /s clear when you want to clear switchboard mode only

The exact subcommands come from:

modeCommands.targets

So they are configuration-driven, not hardcoded to a specific agent name.

User-Facing Behavior

When a target changes, the plugin can send a dynamic switch message such as:

Switched to Coding Assistant.
Runtime: openai-codex/gpt-5.3-codex.

Those values come from the configured target:

  • label
  • provider
  • model

Example for an HTTP service:

Switched to Image Service.
Runtime: http service.

Image Route Notes

If an image target is configured like this:

{
  persistMode: false,
  clearModeOnUse: true
}

then it behaves like a one-shot route:

  • image request goes to the image backend
  • previous persisted agent mode is cleared
  • the next unrelated message falls back to normal routing

This is usually the right behavior for image generation.

If you want image generation to behave like a continuing mode instead, use:

{
  persistMode: true,
  clearModeOnUse: false
}

That makes image requests multi-shot and keeps follow-up messages on the image target until the user switches away.

One-Shot Image Sample

image: {
  type: "http",
  label: "Image Service",
  persistMode: false,
  clearModeOnUse: true,
  url: "http://127.0.0.1:8080/generate",
  responseFormat: "switchboard-v1"
}

Behavior:

  • Draw a small cat -> routes to image
  • How is the weather today? -> falls back to general

Multi-Shot Image Sample

image: {
  type: "http",
  label: "Image Service",
  persistMode: true,
  clearModeOnUse: false,
  url: "http://127.0.0.1:8080/generate",
  responseFormat: "switchboard-v1"
}

Behavior:

  • /s image -> switches the chat to image
  • Draw a small cat -> routes to image
  • Make it blue -> stays on image

Install

Link-install locally:

openclaw plugins install -l ./claw-switchboard

Inspect and verify:

openclaw plugins inspect claw-switchboard
openclaw plugins doctor

Local Test Flow

  1. Keep the stock Telegram channel disabled.
  2. Enable channels["claw-switchboard"].
  3. Configure at least one fallback target and one bot token.
  4. Start OpenClaw:
openclaw gateway
  1. If you use an external image route, start that backend too.

Cron Delivery

claw-switchboard now supports outbound text delivery for run summaries and cron announce messages through the same Telegram bot token.

In the delivery UI:

  • set Channel to Claw Switchboard
  • set To to the Telegram chat id

Accepted To formats:

  • YOUR_CHAT_ID
  • telegram:YOUR_CHAT_ID
  • claw-switchboard:YOUR_CHAT_ID

Repeated known prefixes are also normalized, so values like claw-switchboard:telegram:YOUR_CHAT_ID are accepted too.

For direct messages to your phone, the raw numeric chat id is the safest choice.

Example Conversations

General

User:

hello

Expected:

  • routes to general
  • reply comes from the general agent

Coding Follow-Up

User:

Write a compare-two-numbers program
Rewrite it in English

Expected:

  • first message routes to coding
  • second message stays on coding

Image One-Shot

User:

Draw a small cat
How is the weather today?

Expected:

  • first message routes to image
  • second message does not stay on image
  • second message falls back to general

Image Multi-Shot

Target config:

image: {
  type: "http",
  label: "Image Service",
  persistMode: true,
  clearModeOnUse: false,
  url: "http://127.0.0.1:8080/generate",
  responseFormat: "switchboard-v1"
}

User:

/s image
Draw a small cat
Make it blue

Expected:

  • the chat switches to image
  • the first image request routes to image
  • the follow-up request also stays on image

Explicit Switch

User:

/s code
Write a FastAPI hello world
/s
Hello

Expected:

  • /s code switches the chat to the configured coding target
  • coding request stays on the coding target
  • /s clears the mode
  • Hello returns to default switchboard routing

Publish Readiness

Current publish check summary:

  • no user-specific agent names are hardcoded in routing logic
  • Telegram mode aliases are config-driven
  • target labels are config-driven
  • runtime labels are config-driven
  • plugin behavior is generic across agent and HTTP targets

Capabilities

Channels
claw-switchboard
configSchema
Yes
Executes code
Yes
HTTP routes
0
Runtime ID
claw-switchboard
Setup entry
Yes

Compatibility

Built With Open Claw Version
2026.3.28
Min Gateway Version
>=2026.3.28
Plugin Api Range
2026.3.28

Verification

Tier
source linked
Scope
artifact only
Summary
Validated package structure and linked the release to source metadata.
Commit
claw-switchb
Tag
claw-switchboard
Provenance
No
Scan status
suspicious

Tags

latest
1.0.0