# Wire Protocol Two channels between **SynthesisAgent.OpenclawPlugin** (OpenClaw side) and **SynthesisAgent.ClaudePlugin** (Claude side): 1. **HTTP** — `OpenClaw → OpenclawPlugin`. OpenAI-compatible `/v1/chat/completions` SSE. This is the public boundary; OpenClaw routes agent turns here. 2. **WebSocket** — `ClaudePlugin → OpenclawPlugin`. Single long-lived per Claude process. Carries the channel-notification protocol and the reply path. Same-machine deployment — no auth, no permission_request reverse channel. ## HTTP (OpenClaw → OpenclawPlugin) ``` POST /v1/chat/completions Headers (v1, transitional — read in this order, first wins): X-Openclaw-Agent-Id agent identifier X-Openclaw-Chat-Id channel/chat identifier (DM, group, …) X-Openclaw-Workspace absolute workspace dir Body: OpenAI-format chat completion request (messages[], tools[], …) ``` Session key = `${agentId}::${chatId}` (matches contractor-agent's `buildSessionKey`). Response: SSE stream, OpenAI chat.completion.chunk events: - empty content delta every 30s (heartbeat — empty delta counts as model progress for OpenClaw's idle watchdog; SSE comments do **not**) - single content delta with the reply text once `ClaudePlugin` reports `reply` with `final: true` - final stop chunk + `data: [DONE]` ## WebSocket bridge OpenclawPlugin listens on `ws://127.0.0.1:/bridge`. Each spawned Claude process dials in once. ### Connection bootstrap Spawn env vars set by OpenclawPlugin's process-manager: | Env var | Example | |---|---| | `SYNTHESIS_WS_URL` | `ws://127.0.0.1:18901/bridge` | | `SYNTHESIS_OPENCLAW_SESSION` | `developer::discord:channel:123` | | `SYNTHESIS_CLAUDE_SESSION` | UUID for traceability | ClaudePlugin sends: ```json { "type": "hello", "openclaw_session": "", "claude_session": "", "pid": 12345 } ``` OpenclawPlugin replies: ```json { "type": "hello_ack" } ``` ### `OpenclawPlugin → ClaudePlugin` ```json { "type": "inbound", "content": "", "meta": { "chat_id":"…", "message_id":"…", "ts":"…" } } ``` ClaudePlugin translates this to an MCP notification on its stdio: ```json { "method": "notifications/claude/channel", "params": { "content": "…", "meta": {…} } } ``` Claude Code reacts by starting a new turn. ### `ClaudePlugin → OpenclawPlugin` ```json { "type": "reply", "content": "", "final": true } ``` `final: false` lets the model stream progress chunks. OpenclawPlugin buffers them and completes the pending SSE stream when a final `reply` arrives. ### Heartbeat ```json { "type": "ping" } { "type": "pong" } ``` OpenclawPlugin sends `ping` every 30s. ClaudePlugin missing 2 in a row → terminate the connection (it will reconnect with exponential backoff). ## Reconnect semantics - ClaudePlugin reconnects with exponential backoff (1s → 30s cap). - During disconnect, OpenclawPlugin rejects any in-flight dispatch with an error (the HTTP caller sees an SSE error chunk + `[DONE]`). - `hello` on reconnect re-establishes routing. - No queued message buffering yet (v1 simplification — add later if needed).