# 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: ```json { "type": "hello", "openclaw_session": "", "claude_session": "", "pid": 12345, "token": "" } ``` OpenclawPlugin responds with: ```json { "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) ```json { "type": "inbound_message", "request_id": "msg_abc123", "content": "user message text", "meta": { "chat_id": "", "message_id": "...", "user": "alice", "user_id": "...", "ts": "2026-05-14T...Z", "source_channel": "discord", "attachments": [ ... ] } } ``` ClaudePlugin translates each `inbound_message` into: ```json { "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: ```json { "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: ```json { "type": "tool_call", "call_id": "tc_001", "tool": "pcexec", "args": { ... } } ``` OpenclawPlugin invokes the corresponding OpenClaw tool and responds: ```json { "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: ```json { "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 `"). ## Direction: bidirectional health ```json { "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 `hello` message re-establishes session binding.