← Back to Plugins
Channels

Agent Switcher

coff33ninja By coff33ninja 👁 139 views ▲ 0 votes

OpenClaw plugin: per-message and sticky agent (persona) switching across all channels from the master agents.list

GitHub

Install

npm install
npm

Configuration Example

{
  "plugins": {
    "allow": ["clawrouter", "whatsapp", "agent-switcher"],
    "entries": {
      "agent-switcher": {
        "enabled": true
      }
    }
  }
}

README

# Agent Switcher

Per-message and **sticky** agent (persona) switching across all OpenClaw channels, driven by the user's master `agents.list`.

The plugin catalogs every agent defined in your OpenClaw config (the master + all subs) at load time and lets you route any conversation to a specific persona — either for a single message (one-shot) or persistently (sticky, survives restarts).

- **Repo-bound**: the agent catalog is populated dynamically from `agents.list` in `openclaw.json` — no hardcoded agent names.
- **One-shot** routing via keyword/prefix rules.
- **Sticky** routing via `/agent <name>` or sticky keyword rules, persisted through a gateway restart.
- **Reset** back to the master agent at any time.

---

## Requirements

- OpenClaw gateway `>= 2026.7.1` (`compat.minGatewayVersion` / `peerDependencies`).
- A user `agents.list` configured in `openclaw.json` containing `main` (the master) plus any number of subs.

---

## Installation

Copy the `agent-switcher/dist` build output into your OpenClaw extensions directory and enable it:

```
~/.openclaw/extensions/agent-switcher/dist/   # the .js build output
```

Add the plugin to your plugin entries (or allow-list):

```jsonc
{
  "plugins": {
    "allow": ["clawrouter", "whatsapp", "agent-switcher"],
    "entries": {
      "agent-switcher": {
        "enabled": true
      }
    }
  }
}
```

If you distribute/install from source, run the build first:

```bash
npm install
npm run build          # tsc -p tsconfig.json
```

> **Note for gateway installs:** the extensions dir holds a *copy* of `dist/`. After rebuilding, re-copy the new `dist/*.js` (including `agents.js`) to the extensions target and restart the gateway.

---

## Usage

### Commands

| Command | Behavior |
|---------|----------|
| `/agent` | Show the currently routed agent for this conversation (or master if none). |
| `/agent <name>` | **Stickily** route this conversation to that agent. Accepts the agent id or name, case-insensitive. `master` / `default` map to the master. |
| `/agent off` · `/agent reset` · `/agent clear` | Reset routing back to the master agent for this conversation. |
| `/agents` | List the full catalog (master + all subs) with their roles. |

Any `/agent <unknown>` returns the list of known agent ids.

### Automatic rules

Configured via `configSchema` in the plugin (`defaultAgent` + an ordered `rules` array). Each rule can match on:

- `channels` — channel ids
- `conversations` — conversation ids
- `senders` — sender ids
- `prefix` — message begins with one of these (one-shot)
- `keywords` — message contains any of these (one-shot)
- `sticky` — message contains any of these and routes **sticky** (persistent)

Rules are evaluated in order; the first hard match (prefix/keyword → one-shot, or sticky keyword → sticky) wins.

```jsonc
"config": {
  "defaultAgent": "main",
  "rules": [
    { "id": "coding", "agent": "coder", "keywords": ["refactor", "write a function"] },
    { "id": "sci", "agent": "scientist", "prefix": ["research"], "sticky": ["deep dive"] }
  ]
}
```

---

## How the catalog is built

`src/agents.ts` builds the catalog from (in priority order):

1. `api.config.agents.list` — the full merged config handed to the plugin at registration.
2. `api.config.agents` (if the list lives directly under `agents`).
3. A file fallback reading `openclaw.json` (via `api.runtime.fs`) next to the gateway root.

The **master** is `defaultAgent` (or the agent flagged `master: true`, or `main`). Every other entry is a sub. `resolveId` matches ids/names case-insensitively and maps `master`/`default`/`off` → the master id.

---

## How sticky persistence works

`src/sticky.ts` — `persistStickyRoute` binds a conversation to an agent via the core channel session surface
(`core.channel.session.updateLastRoute` / `session.updateLastRoute`), writing a `ChannelRouteRef` for
`agent:channel:direct:conversationId`. Because the route policy becomes `lastRoutePolicy = "session"`, the binding
**survives a gateway restart**. The in-process `state.json` map is a fast-path mirror, with the core route as the
authoritative backstop.

---

## Command / hook contract (OpenClaw plugin API)

- Hooks use `api.on("message_received", (event, ctx) => ...)` (not the legacy `registerHook`).
- Commands use `api.registerCommand({ name, description, acceptsArgs, handler(ctx) })`.
- `ctx.channelId`/`ctx.conversationId`/`ctx.sessionKey` locate the conversation; `event.content`/`event.from` carry the message.

---

## File layout

```
agent-switch/
├── README.md
├── openclaw.plugin.json     # plugin manifest (id, version, configSchema)
├── package.json             # npm metadata + build script
├── tsconfig.json
├── src/
│   ├── index.ts             # registration: hooks + commands
│   ├── agents.ts            # dynamic catalog from agents.list
│   ├── rules.ts             # ordered match rules (one-shot / sticky)
│   ├── sticky.ts            # persistStickyRoute (core session route)
│   ├── handoff.ts           # handoff message formatting
│   └── sdk-types.d.ts       # light type hints for the plugin surface
├── dist/                    # compiled output (loaded by the gateway)
└── docs/
```

---

## License

MIT
channels

Comments

Sign in to leave a comment

Loading comments...