Skip to content

WebSocket Protocol

This is the complete message contract between the Antimatter Gateway and the mobile client (Android or iOS). All frames are UTF-8 JSON text objects with a type discriminator field.

  • Compression: permessage-deflate enabled
  • Max payload: 10 MiB
  • Binding: Gateway binds to 127.0.0.1:8765 (IPC) and is exposed via Cloudflare tunnel

Mobile App Antimatter Gateway
β”‚ β”‚
β”‚ WSS upgrade (Authorization: Bearer) β”‚
β”‚ ────────────────────────────────────────►│ Origin check + token verify
β”‚ ◄────────── 101 Switching Protocols ─────│ (or 401/403/close 4001)
β”‚ β”‚
β”‚ AUTH_CHALLENGE { challenge: base64 } β”‚
β”‚ ────────────────────────────────────────►│ Sign nonce with Ed25519 key
β”‚ ◄────── AUTH_RESPONSE { signature } ────│ Client marked authenticated
β”‚ β”‚
β”‚ GET_AVAILABLE_AGENTS β”‚
β”‚ ────────────────────────────────────────►│
β”‚ ◄────── AVAILABLE_AGENTS { agents[] } ──│
β”‚ β”‚
β”‚ ←──── Bidirectional message exchange ───►│
β”‚ SEND_MESSAGE / GET_FILES / WRITE_FILE... β”‚

The Gateway rejects WebSocket upgrades unless the Origin header matches the allow-list:

  • vscode-webview://… (extension webview)
  • https://<team>.cloudflareaccess.com (Cloudflare Access)
  • Mobile app origins (dynamic β€” derived from platform)

Rejected origins β†’ HTTP 403 Forbidden (Cross-Site WebSocket Hijacking prevention).

The mobile client presents the 256-bit pairing token, checked in priority order:

  1. Authorization: Bearer <token> header
  2. First Sec-WebSocket-Protocol value
  3. ?token=<token> URL query parameter

The token is compared with timing-safe equality (crypto.timingSafeEqual). Invalid token β†’ close code 4001 Unauthorized.

After the socket opens, the client sends AUTH_CHALLENGE with a random base64 nonce. The Gateway signs the raw nonce bytes with its persistent Ed25519 private key (stored in OS keychain) and returns AUTH_RESPONSE. The client verifies the signature against the Ed25519 public key received during QR pairing β€” proving the Gateway’s identity and preventing MITM attacks.


Code Meaning Cause
1000 Normal closure Clean disconnect
4000 Rate limited Too many failed token attempts (60s ban)
4001 Unauthorized Missing or invalid pairing token
403 Forbidden Origin (HTTP) Origin header not in allow-list

These are sent from the mobile client to the Gateway.

Begin the Ed25519 cryptographic handshake.

{
"type": "AUTH_CHALLENGE",
"challenge": "base64-encoded-random-nonce"
}

Request the list of currently connected adapters.

{
"type": "GET_AVAILABLE_AGENTS"
}

Inject a prompt into the target AI agent.

{
"type": "SEND_MESSAGE",
"target": "ag",
"content": "Refactor the authentication module"
}
Field Type Required Description
target string βœ“ Adapter name ("ag", "ag2", "cc")
content string βœ“ Text prompt to inject
images string[] β€” Base64-encoded image attachments

Request the workspace file tree from the target adapter.

{
"type": "GET_FILES",
"target": "ag",
"path": "/"
}

Read the contents of a specific file.

{
"type": "READ_FILE",
"target": "ag",
"path": "/src/main/App.kt"
}

Write content to a file in the workspace.

{
"type": "WRITE_FILE",
"target": "ag",
"path": "/src/main/App.kt",
"content": "package com.example\n..."
}

Subscribe to real-time trajectory updates for a specific conversation.

{
"type": "SUBSCRIBE_CONVERSATION",
"conversationId": "uuid-string"
}

Keepalive ping to prevent Cloudflare from closing idle connections.

{ "type": "PING" }

These are sent from the Gateway to the mobile client.

Reply to AUTH_CHALLENGE with the Ed25519 signature.

{
"type": "AUTH_RESPONSE",
"signature": "base64-encoded-ed25519-signature"
}

List of currently registered and connected adapters.

{
"type": "AVAILABLE_AGENTS",
"agents": ["ag", "ag2", "cc"]
}

A single trajectory step from the AI agent β€” streamed in real-time.

{
"type": "STEP",
"conversationId": "uuid-string",
"stepIndex": 42,
"stepType": "tool_call",
"content": "...",
"toolName": "bash",
"toolInput": { "command": "git status" },
"toolResult": "On branch main..."
}
stepType Description
thinking Agent internal reasoning (may be truncated)
text Agent text response
tool_call Agent invokes a tool
tool_result Tool execution result
artifact File artifact created by agent

Response to GET_FILES.

{
"type": "FILE_TREE",
"path": "/",
"entries": [
{ "name": "src", "isDirectory": true },
{ "name": "README.md", "isDirectory": false, "size": 4096 }
]
}

Response to READ_FILE.

{
"type": "FILE_CONTENT",
"path": "/src/main/App.kt",
"content": "package com.example\n...",
"encoding": "utf-8"
}

Reply to PING.

{ "type": "PONG" }

Returned when a handler throws or an unknown message type is received.

{
"type": "ERROR",
"code": "UNKNOWN_TYPE",
"message": "Unrecognized message type: FOO"
}

Any adapter that speaks this IPC protocol can integrate with Antimatter. Here’s a minimal example:

Minimal Python adapter
import asyncio, websockets, json
async def main():
async with websockets.connect("ws://127.0.0.1:8765") as ws:
# Register with the Gateway
await ws.send(json.dumps({
"type": "REGISTER_ADAPTER",
"name": "my-custom-agent"
}))
async for message in ws:
msg = json.loads(message)
if msg["type"] == "SEND_MESSAGE":
print(f"Received prompt: {msg['content']}")
# Process and respond...
asyncio.run(main())

Saif Mukhtar

Saif Mukhtar

Creator & Lead Developer of Antimatter Β· Android, iOS & Python engineer