Developer Integration
SnakeLab exposes three ZeroMQ interfaces for downstream systems. Install
pyzmq for Python clients and replace wintermute with your server hostname.
| Interface | Default endpoint | Client socket | Reference |
|---|---|---|---|
| Control | tcp://wintermute:41970 |
REQ |
Control protocol |
| Events | tcp://wintermute:41972 |
SUB |
Event protocol |
| Telemetry | tcp://wintermute:41971 |
SUB |
Live telemetry |
- Establish an event subscription before submitting work.
- Call
simulation.submiton the control port with a configuration object. - Save the returned
run_id; the reply confirms the run is queued. - React to
simulation_endedevents whosepayload.run_idmatches that run.
The event is an asynchronous completion result. Delivery is best effort, with
no acknowledgement or replay; use simulation.status to reconcile missed
notifications while the run is known to the server. Every submission creates
a new run, including retries after a timeout.
Control and telemetry use protocol version 1. Events use their independently versioned protocol, currently version 2.
Live Telemetry
Live telemetry is published on tcp://wintermute:41971. Connect a ZeroMQ SUB
socket and subscribe to one or more topics:
snake_lab.runsnake_lab.episodesnake_lab.frame
Per-move frames are built and published only while a subscription matches
snake_lab.frame. Opening lab-client during a run enables streaming on
subsequent moves; closing the last viewer disables it once ZeroMQ reports the
disconnection. Prefix subscriptions such as snake_lab. and the empty filter
(all topics) also enable frames. Subscribing only to run, episode, or completion
events does not enable them. Subscription and disconnect detection are
asynchronous; joining a paused run provides a fresh frame on its next move.
Run/episode telemetry, stored results, and completion events continue independently.
No client change or simulation configuration option is required.
Each publication is a two-part message: the UTF-8 topic followed by a JSON
envelope containing protocol_version, sequence, run_id, and payload.
Subscribe before submitting a run when the initial lifecycle events are needed.
The project’s control client and telemetry client are the reference implementations.
High-score Board Snapshots
Every successfully completed simulation saves one board: the first position where it achieved its final high score. Each episode retains its highest-scoring immutable board by reference, then replaces the simulation’s best board only if its completed score is higher. Ties keep the earlier board. A simulation that never scores saves the first completed episode’s starting board.
Capture works without a viewer or telemetry subscription. There are no board
copies or database writes in the capture hot loop. The server saves the winning
snapshot with the final score and completed status before publishing
simulation_ended. Failed and cancelled simulations do not save snapshots.
Request a saved board on the control endpoint using a REQ socket:
{
"protocol_version": 1,
"request_id": "snapshot-1",
"method": "simulation.highscore_snapshot",
"payload": {"run_id": "<run ID>"}
}
The successful response has status: "ok" and a payload containing run_id
and snapshot. The snapshot fields are:
| Field | Meaning |
|---|---|
version |
Snapshot format version, currently 1; separate from the control protocol version |
episode |
Episode number, starting at 1 |
step |
Move number within the episode; 0 for a starting-board capture |
board.grid_size |
[width, height] |
board.snake_head |
Head coordinate [x, y] |
board.snake_body |
Coordinates ordered from neck to tail, excluding the head |
board.food |
Food coordinate, or null when the board is full |
board.direction |
Direction vector [dx, dy] |
board.score |
The simulation’s final high score |
Coordinates are zero-based, with x increasing rightward and y downward.
The board object can be passed directly to BoardSnapshot.from_dict().
Clients own rendering and image export; the server returns raw JSON, not SVG
or PNG. See the control protocol
for an example response payload.
Python clients using the project’s AsyncLabClient can request the full
response envelope with:
response = await client.highscore_snapshot(run_id)
if response["status"] == "ok":
snapshot = response["payload"]["snapshot"]
board = snapshot["board"]
# Render or export the board in your client.
else:
code = response["error"]["code"]
# Handle unavailable snapshots separately from unknown run IDs.
Lookup reads the database, so saved snapshots remain accessible after server restarts. Existing runs retain their scores and configurations but have no snapshot if they completed before capture was implemented. The server does not replay simulations or reconstruct missing boards.
| Error code | Meaning |
|---|---|
run_not_found |
The run ID does not exist in the database |
snapshot_unavailable |
The run exists but has no saved snapshot, including old, unfinished, failed, or cancelled runs |
invalid_request |
The payload must contain only a non-empty string run_id |
Existing clients can continue using the control and event protocols without requesting snapshots. An optimizer such as AX3L can request a snapshot using the run ID it selects as its golden configuration; SnakeLab does not select the golden configuration itself.
Simulation Execution
Game logic, policy inference, replay storage, and training run on CPU. The simulator uses the original Python game rules, Python exploration generator, and NumPy replay batches. Episodes remain serial with one training attempt after each completed episode when a batch is available. CUDA availability does not change the selected device.
This restores the execution path used before 0.10.2, with inference and training now forced to CPU. Seeded trajectories differ from the tensor-based 0.10.2 release; identical results across hardware or library versions are not guaranteed. Subscription-driven telemetry and completion event semantics are unchanged.
Replay and Training
Replay stores complete episodes and samples fixed-length sliding windows uniformly across all eligible windows, without replacement within a batch. Windows never cross episode boundaries. Longer games contribute more windows; a batch can contain multiple windows from the same game.
The defaults are training.batch_size: 64 (windows) and
training.sequence_length: 8 (moves). Training starts when enough windows
exist for a batch; there is no minimum episode count. Each sampled window
contributes its final move as the loss target. Terminal moves are included
when their ending windows are selected.
The whole-game sampling and terminal-aligned chunking introduced in 0.10.4
have been rolled back. Remove training.replay_min_episodes from saved
configurations created with that feature. Explicit batch_size values remain
supported and now count windows again; use 64 to match the restored default.
Configuration Queries
The configurations table stores one row per accepted run, linked to
simulation_runs.run_id. Its 26 numeric columns follow the configuration
schema, replacing dots with underscores: training.learning_rate becomes
training_learning_rate, and game.rewards.food becomes game_rewards_food.
seed uses BIGINT UNSIGNED; other integers use INT UNSIGNED and numbers
use DOUBLE. Configuration and run creation commit together. Configuration values are stored only in configurations.
Repeated configurations have separate rows for each run. Join to run status when searching completed experiments:
SELECT c.training_learning_rate, c.model_hidden_size,
COUNT(*) AS completed_runs, MAX(r.high_score) AS best_score
FROM configurations AS c
JOIN simulation_runs AS r ON r.run_id = c.run_id
WHERE r.status = 'completed'
GROUP BY c.training_learning_rate, c.model_hidden_size;
See the changelog for the one-time v0.13.0 clean database setup.
Schema snake_lab/schemas/database-v3.sql only creates the new table. It can
be reapplied safely and does not backfill historical runs or delete data.
New accepted runs receive configuration rows immediately, regardless of their
eventual status. Deleting a run cascades to its configuration row.
Schema snake_lab/schemas/database-v4.sql adds nullable JSON column
simulation_runs.high_score_snapshot for one saved board per run. It can be
reapplied safely without deleting existing data or backfilling old snapshots.
NULL means no snapshot is available. See High-score Board Snapshots
for capture behavior and the retrieval API.
scripts/upgrade.sh applies the migration while the service is stopped,
before deploying and starting the application. To apply the schema separately:
sudo systemctl stop snake-lab.service
sudo scripts/apply-database-schema.sh
# Restart after the schema command succeeds.
sudo systemctl start snake-lab.service
To run the optional MariaDB integration test, set SNAKELAB_TEST_DB_SOCKET to
an isolated test server’s Unix socket. The test uses passwordless root access
and creates and drops a randomly named test database.