Developer overview · Control protocol

Transport and Envelope

The server publishes events on tcp://wintermute:41972. --events-port changes the port; --address selects the bind address for all three interfaces. Connect a ZeroMQ SUB socket and subscribe to the desired topic. Each publication has exactly two frames: a UTF-8 topic, followed by a UTF-8 JSON object with these fields:

Field Type Meaning
protocol_version integer Event protocol version, currently 2
event_type string Explicit event identifier from the table below
payload object Fields defined for that event type

Event definitions and validation live in snakelab/zmq/ZMQHelper.py. event_message(event_type, payload) builds an outgoing envelope and copies its trusted internal payload without validation. parse_event(decoded_json) validates incoming envelopes, rejecting unknown fields, event types, and versions with ProtocolError (invalid_event, unknown_event, or unsupported_protocol).

Supported Event Types

Event type Constant Subscription topic Required payload Optional payload
simulation_ended EVENT_SIMULATION_ENDED snake_lab.simulation.ended run_id, state error

simulation_ended

Signals that a simulation reached a terminal state after its final state and results were stored. Correlate payload.run_id with the run_id returned by simulation.submit; the event does not carry the control request’s request_id.

Payload field Type Meaning
run_id non-empty string Simulation run identifier
state string Exactly completed, failed, or cancelled
error string, optional Error details; omitted when absent, never null
{
  "protocol_version": 2,
  "event_type": "simulation_ended",
  "payload": {
    "run_id": "f6e72cb3-9bcf-4669-b368-a17c656bad79",
    "state": "completed"
  }
}

A failed run uses state: "failed" and the server includes its error string. The schema permits an omitted error. Cancellation includes queued cancellations and runs cancelled during orderly shutdown. The intermediate cancelling state does not emit this event. Repeated configurations create separate runs and completion events with distinct run identifiers.

Subscription Example

Start this subscriber before submitting work from another client:

import json
import zmq

from snakelab.zmq.ZMQHelper import (
    EVENT_SIMULATION_ENDED,
    TOPIC_SIMULATION_ENDED,
    parse_event,
)

with zmq.Context() as context:
    with context.socket(zmq.SUB) as socket:
        socket.setsockopt(zmq.SUBSCRIBE, TOPIC_SIMULATION_ENDED.encode("utf-8"))
        socket.connect("tcp://wintermute:41972")
        while True:
            topic, body = socket.recv_multipart()
            if topic != TOPIC_SIMULATION_ENDED.encode("utf-8"):
                continue
            event = parse_event(json.loads(body))
            if event["event_type"] == EVENT_SIMULATION_ENDED:
                result = event["payload"]
                print(result["run_id"], result["state"], result.get("error"))

Delivery and Publishing

Subscriptions take time to propagate; connect() returning does not establish readiness. This is a live, best-effort stream with no acknowledgement or replay. Disconnected, late, or slow subscribers may miss events. Use the control simulation.status method to reconcile missed notifications for runs known to the current server process. There is no ordering guarantee across the separate control, event, and telemetry sockets.

Server code calls EventsPublisher.publish_event(event_type, payload). The call snapshots the trusted internal payload, then queues it for asynchronous delivery. Returning means local enqueueing, not receipt by a subscriber. Events are processed individually in queue order. Orderly shutdown drains pending events before closing the publisher when the transport remains operational; this does not guarantee delivery.

Migration from Event Version 1

Event version 2 replaces the former envelope’s top-level run_id with event_type and moves run_id into payload. Update subscribers to check version 2, dispatch on event_type, and read payload.run_id. The subscription topic and default port are unchanged. There is no dual publication of version 1. Control and telemetry remain at version 1.

Server integrations should replace offer_simulation_ended(run_id, state, error) with publish_event(EVENT_SIMULATION_ENDED, payload), including run_id and state and adding error only when it is a string.