Kimberlite Wire Protocol Specification
On this page
- Overview
- Connection Lifecycle
- 1. TCP Connection
- 2. Handshake
- 3. Request/Response Loop
- 4. Disconnection
- Frame Format
- Message Types
- Request Message
- Response Message
- Operations
- 1. Handshake
- 2. CreateStream
- 3. AppendEvents
- 4. Query
- 5. QueryAt
- 6. ReadEvents
- 7. Subscribe
- 8. Sync
- Error Codes
- Cluster Behavior
- Leader Discovery
- Postcard Serialization
- Example: CreateStreamRequest
- Example: AppendEventsRequest
- Example: Request with Tenant Context
- Implementation Checklist
- Client Requirements
- Server Requirements
- Security Considerations
- Authentication
- Authorization
- Transport Security
- Denial of Service
- Versioning
- Protocol Version
- Example Session
- Complete Client-Server Exchange
- References
- Changelog
- Version 1.1 (2026-02-09)
- Version 1 (2026-01-30)
- License
Version: 1 Status: Production Authors: Kimberlite Team Last Updated: 2026-01-30
Overview
This document specifies the Kimberlite wire protocol used for client-server communication. Third-party SDK implementers can use this specification to build clients in any programming language.
Protocol Characteristics:
- Transport: TCP with optional TLS 1.3
- Serialization: Bincode (compact binary format)
- Session: Stateful connection with handshake
- Multiplexing: Multiple concurrent requests over single connection
- Ordering: Request-response pairing via request IDs
Connection Lifecycle
1. TCP Connection
Client establishes TCP connection to server:
Client Server
| |
|---- TCP SYN (port 5432) ----------->|
|<--- TCP SYN-ACK --------------------|
|---- TCP ACK ----------------------->|
| |
|---- TLS ClientHello (optional) ---->|
|<--- TLS ServerHello ----------------|
|---- TLS Finished ------------------>|
|<--- TLS Finished -------------------|
| |
Default Port: 5432 (PostgreSQL convention for familiarity) TLS: Optional (disabled by default for local dev, required for production)
2. Handshake
First message after connection MUST be a Handshake request:
Server responds with HandshakeResponse:
Authentication:
auth_tokencan beNonefor local development- Production deployments should require valid JWT or API key
- Server sets
authenticated: falseif token is invalid
3. Request/Response Loop
After successful handshake, client sends requests and receives responses:
Client Server
| |
|---- HandshakeRequest -------------->|
|<--- HandshakeResponse (auth=true) --|
| |
|---- CreateStreamRequest (id=1) ---->|
|---- AppendEventsRequest (id=2) ---->|
|<--- CreateStreamResponse (id=1) ----|
|<--- AppendEventsResponse (id=2) ----|
| |
Key Properties:
- Responses may arrive out of order (use
request_idto match) - Client must handle concurrent responses
- Each request includes tenant context
4. Disconnection
Either side can close the TCP connection:
- Graceful: Client sends all pending requests, waits for responses, then closes
- Abrupt: Connection loss (network failure, server restart)
Retries: Client should implement exponential backoff with jitter for connection failures.
Frame Format
All messages are framed with a header followed by payload:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Magic (0x56444220) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Version (u16) | Payload Length (u32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| CRC32 Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Payload (Bincode) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Field Descriptions:
| Field | Size | Description |
|---|---|---|
| Magic | 4 bytes | 0x56444220 (“VDB “ in ASCII) |
| Version | 2 bytes | Protocol version (current: 1) |
| Payload Length | 4 bytes | Length of payload in bytes (max: 16 MiB) |
| CRC32 Checksum | 4 bytes | CRC32 checksum of payload |
| Payload | Variable | Bincode-serialized message |
Constants:
MAGIC:0x56444220MAX_PAYLOAD_SIZE: 16 MiB (16,777,216 bytes)FRAME_HEADER_SIZE: 14 bytes
Validation:
- Check magic number (reject if mismatch)
- Check version (reject if unsupported)
- Check payload length (reject if > MAX_PAYLOAD_SIZE)
- Read payload bytes
- Verify CRC32 checksum (reject if mismatch)
- Deserialize payload with Bincode
Message Types
Request Message
Response Message
Operations
1. Handshake
Request:
Response:
Capabilities:
"query": Server supports SQL queries"append": Server supports event appending"subscribe": Server supports real-time event subscriptions"query_at": Server supports point-in-time queries"sync": Server supports explicit sync operations"cluster": Server is part of a cluster (has leader/follower)
Errors:
AuthenticationFailed: Invalid or expired tokenUnsupportedVersion: Client version not supported
2. CreateStream
Request:
Response:
Errors:
StreamAlreadyExists: Stream name already exists in tenantTenantNotFound: Tenant ID does not exist
3. AppendEvents
Request:
Response:
Semantics:
- Events are assigned sequential offsets starting at
first_offset - Maximum batch size: 10,000 events or 4 MiB total payload (whichever is smaller)
- Events in batch have offsets:
[first_offset, first_offset+1, ..., first_offset+count-1]
Errors:
StreamNotFound: Stream ID does not existInvalidRequest: Batch too large or empty
Note: Optimistic concurrency control is implemented in the kernel but not yet exposed in the wire protocol. The kernel supports an expected_offset field in AppendBatch commands that validates the stream hasn’t advanced before appending. This will be added to the wire protocol in a future version with error code 16 (OffsetMismatch). See ROADMAP.md for details.
4. Query
Request:
Response:
SQL Dialect: Subset of SQL-92 with DuckDB extensions
- Supported: SELECT, WHERE, GROUP BY, ORDER BY, LIMIT, JOINs
- Partially Supported: INSERT (via append-only semantics), CREATE TABLE (as stream projection)
- Unsupported: UPDATE, DELETE (append-only log)
Errors:
QueryParseError: Invalid SQL syntaxQueryExecutionError: Runtime error (e.g., division by zero)TableNotFound: Referenced table/view does not exist
5. QueryAt
Request:
Response:
pub type QueryAtResponse = QueryResponse;
Semantics:
- Executes query as if database state is at specified log position
- Used for point-in-time compliance queries
- Position must be at a committed transaction boundary
Errors:
PositionAhead: Position is beyond current log headProjectionLag: Projections not caught up to requested position (retry)- Same errors as
Query
6. ReadEvents
Request:
Response:
Semantics:
- Returns events in offset order starting from
from_offset - Stops when
max_byteswould be exceeded (returns fewer events if needed) - If
from_offsetis beyond stream end, returns empty array withnext_offset: None - For pagination, use returned
next_offsetasfrom_offsetin next request
Errors:
StreamNotFound: Stream ID does not existInvalidOffset: Offset is invalid (negative)
7. Subscribe
Request:
Response:
Semantics:
- Creates a real-time subscription to a stream starting at
from_offset - Server validates that the stream exists before establishing the subscription
subscription_idis deterministic (derived from tenant + stream) for idempotent reconnection- Credits control flow: client requests more credits as it processes events
- Consumer groups enable coordinated consumption across multiple clients
Errors:
StreamNotFound: Stream ID does not existInvalidOffset: Starting offset is invalid
8. Sync
Request:
Response:
Semantics:
- Forces all buffered writes to disk (fsync)
- Used to ensure durability before critical operations
- Blocks until sync completes
Errors:
StorageError: Underlying storage sync failed
Error Codes
| Code | Name | Description | Retryable |
|---|---|---|---|
| 0 | Unknown | Unknown error | No |
| 1 | InternalError | Server internal error | Yes |
| 2 | InvalidRequest | Invalid request format or parameters | No |
| 3 | AuthenticationFailed | Authentication failed | No |
| 4 | TenantNotFound | Tenant ID does not exist | No |
| 5 | StreamNotFound | Stream ID does not exist | No |
| 6 | TableNotFound | Table/view not found in query | No |
| 7 | QueryParseError | Invalid SQL syntax | No |
| 8 | QueryExecutionError | Query runtime error | No |
| 9 | PositionAhead | Position beyond current log | No |
| 10 | StreamAlreadyExists | Stream name already exists | No |
| 11 | InvalidOffset | Invalid stream offset | No |
| 12 | StorageError | Storage layer error | Yes |
| 13 | ProjectionLag | Projections not caught up | Yes |
| 14 | RateLimited | Rate limit exceeded | Yes |
| 15 | NotLeader | Server is not cluster leader | Yes |
Note on Future Error Codes:
- Error codes 16+ are reserved for future use
- Error code 16 (
OffsetMismatch) is planned for optimistic concurrency control (see ROADMAP.md)
Retry Policy:
- Retryable errors: Use exponential backoff (100ms, 200ms, 400ms, …)
- Non-retryable errors: Fail immediately, report to caller
- For
NotLeader, client should discover and reconnect to leader
Cluster Behavior
Leader Discovery
Kimberlite clusters use a single-leader model:
- All writes go to leader
- Reads may go to followers (eventual consistency)
Discovery Protocol:
- Client connects to any server in cluster
- If server is not leader for write operations, it returns
NotLeadererror - Error message may include leader hint (e.g., “not leader, try 192.168.1.10:5432”)
- Client reconnects to leader
- Client caches leader address for future connections
Recommended Client Behavior:
- Maintain connection pool with all cluster addresses
- Health-check all connections every 30 seconds
- On
NotLeader, parse error message for leader hint - Cache leader address for fast-path reconnection
Postcard Serialization
Kimberlite uses Postcard for efficient, stable binary serialization.
Key Properties:
- Variable-length integers: Efficient varint encoding (smaller payloads)
- Stable wire format: Guaranteed compatibility across versions
- Zero-copy deserialization: Minimal allocation overhead
- Strings: Length-prefixed (varint length + UTF-8 bytes)
- Vectors: Length-prefixed (varint length + elements)
- Enums: Discriminant (varint) + variant data
- Option: Discriminant (0 = None, 1 = Some) + value if Some
- No_std compatible: Works in constrained environments
Example: CreateStreamRequest
CreateStreamRequest
Binary Encoding (hex):
06 00 00 00 00 00 00 00 // name length (6)
65 76 65 6E 74 73 // "events" (UTF-8)
00 00 00 00 // data_class discriminant (0 = PHI)
00 00 00 00 // placement discriminant (0 = Global)
Example: AppendEventsRequest
AppendEventsRequest
Binary Encoding (hex):
2A 00 00 00 00 00 00 00 // stream_id (42)
02 00 00 00 00 00 00 00 // events.len (2)
03 00 00 00 00 00 00 00 // events[0].len (3)
01 02 03 // events[0] bytes
02 00 00 00 00 00 00 00 // events[1].len (2)
04 05 // events[1] bytes
Example: Request with Tenant Context
Request
Binary Encoding (hex):
01 00 00 00 00 00 00 00 // id (1)
2A 00 00 00 00 00 00 00 // tenant_id (42)
00 00 00 00 // payload discriminant (0 = Handshake)
01 00 // client_version (1)
00 00 00 00 // auth_token discriminant (0 = None)
Implementation Checklist
Client Requirements
- TCP connection with optional TLS
- Frame parsing (magic, version, length, CRC32)
- Bincode deserialization
- Request ID generation (monotonic u64)
- Response matching (request_id → pending request)
- Error handling (map error codes to exceptions/errors)
- Handshake on connection
- Tenant ID management
- Leader discovery and failover (cluster mode)
- Connection pooling
- Health checks
Server Requirements
- TCP listener with TLS support
- Handshake handling (version negotiation)
- Frame writing (header + CRC32 + payload)
- Bincode serialization
- Request routing to operation handlers
- Error response generation
- Tenant isolation
- Leader election (cluster mode)
- Rate limiting
Security Considerations
Authentication
- Production: REQUIRED TLS + token-based auth (JWT recommended)
- Development: TLS optional, token may be empty
Authorization
- Tenant Isolation: All operations scoped to tenant_id in request
- Stream Permissions: Per-stream access control (future feature)
- Data Class Restrictions: PHI streams may require elevated privileges
Transport Security
- TLS Version: 1.3 only (1.2 deprecated)
- Cipher Suites: AES-256-GCM, ChaCha20-Poly1305
- Certificate Validation: Client must verify server certificate
Denial of Service
- Rate Limiting: Server enforces per-tenant request rate limits
- Payload Size: Max 16 MiB per message
- Connection Limits: Max 1,000 concurrent connections per tenant
- Batch Limits: Max 10,000 events or 4 MiB per append
Versioning
Protocol Version
Current version: 1
Version Negotiation:
- Client sends
client_version: 1inHandshakeRequest - Server responds with
server_version: 1inHandshakeResponse - If versions incompatible, server returns
UnsupportedVersionerror
Backward Compatibility:
- Minor changes (new optional fields) do not increment version
- Breaking changes (field removal, type changes) increment version
- Server may support multiple versions simultaneously
Example Session
Complete Client-Server Exchange
1. Connect
Client -> Server: TCP SYN
Server -> Client: TCP SYN-ACK
Client -> Server: TCP ACK
2. Handshake
Client -> Server:
Frame Header:
Magic: 0x56444220
Version: 1
Payload Length: 64
CRC32: 0xABCD1234
Payload (Bincode):
Request {
id: 1,
tenant_id: 42,
payload: Handshake(HandshakeRequest {
client_version: 1,
auth_token: Some("secret-token")
})
}
Server -> Client:
Frame Header: ...
Payload:
Response {
request_id: 1,
payload: Handshake(HandshakeResponse {
server_version: 1,
authenticated: true,
capabilities: ["query_at", "sync"]
})
}
3. Create Stream
Client -> Server:
Request {
id: 2,
tenant_id: 42,
payload: CreateStream(CreateStreamRequest {
name: "events",
data_class: DataClass::PHI,
placement: Placement::Global
})
}
Server -> Client:
Response {
request_id: 2,
payload: CreateStream(CreateStreamResponse {
stream_id: StreamId(100)
})
}
4. Append Events
Client -> Server:
Request {
id: 3,
tenant_id: 42,
payload: AppendEvents(AppendEventsRequest {
stream_id: StreamId(100),
events: vec![b"event1".to_vec(), b"event2".to_vec()]
})
}
Server -> Client:
Response {
request_id: 3,
payload: AppendEvents(AppendEventsResponse {
first_offset: Offset(0),
count: 2
})
}
5. Query
Client -> Server:
Request {
id: 4,
tenant_id: 42,
payload: Query(QueryRequest {
sql: "SELECT COUNT(*) as count FROM events",
params: vec![]
})
}
Server -> Client:
Response {
request_id: 4,
payload: Query(QueryResponse {
columns: vec!["count".to_string()],
rows: vec![
vec![QueryValue::BigInt(2)]
]
})
}
6. Disconnect
Client -> Server: TCP FIN
Server -> Client: TCP FIN-ACK
References
- Bincode Specification: https://github.com/bincode-org/bincode/blob/trunk/docs/spec.md
- CRC32 Algorithm: Uses
crc32fastcrate (Castagnoli polynomial) - TLS 1.3: RFC 8446
Changelog
Version 1.1 (2026-02-09)
- Added Subscribe operation for real-time event streaming with credit-based flow control
- Added consumer group support for coordinated subscription
- Updated handshake capabilities to advertise
"query","append","subscribe"
Version 1 (2026-01-30)
- Initial production protocol specification
- Core operations: Handshake, CreateStream, AppendEvents, Query, QueryAt, ReadEvents, Sync
- 16 error codes covering common failure modes
- Bincode serialization with fixed-width encoding
- Multi-tenant request structure
- Point-in-time queries via QueryAt
- Cluster support with leader discovery
License
This specification is licensed under CC BY 4.0. Implementations may use any license.