Skip to content

Wire Protocol

ConvergeDB uses a custom binary protocol over TCP. All multi-byte integers are little-endian.

Every message is a frame: a 12-byte header followed by a variable-length payload.

┌─────────┬───────┬──────────┬────────┬──────────────┬───────┐
│ version │ flags │ stream │ opcode │ payload_len │ crc16 │
│ u8 │ u8 │ u16 LE │ u16 LE │ u32 LE │ u16 LE│
│ (1B) │ (1B) │ (2B) │ (2B) │ (4B) │ (2B) │
└─────────┴───────┴──────────┴────────┴──────────────┴───────┘
  • version: Protocol version (currently 1).
  • flags: Reserved, currently 0.
  • stream_id: Correlates request/response pairs. 0 for fire-and-forget operations.
  • opcode: Identifies the frame type (see below).
  • payload_len: Byte length of the payload following the header.
  • crc16: CRC-16/XMODEM over the first 10 header bytes.
OpcodeValueDescription
CONNECT0x01Source authentication and connection setup.
ASSERT0x02Assert full entity state (fire-and-forget).
RETRACT0x03Retract entity assertion (fire-and-forget).
QUERY0x04Point read (request-response via stream_id).
SUBSCRIBE0x05Subscribe to entity kind changes.
UNSUBSCRIBE0x06Cancel a subscription.
HEARTBEAT0x07Keep-alive.
PATCH0x08Partial field update with presence bitmask.
SOURCE_EPOCH_BEGIN0x09Begin source epoch reconciliation.
SOURCE_EPOCH_END0x0AEnd source epoch, retract stale entities.
BATCH0x0BBatch of mixed write operations (ADR-0009).
CREATE_KIND0x10Register a new entity kind.
GET_KIND0x11Read-only schema resolution.
OpcodeValueDescription
STATE0x81Entity state notification (created, updated, or bootstrap).
DELETED0x82Entity deleted notification.
BOOTSTRAP_BEGIN0x83Bootstrap snapshot start marker.
BOOTSTRAP_END0x84Bootstrap snapshot end marker.
ACK0x85Acknowledgement.
ERROR0x86Error response.
EVENT0x87Per-assert event for event-stream kinds.

The BATCH opcode packs multiple write operations into a single frame. The SDK uses this transparently when flushing more than one buffered operation.

op_count (u16 LE)
[sub-op]*

Each sub-op starts with an op_type byte matching the corresponding individual opcode, followed by the operation’s fields. Every sub-op ends with an explicit meta_len(u16 LE) + metadata section, even when metadata is empty (2 zero bytes).

op_type(u8) + kind_id(u16) + entity_id(32B) + fd_len(u32) + field_data + meta_len(u16) + metadata
op_type(u8) + kind_id(u16) + entity_id(32B) + meta_len(u16) + metadata
op_type(u8) + kind_id(u16) + entity_id(32B) + presence_mask(u64) + fd_len(u32) + field_data + meta_len(u16) + metadata

When the server receives a BATCH frame, it decodes all sub-ops, groups them by target partition (determined by hashing kind_id + entity_id), and sends a single batched message per partition to the engine thread. This reduces per-partition channel pressure from N messages to 1 per partition touched by the batch.