Files
SynthesisAgent/docs/wire-protocol.md
zhi 57fc8610ed chore: bump submodules + update wire protocol + STATUS
Submodules now have real implementations of the HTTP+WS bridge and the
channel-side MCP server. STATUS reflects locked architecture decisions
and the laptop smoke-test plan. wire-protocol.md updated to drop the
auth token and permission_request descriptions (no longer in scope).
2026-05-14 13:28:22 +00:00

3.1 KiB

Wire Protocol

Two channels between SynthesisAgent.OpenclawPlugin (OpenClaw side) and SynthesisAgent.ClaudePlugin (Claude side):

  1. HTTPOpenClaw → OpenclawPlugin. OpenAI-compatible /v1/chat/completions SSE. This is the public boundary; OpenClaw routes agent turns here.
  2. WebSocketClaudePlugin → 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:<channelWsPort>/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:

{ "type": "hello",
  "openclaw_session": "<session_key>",
  "claude_session":   "<uuid>",
  "pid":              12345 }

OpenclawPlugin replies:

{ "type": "hello_ack" }

OpenclawPlugin → ClaudePlugin

{ "type": "inbound",
  "content": "<user message text>",
  "meta": { "chat_id":"…", "message_id":"…", "ts":"…" } }

ClaudePlugin translates this to an MCP notification on its stdio:

{ "method": "notifications/claude/channel",
  "params": { "content": "…", "meta": {} } }

Claude Code reacts by starting a new turn.

ClaudePlugin → OpenclawPlugin

{ "type": "reply",
  "content": "<text chunk>",
  "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

{ "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).