> For the complete documentation index, see [llms.txt](https://fleet.hackmap.win/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://fleet.hackmap.win/reference/ws-protocol.md).

# WebSocket protocol

Workers connect to `/api/v1/automations/{type}/workers/{id}/control`. Auth is the worker token, sent as `Authorization: Bearer <token>` header or `?token=<token>` query param.

The protocol is JSON frames, both directions. Every frame is a JSON object with a `type` field that discriminates the schema. Decode is strict (unknown fields rejected) so a malformed frame from one side is loud, not silent.

```python
PROTOCOL_VERSION = 1
```

Workers send the protocol version on register. Master closes the connection with code 4000 if it doesn't match.

## Worker → master

### `register`

Sent once, immediately after connect.

```json
{
  "type": "register",
  "worker_id": "host1",
  "automation_type": "url-pinger",
  "protocol_version": 1,
  "hardware": {
    "cores": 4,
    "ram_gb": 8,
    "hostname": "host1",
    "kernel": "6.8.0-..."
  }
}
```

The master upserts the worker record, adds it to `wkrs:<type>` and `wkrs:all`, and starts accepting other frames.

### `state`

Sent periodically by the stats reporter (every \~15 seconds) and on state transitions.

```json
{
  "type": "state",
  "state": "RUNNING",
  "config_gen": 12,
  "ts": 1778690712,
  "last_error": null
}
```

`state` is one of `IDLE`, `RUNNING`, `ERROR`. The master writes it to `wkr:<type>:<id>:state` hash.

### `stats`

Sent every \~15 seconds.

```json
{
  "type": "stats",
  "slots": 4,
  "cpu_pct": 15.2,
  "rss_mb": 320,
  "ts": 1778690712,
  "metrics": {
    "counter.pings_ok": 142,
    "counter.pings_failed": 3
  }
}
```

`metrics` is plugin-defined — whatever the slots have incremented through `ctx.metrics`. Counters are summed across slots before sending; gauges keep the last value; histograms report p50/p95/p99.

### `output`

Sent whenever a slot calls `ctx.emit(payload)`.

```json
{
  "type": "output",
  "slot_id": 0,
  "ts": 1778690712,
  "payload": {"url": "https://...", "status": 200}
}
```

The master pushes onto `stream:<type>` (a Redis ZSET) and publishes `event:<type>.emit` on pubsub.

### `ping`

Sent occasionally as a liveness check. Master replies with `ping` carrying the same `id`.

```json
{"type": "ping", "id": "abc123"}
```

## Master → worker

### `config_changed`

Sent immediately after a `PATCH /config` call from an admin. The worker reacts by re-polling the HTTP config endpoint.

```json
{"type": "config_changed", "config_gen": 12}
```

The frame is a notification, not the config itself. The actual config is always fetched via HTTP. This separation means a flaky WS doesn't lose configs — the 30-second poll catches anything missed.

### `task_assign` (reserved, not yet emitted)

Will be used in a future revision to push individual tasks to BatchAutomation workers without the BLPOP queue.

```json
{"type": "task_assign", "task_id": "...", "payload": {...}}
```

Currently unused. BatchAutomation slots BLPOP directly from Redis.

### `ping`

Echoed back from a worker-initiated ping. Same shape, same `id`.

## Reconnect behavior

The worker's WebSocket client auto-reconnects with exponential backoff (1s, 2s, 4s, ..., capped at 30s). On every reconnect it sends `register` again. The master is idempotent on register — same worker\_id just refreshes the hardware record.

Outbound frames are buffered while disconnected, up to 1024 frames. After that, the oldest get evicted to make room. Most frames are recoverable from local state (stats, state are time-snapshots) so buffer eviction isn't catastrophic. Output frames are the one exception — if the WS is down longer than the buffer can hold, you lose outputs.

## Closing

Code 4000: protocol version mismatch. Worker should not retry.

Code 4401: invalid worker token. Worker should fix the token and retry.

Other codes: standard WebSocket close reasons. Worker retries with backoff.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://fleet.hackmap.win/reference/ws-protocol.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
