Skip to content

Wire Protocol

Flo uses a custom binary protocol for client-server communication on port 9000. The protocol supports TLV (Type-Length-Value) encoding for efficiency and zero-copy parsing.

Clients connect via TCP to the server’s client port (default 9000). The first bytes sent identify the protocol:

Magic BytesProtocol
FLO\x01Binary wire protocol
*, $, +, -RESP (Redis protocol)
GET, POST, …HTTP (redirected to dashboard)
WebSocket upgradeWebSocket (binary protocol over WS frames)

The Acceptor thread peeks at the first bytes to detect the protocol and routes accordingly.

Every request has a fixed 32-byte header followed by a variable-length payload:

┌──────────────────────────────────────────────────────────────────┐
│ Request Header (32 bytes) │
├──────────┬──────────┬───────┬──────────┬──────────┬──────────────┤
│ magic │ opcode │ flags │ req_id │ payload │ namespace │
│ (4B) │ (2B) │ (2B) │ (8B) │ _len(4B) │ _hash (8B) │
├──────────┴──────────┴───────┴──────────┴──────────┴──────────────┤
│ key_length │ padding │
│ (2B) │ (2B) │
├────────────┴─────────────────────────────────────────────────────┤
│ Payload (variable) │
│ [key bytes] [value bytes] [optional TLV fields] │
└──────────────────────────────────────────────────────────────────┘
FieldTypeDescription
magicu32Protocol magic: 0x464C4F01 (“FLO\x01”)
opcodeu16Operation code (see OpCode table)
flagsu16Request flags (compression, etc.)
req_idu64Client-assigned request ID for response matching
payload_lenu32Length of payload following the header
namespace_hashu64FNV-1a hash of the namespace string
key_lengthu16Length of the key in the payload
┌──────────────────────────────────────────────────────────────────┐
│ Response Header (24 bytes) │
├──────────┬──────────┬───────┬──────────┬──────────────────────────┤
│ magic │ status │ flags │ req_id │ payload_len │
│ (4B) │ (2B) │ (2B) │ (8B) │ (4B) │
├──────────┴──────────┴───────┴──────────┴──────────────────────────┤
│ Payload (variable) │
└──────────────────────────────────────────────────────────────────┘
CodeNameDescription
0x00OKOperation succeeded
0x01NOT_FOUNDKey / queue / stream not found
0x02BAD_REQUESTInvalid request parameters
0x03CONFLICTCAS version mismatch
0x04UNAUTHORIZEDAuthentication required
0x05OVERLOADEDServer at capacity (retry)
0x06INTERNAL_ERRORServer error
0x07NOT_LEADERNode is not the leader for this partition

The protocol defines 167 operation codes organized by subsystem:

OpCodeValueDescription
kv_put0x100Set key-value pair (returns new version)
kv_get0x101Get value + version by key
kv_mget0x102Multi-key get
kv_delete0x103Delete a key
kv_scan0x104Prefix scan
kv_history0x105Get version history
kv_incr0x10BAtomic counter (signed i64 delta, default +1)
kv_json_get0x10CExtract JSON sub-field via JSONPath
kv_json_set0x10DAtomically set a JSON sub-field
kv_json_del0x10EAtomically delete a JSON sub-field
kv_touch0x113Update TTL on existing key (no value rewrite)
kv_persist0x114Clear TTL (make key permanent)
kv_exists0x115Existence check (no value transferred)
kv_begin_txn0x110Open a per-shard transaction (returns txn_id + pinned partition hash)
kv_commit_txn0x111Commit a transaction atomically (returns commit_index + op_count)
kv_rollback_txn0x112Discard a transaction (idempotent)
kv_txn_response0x119Reply envelope for ops executed inside a transaction

Response opcodes occupy 0x106–0x10A and 0x116–0x118. CAS is expressed as a TLV option on kv_put, not a separate opcode. Per-shard transactions (see Transactions) are scoped to a single partition and carry the open txn_id via the txn_id TLV option (tag 0x09, u64 LE) on subsequent KV ops.

OpCodeValueDescription
queue_enqueue0x20Add message to queue
queue_dequeue0x21Fetch and lease messages
queue_ack0x22Acknowledge messages
queue_nack0x23Negative acknowledge
queue_peek0x24Peek without leasing
queue_touch0x25Extend lease
queue_dlq_list0x26List DLQ messages
queue_dlq_requeue0x27Requeue from DLQ
OpCodeValueDescription
stream_append0x30Append record
stream_read0x31Read records
stream_create0x32Create stream
stream_info0x33Stream metadata
stream_trim0x34Trim old records
stream_group_create0x35Create consumer group
stream_group_read0x36Consumer group read
stream_group_ack0x37Consumer group ack
OpCodeValueDescription
ts_write0x40Write data point
ts_write_batch0x41Batch write points
ts_query0x42Execute FloQL query
ts_create_measurement0x43Create measurement
OpCodeValueDescription
action_register0x50Register action type
action_invoke0x51Invoke action
action_status0x52Get execution status
action_result0x53Get execution result
OpCodeValueDescription
worker_register0x60Register worker
worker_await0x61Await task assignment
worker_complete0x62Complete task
worker_fail0x63Fail task
worker_heartbeat0x64Worker heartbeat
worker_touch0x65Extend task lease
OpCodeValueDescription
cluster_join0xC0Join cluster
cluster_leave0xC1Leave cluster
cluster_status0xC2Cluster health
cluster_partition_table0xC3Get partition table

Some operations include optional TLV (Type-Length-Value) fields after the key and value:

┌──────┬────────┬───────────┐
│ type │ length │ value │
│ (1B) │ (2B) │ (var) │
└──────┴────────┴───────────┘
TypeNameUsed By
0x01TTLKV put
0x02CAS versionKV put/delete
0x03PriorityQueue enqueue
0x04DelayQueue enqueue
0x05Dedup keyQueue enqueue
0x06Block timeoutGet, dequeue, stream read
0x07Consumer groupStream group operations
0x08Idempotency keyAction invoke

Flo also accepts Redis RESP protocol on the same port. The Acceptor detects RESP by the leading character (*, $, +, -, :). RESP commands are translated to Flo operations:

RESP CommandFlo Operation
SET key value [EX sec] [NX] [XX]kv_put
GET keykv_get
DEL keykv_delete
EXISTS keykv_exists
INCR key / INCRBY key n / DECR keykv_incr
EXPIRE key seckv_touch
PERSIST keykv_persist
JSON.GET key [path]kv_json_get
JSON.SET key path valuekv_json_set
JSON.DEL key [path]kv_json_del
SCAN cursor MATCH patternkv_scan
LPUSH queue valuequeue_enqueue
RPOP queuequeue_dequeue
XADD stream * field valuestream_append
XREAD COUNT n STREAMS stream idstream_read

This allows Redis clients (redis-cli, ioredis, redis-py, go-redis, RedisInsight, etc.) to connect to Flo without modification for the operations above. See Redis compatibility for the full mapping and known semantic differences.