openclaw - 💡(How to fix) Fix [Feature]: messages_send hardcodes role: "assistant" — add role parameter for cross-agent invocation

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

The messages_send MCP tool hardcodes role: "assistant" on outbound messages, preventing external MCP clients from delivering messages that trigger the target agent's loop. Add an optional role parameter (default unchanged) so callers can specify "user" to wake the agent.

Error Message

if (!conversation) throw new Error(Conversation not found for session ${params.sessionKey});

Root Cause

The messages_send MCP tool hardcodes role: "assistant" on outbound messages, preventing external MCP clients from delivering messages that trigger the target agent's loop. Add an optional role parameter (default unchanged) so callers can specify "user" to wake the agent.

Fix Action

Fix / Workaround

Affected: Cross-stack agent invocation (e.g. Hermes ↔ OpenClaw via MCP), and any external client wanting to drive an OpenClaw agent programmatically. Severity: Blocks workflow — no supported way to wake an OpenClaw agent through the MCP bridge. Frequency: Every cross-agent invocation attempt fails silently (message lands in log, agent doesn't process it). Consequence: Multi-agent architectures must fall back to file-based workarounds, custom relay scripts, or terminal-autonomy shell-outs — none using the intended MCP primitive.

Code Example

server.tool("messages_send", "Send a message back through the same OpenClaw conversation route.", {
       session_key: string().min(1),
  -    text: string().min(1)
  -}, async ({ session_key, text }) => {
  +    text: string().min(1),
  +    role: _enum(["user", "assistant"]).optional().describe("Message role: 'user' to trigger agent response, 'assistant' for log entries")
  +}, async ({ session_key, text, role }) => {
       return {
           content: [{ type: "text", text: "sent" }],
  -        structuredContent: { result: await bridge.sendMessage({ sessionKey: session_key, text }) }
  +        structuredContent: { result: await bridge.sendMessage({ sessionKey: session_key, text, role }) }
       };  
   });

---

async sendMessage(params) {
       const conversation = await this.getConversation(params.sessionKey);
       if (!conversation) throw new Error(`Conversation not found for session ${params.sessionKey}`);
       return await this.requestGateway("send", {
           channel: conversation.channel,
           accountId: conversation.accountId,
           threadId: conversation.threadId == null ? void 0 : String(conversation.threadId),
           message: params.text,
  +        role: params.role ?? "assistant",  // Default to assistant for backward compatibility
           sessionKey: conversation.sessionKey,
           idempotencyKey: randomUUID()
       }); 
   }
RAW_BUFFERClick to expand / collapse

Summary

The messages_send MCP tool hardcodes role: "assistant" on outbound messages, preventing external MCP clients from delivering messages that trigger the target agent's loop. Add an optional role parameter (default unchanged) so callers can specify "user" to wake the agent.

Problem to solve

The MCP bridge tool messages_send does not allow callers to specify the message role. Messages sent through the bridge always appear as role: "assistant" in the conversation history. This prevents external MCP clients (other AI agents, harnesses like Hermes) from sending messages that trigger the agent's message loop, which only responds to role: "user" messages.

Steps to reproduce:

  1. Start OpenClaw Gateway with an active session
  2. Connect an MCP client (Hermes / Codex / Claude Code) via openclaw mcp serve
  3. Call messages_send with session_key + text
  4. Message appears in conversation history with role: "assistant"
  5. Target agent does not wake up — agent loop only triggers on role: "user"

Expected: MCP client can choose to send as role: "user" to trigger agent response. Actual: All MCP messages hardcoded as role: "assistant"; agent ignores.

Proposed solution

Add an optional role parameter to the messages_send tool. Default behavior unchanged (role omitted → assistant, as today). When callers specify role: "user", the message wakes the agent loop.

File: /usr/local/lib/node_modules/openclaw/dist/mcp-cli-bDM0zKWg.js

 server.tool("messages_send", "Send a message back through the same OpenClaw conversation route.", {
     session_key: string().min(1),
-    text: string().min(1)
-}, async ({ session_key, text }) => {
+    text: string().min(1),
+    role: _enum(["user", "assistant"]).optional().describe("Message role: 'user' to trigger agent response, 'assistant' for log entries")
+}, async ({ session_key, text, role }) => {
     return {
         content: [{ type: "text", text: "sent" }],
-        structuredContent: { result: await bridge.sendMessage({ sessionKey: session_key, text }) }
+        structuredContent: { result: await bridge.sendMessage({ sessionKey: session_key, text, role }) }
     };  
 });

And in OpenClawChannelBridge.sendMessage:

 async sendMessage(params) {
     const conversation = await this.getConversation(params.sessionKey);
     if (!conversation) throw new Error(`Conversation not found for session ${params.sessionKey}`);
     return await this.requestGateway("send", {
         channel: conversation.channel,
         accountId: conversation.accountId,
         threadId: conversation.threadId == null ? void 0 : String(conversation.threadId),
         message: params.text,
+        role: params.role ?? "assistant",  // Default to assistant for backward compatibility
         sessionKey: conversation.sessionKey,
         idempotencyKey: randomUUID()
     }); 
 }

Alternatives considered

  • Add a separate messages_send_as_user tool that forces user-role. Simpler API but less flexible — every new role variant needs a new tool name; doesn't generalize.
    • Treat all MCP-originated messages as user-role by default. Policy change, but breaks existing channel-bridge use cases where assistant-role IS the intended behavior.

Impact

Affected: Cross-stack agent invocation (e.g. Hermes ↔ OpenClaw via MCP), and any external client wanting to drive an OpenClaw agent programmatically. Severity: Blocks workflow — no supported way to wake an OpenClaw agent through the MCP bridge. Frequency: Every cross-agent invocation attempt fails silently (message lands in log, agent doesn't process it). Consequence: Multi-agent architectures must fall back to file-based workarounds, custom relay scripts, or terminal-autonomy shell-outs — none using the intended MCP primitive.

Evidence/examples

Reproduced 2026-05-24 against OpenClaw 2026.5.20.

Source locations:

  • messages_send tool schema: dist/mcp-cli-bDM0zKWg.js (~lines 570–580)
  • sendMessage method in OpenClawChannelBridge: dist/mcp-cli-bDM0zKWg.js (~lines 216–228)

Observed behavior — MCP call:

{ "session_key": "agent:main:telegram:default:direct:8234042566",
  "text": "Tony, please introduce yourself" }

Result: message appears in conversation history as role: "assistant"; target agent never processes it.

Use case enabled by the fix:

  • Agent A (Claude Code / Hermes) sends messages to Agent B (OpenClaw)
  • Agent B responds naturally as if a human sent the message
  • Teams of AI agents can collaborate via OpenClaw's channel routing

Additional information

Proposed change is backward-compatible (role is optional; the sendMessage update uses params.role ?? "assistant" to preserve today's behavior)

  • MCP protocol itself is role-agnostic — role assignment is a server-side implementation detail, so this fits MCP conventions
  • Enables clean cross-agent MCP bridges between OpenClaw and other agent harnesses (Hermes, Codex, Claude Code SDK clients)

Environment:

  • OpenClaw version: 2026.5.20
  • Gateway mode: local (loopback)
  • MCP client: Hermes (DeepSeek V4 Pro) via `openclaw mcp serve Reported by: Stevie Pearce (rabrats), with analysis by Tony (OpenClaw agent on Kimi K2.5).

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING