The wire protocol between a PlugRL training server, which holds the policy and the learning algorithm, and an env client, which runs environments and asks for actions.
The two are separate processes, usually on separate machines, deliberately: an environment stack and a training stack can then be installed, versioned and scheduled independently, and an env client need not be Python at all.
SPEC.md is the specification. It is normative, it names its own known defects, and everything in it that can be checked by a test is.
SPEC.md |
the protocol, in full |
src/plugrl_protocol/ |
the message types and the msgpack codec — 101 lines |
examples/ |
two env clients written against the spec, sharing no code with PlugRL |
tests/ |
the spec's checkable clauses, as tests |
The package is small on purpose. It is the piece both sides import, so anything that could live on one side does.
Four message types, four lowercase strings. The server speaks first.
client server
|------------ WebSocket handshake ---------->|
|<---------------- metadata -----------------|
|------------------ infer ------------------>| observations
|<----------------- action ------------------| an action chunk
|----------------- feedback ---------------->| reward, done, next obs
Messages are msgpack maps carrying a message_type. Arrays travel as
{b"__ndarray__": true, b"data": <bin>, b"dtype": "<f4", b"shape": [4, 1, 7]}
dtype is a numpy typestr — a byte-order character, a kind character and an
item size. It is nearly the only piece of numpy vocabulary on the wire, and
about ten lines of code to parse. The exception is the text field; see
SPEC.md section 3.4, which does not
pretend otherwise.
Three things a first implementation usually gets wrong, all specified:
- messages strictly alternate infer, action, feedback — the server has no dispatcher (section 4.2);
- the environment set in a
feedbackneed not match the one in theinferit follows (section 4.3); - the reward in a
feedbackis the sum over the action chunk, not the last step's (section 5.4).
The serialization is openpi's.
msgpack_numpy.py is taken from it under Apache-2.0 (attributed in the file
header and in NOTICE) and reformatted only — the array encoding
is byte-identical, so the part of a client that takes real work in C++ or
Rust carries across between the two ecosystems.
The message layer is where they differ: openpi sends a bare observation and
gets a bare action back, which is what serving a policy needs. PlugRL wraps
both in an envelope and adds a feedback return channel, which is what
training one needs. SPEC.md section 9 has
the full comparison.
Not on PyPI. Install from git, pinned to a commit - which is how
plugrl-server and plugrl-env-client depend on it:
uv add "plugrl-protocol @ git+https://github.com/PlugRL/plugrl-protocol.git@<commit>"
# or
pip install "plugrl-protocol @ git+https://github.com/PlugRL/plugrl-protocol.git@<commit>"Only Python clients that want the shared codec need this at all. A client in
another language should implement SPEC.md directly, which is what
the C++ example does - and what examples/conformance_server.py will grade
it against.