I tried to do something that sounds trivial:
- Add a second Telegram bot
- Map it to a second isolated agent (separate workspace, SOUL, identity, memory)
- Keep everything deterministic: Bot A → Agent A, Bot B → Agent B
And yet I managed to hit a surprisingly nasty failure mode:
After adding the second bot, my original bot stopped replying — or worse, it replied using the wrong agent’s workspace and “forgot” all our previous context.
This post is a practical write-up of what went wrong, why it’s so easy to mess up, and the clean configuration that fixes it.
What I wanted
I wanted the “two brains” setup:
- Bot A → Agent A (default)
- Bot B → Agent B (separate persona)
Each agent should have:
- its own
workspace - its own
SOUL.md,IDENTITY.md,USER.md - its own session store and memory
The symptom that revealed the bug
The first symptom was obvious:
- The new bot replied with a pairing code
- The old bot went completely silent
The second symptom was more subtle but more important:
- Sometimes I could still chat, but the assistant didn’t remember previous context
- The “current workspace” clearly wasn’t the one I expected
That’s when it clicked: this wasn’t a Telegram problem — it was a routing problem.
Two concepts you must not mix up
OpenClaw has two parallel ideas:
- Multi-account channels (e.g., Telegram with multiple bot tokens)
- Multi-agent routing (multiple isolated agents in one gateway)
They only work cleanly if you commit to the canonical representations.
1) Multi-account: don’t keep one token at the root
When you go multi-account, you should put all accounts under accounts, including the original one as default.
If you keep the old bot token at channels.telegram.botToken while adding a new one under channels.telegram.accounts.secondary, you’ve created a “two-level representation” for the same concept.
Even if it looks harmless, it’s a trap: runtime behavior becomes confusing, and you can end up with only the accounts.* bot actually polling.
The canonical pattern is:
channels: {
telegram: {
accounts: {
default: { botToken: "<OLD>" },
secondary: { botToken: "<NEW>" }
}
}
}
This is exactly how the configuration reference describes multi-account for all channels.
2) Default agent: routing fallback is deterministic
When a message doesn’t match any explicit binding, OpenClaw falls back to a default agent in a very specific order:
agents.list[].default == true- else the first agent in
agents.list - else
main
If you accidentally end up with only one agent in agents.list (or you forget to mark your intended agent as default), you can route messages into the wrong brain — and it will look like the assistant lost memory.
It didn’t lose memory. You just talked to the wrong agent.
The clean fix (canonical config)
Here’s the minimal, stable configuration that matches the mental model “two bots, two agents”.
Telegram: two bot tokens, both inside accounts
channels: {
telegram: {
enabled: true,
dmPolicy: "pairing",
groupPolicy: "allowlist",
streaming: "off",
accounts: {
default: {
name: "Primary Bot",
enabled: true,
botToken: "<OLD_BOT_TOKEN>"
},
secondary: {
name: "Secondary Bot",
enabled: true,
botToken: "<SECONDARY_BOT_TOKEN>"
}
}
}
}
Agents: explicitly declare both brains, mark one default
agents: {
defaults: {
workspace: "~/.openclaw/workspace"
},
list: [
{
id: "main",
name: "Primary Agent",
workspace: "~/.openclaw/workspace",
default: true
},
{
id: "secondary",
name: "Secondary Agent",
workspace: "~/.openclaw/workspace-secondary"
}
]
}
Bindings: bind both accounts explicitly
Yes, you can bind only secondary and rely on fallback for default.
But I strongly recommend binding both. It makes the configuration self-documenting and immune to list ordering mistakes.
bindings: [
{ agentId: "main", match: { channel: "telegram", accountId: "default" } },
{ agentId: "secondary", match: { channel: "telegram", accountId: "secondary" } }
]
Pairing: why the bot sometimes stops repeating the pairing code
If you use dmPolicy: "pairing", the bot may show a pairing code once, then go quiet.
That’s not a bug. It’s a workflow.
To approve pairing for a specific Telegram account, you must include the account id:
openclaw pairing list telegram --account secondary
openclaw pairing approve telegram <CODE> --account secondary --notify
If you approve without --account in a multi-account setup, you can end up looking at the wrong queue.
Verification checklist (don’t skip this)
After any multi-agent / multi-account change:
openclaw gateway restart
openclaw agents list --bindings
openclaw channels status --probe
If these three commands look correct, your setup is almost always correct.
Takeaway
The lesson wasn’t “OpenClaw is flaky.”
The lesson was: once you cross into multi-agent + multi-account territory, you must respect:
- canonical config shapes (
accounts.defaultis not optional) - default-agent routing rules (make it explicit)
- account-aware pairing approval (
--accountmatters)
Do that, and the whole system becomes boringly deterministic — which is exactly what you want.