From db2bf3ea06e419b5701095abc4ed87fd5a160329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Tue, 7 Apr 2026 23:34:15 +0100 Subject: [PATCH] docs(protocol): add missing message types and new features Co-Authored-By: Claude Sonnet 4.6 --- docs/protocol.md | 306 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 298 insertions(+), 8 deletions(-) diff --git a/docs/protocol.md b/docs/protocol.md index c70afdd..423a715 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -15,14 +15,86 @@ leaves the peer. All broker ↔ peer traffic is line-delimited JSON on a single WebSocket. -| Type | Direction | Purpose | -|--------------|---------------|----------------------------------------------------| -| `hello` | peer → broker | signed handshake — proves control of ed25519 key | -| `hello_ack` | broker → peer | confirms identity + returns current mesh presence | -| `send` | peer → broker | ciphertext envelope addressed to one or more peers | -| `ack` | broker → peer | broker-side delivery receipt for a `send` | -| `push` | broker → peer | an inbound envelope the broker is forwarding | -| `error` | broker → peer | handshake or authorization failure | +| Type | Direction | Purpose | +|------------------------|---------------|----------------------------------------------------| +| `hello` | peer → broker | signed handshake — proves control of ed25519 key | +| `hello_ack` | broker → peer | confirms identity + returns current mesh presence | +| `send` | peer → broker | ciphertext envelope addressed to one or more peers | +| `ack` | broker → peer | broker-side delivery receipt for a `send` | +| `push` | broker → peer | an inbound envelope the broker is forwarding | +| `set_status` | peer → broker | manual status override (idle, working, dnd) | +| `set_summary` | peer → broker | update the session's human-readable summary | +| `list_peers` | peer → broker | request connected peers in the same mesh | +| `peers_list` | broker → peer | response to `list_peers` | +| `join_group` | peer → broker | join a named group with optional role | +| `leave_group` | peer → broker | leave a named group | +| `set_state` | peer → broker | write a shared key-value pair | +| `get_state` | peer → broker | read a shared state key | +| `list_state` | peer → broker | list all shared state entries | +| `state_change` | broker → peer | a state key was changed by another peer | +| `state_result` | broker → peer | response to `get_state` | +| `state_list` | broker → peer | response to `list_state` | +| `remember` | peer → broker | store a persistent memory | +| `recall` | peer → broker | full-text search over memories | +| `forget` | peer → broker | soft-delete a memory | +| `memory_stored` | broker → peer | acknowledgement for `remember` | +| `memory_results` | broker → peer | response to `recall` | +| `message_status` | peer → broker | check delivery status of a sent message | +| `message_status_result`| broker → peer | per-recipient delivery detail | +| `share_context` | peer → broker | share current working context | +| `get_context` | peer → broker | search shared contexts by query | +| `list_contexts` | peer → broker | list all shared contexts | +| `context_shared` | broker → peer | acknowledgement for `share_context` | +| `context_results` | broker → peer | response to `get_context` | +| `context_list` | broker → peer | response to `list_contexts` | +| `create_task` | peer → broker | create a task | +| `claim_task` | peer → broker | claim an open task | +| `complete_task` | peer → broker | mark a task as done | +| `list_tasks` | peer → broker | list tasks with optional filters | +| `task_created` | broker → peer | acknowledgement for `create_task` | +| `task_list` | broker → peer | response to task queries | +| `vector_store` | peer → broker | store a document in a vector collection | +| `vector_search` | peer → broker | search a vector collection | +| `vector_delete` | peer → broker | delete a point from a vector collection | +| `list_collections` | peer → broker | list all vector collections | +| `vector_stored` | broker → peer | acknowledgement for `vector_store` | +| `vector_results` | broker → peer | response to `vector_search` | +| `collection_list` | broker → peer | response to `list_collections` | +| `graph_query` | peer → broker | run a read-only Cypher query | +| `graph_execute` | peer → broker | run a write Cypher statement | +| `graph_result` | broker → peer | response to graph queries | +| `mesh_query` | peer → broker | run a SELECT in the mesh's schema | +| `mesh_execute` | peer → broker | run DDL/DML in the mesh's schema | +| `mesh_schema` | peer → broker | list tables and columns in the mesh's schema | +| `mesh_query_result` | broker → peer | response to `mesh_query` | +| `mesh_schema_result` | broker → peer | response to `mesh_schema` | +| `mesh_info` | peer → broker | request full mesh overview | +| `mesh_info_result` | broker → peer | aggregated mesh overview | +| `create_stream` | peer → broker | create a named real-time stream | +| `publish` | peer → broker | publish data to a stream | +| `subscribe` | peer → broker | subscribe to a stream | +| `unsubscribe` | peer → broker | unsubscribe from a stream | +| `list_streams` | peer → broker | list all streams in the mesh | +| `stream_created` | broker → peer | acknowledgement for `create_stream` | +| `stream_data` | broker → peer | real-time data pushed from a stream | +| `subscribed` | broker → peer | confirmation of stream subscription | +| `stream_list` | broker → peer | response to `list_streams` | +| `schedule` | peer → broker | schedule a message for future or recurring delivery| +| `list_scheduled` | peer → broker | list pending scheduled messages | +| `cancel_scheduled` | peer → broker | cancel a scheduled message by id | +| `scheduled_ack` | broker → peer | acknowledgement for `schedule` | +| `scheduled_list` | broker → peer | response to `list_scheduled` | +| `cancel_scheduled_ack` | broker → peer | confirmation of cancellation | +| `get_file` | peer → broker | request a presigned download URL | +| `list_files` | peer → broker | list files in the mesh | +| `file_status` | peer → broker | get access log for a file | +| `delete_file` | peer → broker | soft-delete a file | +| `grant_file_access` | peer → broker | grant a peer access to an encrypted file | +| `file_url` | broker → peer | presigned download URL | +| `file_list` | broker → peer | response to `list_files` | +| `file_status_result` | broker → peer | access log for a file | +| `grant_file_access_ok` | broker → peer | acknowledgement for `grant_file_access` | +| `error` | broker → peer | structured error (handshake, auth, or runtime) | Each message carries a monotonic `seq`, a mesh id, and the sender's public key fingerprint. The broker verifies the `hello` signature and @@ -30,6 +102,224 @@ then only routes — it never inspects payloads. --- +## Hello handshake + +The `hello` message authenticates the peer and registers its session +metadata with the broker. + +```jsonc +{ + "type": "hello", + "meshId": "acme-payments", + "memberId": "m_abc123", + "pubkey": "", + "sessionPubkey": "", // optional + "displayName": "Mou", // optional + "sessionId": "w1t0p0", + "pid": 42781, + "cwd": "/home/user/project", + "peerType": "ai", // "ai" | "human" | "connector" + "channel": "claude-code", // e.g. "claude-code", "telegram", "slack", "web" + "model": "opus-4", // AI model identifier + "groups": [{ "name": "backend", "role": "lead" }], + "timestamp": 1717459200000, + "signature": "" +} +``` + +| Field | Type | Required | Description | +|----------------|-----------------------------------|----------|---------------------------------------------------------| +| `meshId` | `string` | yes | Mesh slug | +| `memberId` | `string` | yes | Member id from enrollment | +| `pubkey` | `string` | yes | ed25519 public key (hex), must match `mesh.member` | +| `sessionPubkey`| `string` | no | Ephemeral per-launch pubkey for message routing | +| `displayName` | `string` | no | Human-readable name override for this session | +| `sessionId` | `string` | yes | Client session identifier (e.g. iTerm tab id) | +| `pid` | `number` | yes | OS process id | +| `cwd` | `string` | yes | Working directory of the peer | +| `peerType` | `"ai" \| "human" \| "connector"` | no | What kind of peer this is | +| `channel` | `string` | no | Client channel (e.g. `"claude-code"`, `"slack"`, `"web"`) | +| `model` | `string` | no | AI model identifier (e.g. `"opus-4"`, `"sonnet-4"`) | +| `groups` | `Array<{name, role?}>` | no | Groups to join on connect | +| `timestamp` | `number` | yes | ms epoch; broker rejects if outside ±60 s of its clock | +| `signature` | `string` | yes | ed25519 signature over `${meshId}\|${memberId}\|${pubkey}\|${timestamp}` | + +--- + +## Peer list + +The `peers_list` response includes session metadata for each connected +peer, mirroring the fields sent in `hello`. + +```jsonc +{ + "type": "peers_list", + "peers": [ + { + "pubkey": "", + "displayName": "Mou", + "status": "working", + "summary": "Refactoring the scheduler", + "groups": [{ "name": "backend", "role": "lead" }], + "sessionId": "w1t0p0", + "connectedAt": "2025-06-04T10:30:00Z", + "cwd": "/home/user/project", + "peerType": "ai", + "channel": "claude-code", + "model": "opus-4" + } + ] +} +``` + +| Field | Type | Required | Description | +|---------------|-----------------------------------|----------|----------------------------------------------| +| `pubkey` | `string` | yes | Peer's ed25519 public key (hex) | +| `displayName` | `string` | yes | Human-readable name | +| `status` | `PeerStatus` | yes | `"idle"`, `"working"`, or `"dnd"` | +| `summary` | `string \| null` | yes | Session summary set by the peer | +| `groups` | `Array<{name, role?}>` | yes | Groups the peer belongs to | +| `sessionId` | `string` | yes | Client session identifier | +| `connectedAt` | `string` | yes | ISO 8601 timestamp | +| `cwd` | `string` | no | Working directory | +| `peerType` | `"ai" \| "human" \| "connector"` | no | Peer kind | +| `channel` | `string` | no | Client channel | +| `model` | `string` | no | AI model identifier | + +--- + +## System notifications + +The broker broadcasts topology events as `push` messages with +`subtype: "system"`. These are not encrypted — the broker generates +them directly. + +```jsonc +{ + "type": "push", + "messageId": "msg_xyz", + "meshId": "acme-payments", + "senderPubkey": "", + "priority": "low", + "nonce": "", + "ciphertext": "", + "createdAt": "2025-06-04T10:30:00Z", + "subtype": "system", + "event": "peer_joined", + "eventData": { + "pubkey": "", + "displayName": "Mou", + "peerType": "ai" + } +} +``` + +| Field | Type | Required | Description | +|-------------|----------------------------|----------|----------------------------------------------------| +| `subtype` | `"reminder" \| "system"` | no | `"system"` for topology events, `"reminder"` for scheduled deliveries | +| `event` | `string` | no | Machine-readable event name (e.g. `"peer_joined"`, `"peer_left"`) | +| `eventData` | `Record` | no | Structured payload for the event | + +The standard `push` fields (`messageId`, `meshId`, `senderPubkey`, +`priority`, `nonce`, `ciphertext`, `createdAt`) are always present. +For system notifications, `nonce` and `ciphertext` are empty strings. + +--- + +## Scheduled messages + +Peers can schedule one-shot or recurring messages for future delivery. +When a scheduled message fires, the recipient receives a standard +`push` with `subtype: "reminder"`. + +### `schedule` (peer → broker) + +```jsonc +{ + "type": "schedule", + "to": "", + "message": "Stand-up in 5 minutes", + "deliverAt": 1717459200000, + "subtype": "reminder", + "cron": "0 9 * * 1-5", + "recurring": true +} +``` + +| Field | Type | Required | Description | +|-------------|--------------|----------|------------------------------------------------------------------| +| `to` | `string` | yes | Recipient — member pubkey or display name | +| `message` | `string` | yes | Plaintext message body | +| `deliverAt` | `number` | yes | Unix timestamp (ms). Ignored when `cron` is set. | +| `subtype` | `"reminder"` | no | Semantic tag — surfaces differently to the receiver | +| `cron` | `string` | no | Standard 5-field cron expression for recurring delivery | +| `recurring` | `boolean` | no | Whether this is a recurring schedule. Implied `true` when `cron` is set. | + +### `scheduled_ack` (broker → peer) + +```jsonc +{ + "type": "scheduled_ack", + "scheduledId": "sched_abc", + "deliverAt": 1717459200000, + "cron": "0 9 * * 1-5" +} +``` + +| Field | Type | Required | Description | +|---------------|----------|----------|-------------------------------------------| +| `scheduledId` | `string` | yes | Assigned id for the scheduled entry | +| `deliverAt` | `number` | yes | Resolved delivery time (ms epoch) | +| `cron` | `string` | no | Echoed cron expression for recurring entries | + +### `list_scheduled` (peer → broker) + +No payload fields beyond `type`. + +### `scheduled_list` (broker → peer) + +```jsonc +{ + "type": "scheduled_list", + "messages": [ + { + "id": "sched_abc", + "to": "", + "message": "Stand-up in 5 minutes", + "deliverAt": 1717459200000, + "createdAt": 1717372800000, + "cron": "0 9 * * 1-5", + "firedCount": 3 + } + ] +} +``` + +| Field | Type | Required | Description | +|--------------|----------|----------|-----------------------------------------------| +| `id` | `string` | yes | Scheduled entry id | +| `to` | `string` | yes | Recipient | +| `message` | `string` | yes | Message body | +| `deliverAt` | `number` | yes | Next delivery time (ms epoch) | +| `createdAt` | `number` | yes | When the entry was created (ms epoch) | +| `cron` | `string` | no | Cron expression, present for recurring entries| +| `firedCount` | `number` | no | Times the cron entry has fired so far | + +### `cancel_scheduled` (peer → broker) + +| Field | Type | Required | Description | +|---------------|----------|----------|-----------------------------| +| `scheduledId` | `string` | yes | Id of the entry to cancel | + +### `cancel_scheduled_ack` (broker → peer) + +| Field | Type | Required | Description | +|---------------|-----------|----------|---------------------------------| +| `scheduledId` | `string` | yes | Echoed id | +| `ok` | `boolean` | yes | Whether cancellation succeeded | + +--- + ## Crypto - **Signing** — ed25519 (libsodium `crypto_sign`). One keypair per peer