openclaw-agent-relay
v3.1.0
Published
Send messages to agents in their existing sessions via gateway WebSocket RPC. The response is delivered to the user (Telegram, etc), not webchat.
Maintainers
Readme
openclaw-agent-relay
Wake agents in their existing sessions via gateway WebSocket RPC. The agent sees your message with full conversation history and responds to the user through their channel (Telegram, Discord, etc).
Think of it as sessions_send that actually delivers the response to the user, not to webchat.
Why
Multi-agent setups need agents to talk to each other: a broker sends a reminder, a scheduler triggers a follow-up, a cron wakes an agent to check on a client. OpenClaw has sessions_send for this — but it doesn't solve the last mile: getting the response to the user.
The sessions_send problem
sessions_send injects a message into another agent's session and preserves conversation history. But the agent's response goes to channel=webchat — an internal channel. The user on Telegram never sees it. #13374 (closed NOT_PLANNED).
Worse, sessions_send can corrupt the target session's delivery context, flipping it from telegram to webchat for all subsequent messages (#44153, #31671).
Known workarounds and why they're fragile
Workaround 1: agent calls message tool explicitly. The target agent sends the response via message with channel: "telegram" and an explicit to/threadId, then returns ANNOUNCE_SKIP. This is the most common community workaround (#47971, #44153, #28603). But it has two problems: you have to embed delivery instructions in every sessions_send payload, and the agent tends to forget to use it. From the agent's perspective, it just received a message and is responding normally — it doesn't know its reply won't reach the user. So it writes a perfectly good response that goes straight to webchat. You can prompt it to call the message tool every time, but it drifts, especially in longer sessions.
Workaround 2: rely on announce step. When sessions_send uses timeout=0, the target agent gets an announce step where it can write a response that gets delivered to Telegram. This technically works — but in practice the model tends to return ANNOUNCE_SKIP instead of writing the actual message. Even with explicit instructions, it "forgets" and skips the announce area. This is a known LLM behavior pattern (#43295) — models generate responses first and check rules second, if at all. You can fight this with very short prompts or runtime enforcement (recallBeforeResponse), but it remains unreliable.
On top of that, announce delivery itself has issues:
- Drops
threadIdfor Telegram topics (#47971, #45878) - Silently fails with multi-channel setups (#47524)
ANNOUNCE_SKIPtext can leak to the user's Telegram (#45084)
What this plugin does instead
openclaw-agent-relay bypasses sessions_send and announce entirely. It uses the same gateway RPC mechanism as subagent announce (callGateway({ method: "agent" })) to run an agent turn in the existing session with deliver: true. The agent responds normally — no special instructions, no ANNOUNCE_SKIP, no message tool workarounds — and the response goes straight to Telegram.
Install
openclaw plugins install openclaw-agent-relayConfigure
Add to openclaw.json:
{
plugins: {
entries: {
"openclaw-agent-relay": {
enabled: true,
config: {
authToken: "your-secret-token", // for HTTP endpoint auth
gatewayToken: "copy-from-gateway.auth.token", // from your openclaw.json
// port: 18790, // HTTP endpoint port (default: 18790)
// gatewayPort: 18789, // gateway WS port (default: 18789)
}
}
}
}
}gatewayToken is the same token from gateway.auth.token in your openclaw.json.
Restrict who can wake whom
Use allowedTargets to limit which agents can target which sessions:
{
plugins: {
entries: {
"openclaw-agent-relay": {
enabled: true,
config: {
authToken: "your-secret-token",
gatewayToken: "copy-from-gateway.auth.token",
allowedTargets: {
// wamm can only wake the broker
"wamm-survey-agent": ["agent:broker:*"],
// broker can only wake wamm
"broker": ["agent:wamm-survey-agent:*"]
}
}
}
}
}
}Patterns support trailing :* wildcards. Omit allowedTargets to allow all agents to wake any session.
Usage
Tool: notify_agent
Any agent can call notify_agent as a native tool:
notify_agent({
sessionKey: "agent:my-agent:telegram:direct:123456",
message: "Hey, remind the client about the contract"
})The target agent wakes up in their session, sees the message with full dialogue history, and responds to the user via Telegram.
HTTP: POST /notify
For cron jobs, scripts, or external systems:
curl -X POST http://127.0.0.1:18790/notify \
-H "Authorization: Bearer your-secret-token" \
-H "Content-Type: application/json" \
-d '{
"sessionKey": "agent:my-agent:telegram:direct:123456",
"message": "Reminder: client asked for the contract"
}'Parameters
| Field | Required | Description |
|-------|----------|-------------|
| sessionKey | Yes | Target session key (agent:<agentId>:<channel>:<type>:<peerId>) |
| message | Yes | Message text (agent sees it as a user message) |
| channel | No | Override delivery channel |
| to | No | Override delivery recipient |
Example: what happens inside
Broker wakes another agent via tool call
👤 USER (to broker)
"Remind the WAMM client about the rental contract"
🤖 BROKER → tool call
notify_agent({
sessionKey: "agent:wamm-survey:telegram:direct:647960541",
message: "Remind the client they requested a rental contract. Ask when is convenient to send it."
})
⚙️ TOOL RESULT
"Agent woken in session agent:wamm-survey:telegram:direct:647960541.
They will see your message and respond to the user via their channel."
🤖 BROKER
"Done! WAMM agent will remind the client about the contract."Meanwhile, in the WAMM agent's session (with full conversation history):
... (previous dialogue with client about documents) ...
👤 [injected by relay — agent sees this as a user message]
"Remind the client they requested a rental contract. Ask when is convenient to send it."
🤖 WAMM AGENT → responds in Telegram
"Здравствуйте! Напоминаю — вы просили договор аренды.
Когда вам удобно его получить? Могу отправить прямо сейчас."The client sees the message from the WAMM bot in Telegram — not from the broker, not from webchat.
External trigger via HTTP
# Cron job at 9:00 AM
curl -X POST http://127.0.0.1:18790/notify \
-H "Authorization: Bearer relay-notify-2026" \
-d '{"sessionKey":"agent:support:telegram:direct:123456",
"message":"Morning check: any pending tickets from yesterday?"}'
# Response:
{"ok": true, "method": "gateway-rpc"}
# The support agent wakes up in their Telegram session and responds to the userHow it works
- Plugin generates an Ed25519 device identity at startup
- On
/notifyornotify_agentcall, connects to gateway via WebSocket - Authenticates with challenge-response (device signature + shared token)
- Calls
method: "agent"withsessionKey,message,deliver: true - Gateway runs an agent turn in the existing session (not isolated)
- Agent sees the message with full conversation history
- Response is delivered to the user via the session's channel
Falls back to enqueueSystemEvent + requestHeartbeatNow if gateway WebSocket is unavailable.
Comparison
| | sessions_send | notify_agent |
|---|---|---|
| Agent sees message | Yes | Yes |
| Session context preserved | Yes | Yes |
| Response to Telegram | No (webchat) | Yes |
| Agent formulates response | Yes | Yes |
| Available as tool | Yes (built-in) | Yes (plugin) |
License
MIT
