Bridge between OpenClaw (multi-channel hub) and interactive Claude Code, keeping autonomous agent usage on the subscription quota after the 2026-06-15 Agent SDK credit split. Initial scaffolding only — two submodules with skeletons: - SynthesisAgent.ClaudePlugin: stdio MCP server registered as a --channels source. Declares experimental.claude/channel capability (verified 2026-05-14 against the official Anthropic discord plugin) and emits notifications/claude/channel to push OpenClaw inbound messages into the Claude turn loop. - SynthesisAgent.OpenclawPlugin: process manager + session mapping + bridge WebSocket. Currently shaped around a custom ws protocol; will be rewritten as a model-provider HTTP server (contractor-agent pattern) so OpenClaw routes inbound channel messages through it via /v1/chat/completions. See STATUS.md for the open punch list and docs/wire-protocol.md for the (soon-to-change) inter-plugin frame schema.
3.8 KiB
Wire Protocol — OpenclawPlugin ↔ ClaudePlugin
A WebSocket connection from each spawned ClaudePlugin instance back to OpenclawPlugin's bridge server. JSON messages, one per frame.
Connection bootstrap
On spawn, OpenclawPlugin sets these env vars on the Claude process so ClaudePlugin can find its way home:
| Env var | Purpose |
|---|---|
SYNTHESIS_BRIDGE_URL |
WebSocket URL, e.g. ws://127.0.0.1:18801/bridge |
SYNTHESIS_BRIDGE_TOKEN |
Shared secret, validated server-side |
SYNTHESIS_OPENCLAW_SESSION |
The OpenClaw session ID this Claude process serves |
SYNTHESIS_CLAUDE_SESSION |
The Claude session UUID (for traceability) |
ClaudePlugin connects on startup and sends:
{ "type": "hello",
"openclaw_session": "<sid>",
"claude_session": "<uuid>",
"pid": 12345,
"token": "<bridge_token>" }
OpenclawPlugin responds with:
{ "type": "hello_ack", "tools": [ /* tool catalog */ ], "session_meta": { ... } }
If hello is rejected (bad token, unknown session), the server closes the socket.
Direction: OpenClaw → Claude (inbound events)
{ "type": "inbound_message",
"request_id": "msg_abc123",
"content": "user message text",
"meta": {
"chat_id": "<openclaw_session_id>",
"message_id": "...",
"user": "alice",
"user_id": "...",
"ts": "2026-05-14T...Z",
"source_channel": "discord",
"attachments": [ ... ]
}
}
ClaudePlugin translates each inbound_message into:
{ "method": "notifications/claude/channel",
"params": { "content": ..., "meta": ... } }
…and emits it on its MCP stdio. Claude Code reacts by starting a new turn.
Direction: OpenClaw → Claude (permission reply)
When the user answers a permission prompt out of band (e.g. "yes abxyz" in Discord), OpenClaw forwards:
{ "type": "permission_reply",
"request_id": "abxyz",
"behavior": "allow" }
ClaudePlugin emits an MCP notifications/claude/channel/permission for that request_id.
Direction: Claude → OpenClaw (tool calls)
Each tools/call Claude makes on ClaudePlugin (excluding internal ones the plugin owns) is forwarded over the bridge:
{ "type": "tool_call",
"call_id": "tc_001",
"tool": "pcexec",
"args": { ... } }
OpenclawPlugin invokes the corresponding OpenClaw tool and responds:
{ "type": "tool_result",
"call_id": "tc_001",
"ok": true,
"result": { ... } }
…or with "ok": false, "error": "...".
Direction: Claude → OpenClaw (permission request)
When Claude Code asks for a permission via notifications/claude/channel/permission_request, ClaudePlugin forwards:
{ "type": "permission_request",
"request_id": "<5 lowercase letters minus 'l'>",
"tool": "Bash",
"args": { ... },
"rationale": "..." }
OpenclawPlugin routes this to the user via the source channel (e.g. posts a Discord message: "Approve? Reply yes <code>").
Direction: bidirectional health
{ "type": "ping", "ts": ... }
{ "type": "pong", "ts": ... }
OpenclawPlugin sends ping every 30s. ClaudePlugin missing 2 consecutive pings → server marks the connection dead, allows reconnect.
Reconnect semantics
- ClaudePlugin reconnects on socket close with exponential backoff (1s, 2s, 4s, max 30s).
- During disconnect, OpenclawPlugin buffers up to N inbound messages per session (configurable). Buffer overflow drops the oldest with a warning.
- On reconnect, the
hellomessage re-establishes session binding.