This documents the raw binary TCP protocol RedBoxServer speaks, as
implemented in src/server.cpp. It supersedes the summary comment
previously at the bottom of that file (kept in sync, but this is the
complete reference — including two fields that comment omitted).
There is no HTTP, no JSON, no framing library — just a fixed-size header followed by a command-specific payload, all little-endian.
Every request from client to server starts with the same 5-byte header:
| Offset | Field | Type | Size | Description |
|---|---|---|---|---|
| 0 | CMD | uint8 |
1 byte | Command opcode (see Commands) |
| 1 | META | uint32 |
4 bytes | Context-dependent (vector ID, count, length…) |
| 5 | — | bytes | N bytes | Optional payload, meaning depends on CMD |
The server reads exactly 5 bytes for the header, then reads whatever
payload that command requires (usually a fixed number of bytes derived
from the active database's dimension, or from a length META field).
There's no total-length prefix — the client and server must agree on
how many bytes a given command's payload is, which is why an active
database (for its dim) is required before most commands work at all.
A freshly-connected socket has no active database. The first
command must be SELECT_DB (opens or creates an IVF-indexed database)
or CREATE_HNSW_DB (opens or creates an HNSW-indexed database) — every
other command checks for an active database and the connection is
dropped if there isn't one yet (if (!active_db) break;).
Selecting a database that already exists on disk just opens it; the
dim/capacity (and, for HNSW, M/ef_construction) you pass are
only used if the database doesn't exist yet. If it does exist and your
requested dim doesn't match the file's actual dimension, the server
logs a warning server-side but still acks with 1 and lets you
proceed — mismatched-dimension inserts/searches will misbehave, this
is not currently rejected at the protocol level.
CMD ID values are defined in src/server.cpp. All multi-byte
integers are little-endian.
Insert a vector with a caller-specified ID.
- META: vector ID (
uint32, truncated — see Notes) - Payload:
dim * 4bytes, rawfloat32vector data - Response:
1byte, always'1'
Find the single nearest vector by L2 distance.
- META: ignored
- Payload:
dim * 4bytes, rawfloat32query vector - Response:
4bytes,int32result ID (-1if the database is empty)
Soft-delete a vector by ID (excluded from future searches; tombstoned,
not removed from disk immediately — see docs/DEPLOYMENT.md).
- META: vector ID (
uint32) - Payload: none
- Response:
1byte,'1'if found and deleted,'0'otherwise
Open (or create, if it doesn't exist) an IVF-indexed database and make it the connection's active database.
- META: name length in bytes (
uint32— see Database name rules; a length over 64 drops the connection immediately, a length of 0 is read fine but then fails validation like any other invalid name) - Payload:
<name bytes><dim: uint32><capacity: uint32>— the in-code protocol summary comment omits the trailingdim/capacityfields; they are required. - Response:
1byte —'0'if the name fails validation (a zero-length name always fails; see Database name rules — connection stays open, you can retry);'1'on success. Ifname_lenexceeds 64, the server doesn't respond at all and drops the connection.
Overwrite an existing vector's data in place by ID.
- META: vector ID (
uint32) - Payload:
dim * 4bytes, rawfloat32vector data - Response:
1byte,'1'if the ID exists (and isn't deleted),'0'otherwise
Insert a vector with a server-assigned, auto-incrementing ID.
- META: ignored
- Payload:
dim * 4bytes, rawfloat32vector data - Response:
8bytes,uint64assigned ID
Find the N nearest vectors, closest first.
- META: N (
uint32, cast tointserver-side;N <= 0is accepted and just returns zero results rather than erroring) - Payload:
dim * 4bytes, rawfloat32query vector - Response:
<count: uint32><ids: int32 * count>—countmay be0with no IDs following
Drop the connection's active database entirely: removes it from the
in-memory catalog, deletes <name>.db and <name>.db.del from disk.
The connection has no active database afterward.
- META: ignored
- Payload: none
- Response:
1byte,'1'on success,'0'if there was no active database to identify (shouldn't normally happen given the lifecycle rule above)
Set the number of IVF clusters to probe during search. No-op (but
still acks) on an HNSW database, or if probes is 0.
- META: probe count (
uint32, cast touint8server-side — valid range is really 1–255) - Payload: none
- Response:
1byte, always'1'
Open (or create) an HNSW-indexed database and make it the connection's active database.
- META: name length in bytes (
uint32, same 64-byte limit asSELECT_DB) - Payload:
<name bytes><dim: uint32><capacity: uint32><M: uint8><ef_construction: uint16> - Response:
1byte — same'0'/'1'/drop-connection behavior asSELECT_DBfor invalid names; no explicit ack byte is sent for thename_len > 64case (connection is dropped)
Set the ef_search parameter (search-time candidate list size) for
HNSW queries against the active database.
- META: ef value (
uint32, cast touint16server-side) - Payload: none
- Response:
1byte, always'1'
List every database known to the server's metadata store.
Requires the server to be built with PostgreSQL support: the
REDBOX_ENABLE_PG CMake option (ON by default, auto-disables if
find_package(PostgreSQL) doesn't find it) compiles in a metadata
store backed by libpqxx, defining REDBOX_PG_ENABLED and connecting
via REDBOX_PG_* environment variables (see .github/workflows/ci.yml
for the exact variable names). Without PG support compiled in and
configured, this command always responds with a count of 0 rather
than erroring.
- META: ignored
- Payload: none
- Response:
<count: uint32>, followed bycountentries of:<name_len: uint8><name: name_len bytes><dimensions: uint32><index_type: uint8><vector_count: uint64>—index_typeis0for IVF,1for HNSW (CoreEngine::IndexType)
Get detailed stats for the connection's active database.
Also requires REDBOX_PG_ENABLED.
- META: ignored
- Payload: none
- Response:
1byteokflag, then — only ifok == 1—<vector_count: uint64><capacity: uint64><next_id: uint64><index_type: uint8><dim: uint32>.okis0(with no further bytes sent) if there's no active database, the server wasn't built with PG support, or PG isn't configured/reachable.
Applies to both SELECT_DB and CREATE_HNSW_DB:
- 1–64 bytes long (
0 < len <= 64) - ASCII alphanumeric,
_, or-only — anything else (including non-ASCII/UTF-8 multi-byte sequences) is rejected
A name that's too long causes the server to close the connection
without responding. A name that's the right length but fails character
validation gets an explicit '0' response and the connection stays
open.
- The META field is
uint32on the wire, but IDs areuint64internally —INSERT_AUTO's response is a full 8-byte ID, andRedBoxVector::insert()takes auint64_t id, butINSERT/DELETE/UPDATEcan only ever address a vector via the 4-byte META field. There is no way toINSERT(with a caller-chosen ID) orDELETE/UPDATEa vector whose ID is above2^32 - 1— the ID space these three commands can reach is narrower than the ID spaceINSERT_AUTOcan produce. What a given client does when asked to encode a too-large ID depends on the client (Client/client.py'sstruct.pack('<BI', ...)raisesstruct.errorrather than truncating silently, since Python'sstructmodule rejects out-of-range values for'I'; a client written in a language that does a raw narrowing cast instead could truncate silently). Tracked as a protocol-level limitation in #88; documenting the wire-format fact here so it's not a surprise regardless of how #88 gets resolved. - No request-response correlation. Responses are read strictly in
the order requests were sent on a single connection — there's no
request ID to match a response back to a specific call. Concurrent
requests on one connection are not safe; use one connection per
concurrent caller (or hold a lock around request/response pairs, as
Client/client.pyimplicitly does by being synchronous). Client/client.pyis the reference implementation of this protocol from the client side — see_handshake(),_handshake_hnsw(), and the per-command methods for exactstruct.packlayouts matching everything above.