GitHub - coff33ninja/agent-switcher: OpenClaw plugin: per-message and sticky agent (persona) switching across all channels from the master agents.list · GitHub
Skip to content

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

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):

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

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

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.

"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.tspersistStickyRoute 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

About

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

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages