DockSight Agent Protocol¶
This document is the source of truth for every WebSocket message exchanged between the DockSight Server and DockSight Agents.
All future agent features (logs, metrics, container actions, events, etc.) MUST use the same envelope and conventions defined here. Implementations in apps/server and agent/ should follow this spec; when they diverge, update this document in the same change.
| Item | Value |
|---|---|
| Transport | Native WebSocket (JSON text frames) |
| Endpoint | ws://<host>:<port>/agents |
| Encoding | UTF-8 JSON |
| Spec status | v0.1 (PI-007 baseline + reserved extensions) |
1. Design goals¶
- One envelope for everything — every message uses the same wrapper.
- Namespaced types —
domain.action(e.g.agent.register,container.start). - Explicit direction — each type is Agent→Server, Server→Agent, or bidirectional.
- Forward compatible — unknown fields in
payloadSHOULD be ignored by receivers. - Stable identity — agents are identified by a durable
uuid(persisted on disk).
2. Message envelope¶
Every message MUST be a JSON object with exactly these top-level keys (additional top-level keys are reserved and SHOULD NOT be used yet):
| Field | Type | Required | Description |
|---|---|---|---|
type |
string |
yes | Message type using domain.action naming |
payload |
object |
yes | Type-specific body; use {} if empty |
Rules¶
typeis case-sensitive and lowercase.payloadMUST be a JSON object (not an array, string, or null).- Receivers MUST ignore unknown
payloadproperties. - Receivers MUST ignore (and MAY log) unknown
typevalues without closing the connection, unless documented otherwise. - Binary WebSocket frames are not used in v0.1.
Optional envelope extensions (reserved)¶
These fields are reserved for a future protocol revision. Do not send them until this document marks them as active:
| Field | Type | Purpose |
|---|---|---|
id |
string |
Correlation / request id |
replyTo |
string |
Response correlation |
ts |
string (ISO-8601) |
Sender timestamp |
v |
number |
Protocol version |
3. Naming conventions¶
Type format¶
Examples: agent.register, agent.heartbeat, container.start, logs.stream.
Domains¶
| Domain | Owner | Purpose |
|---|---|---|
agent |
Agent + Server | Identity, session, liveness |
container |
Server → Agent (commands), Agent → Server (results/events) | Container lifecycle actions |
logs |
Agent → Server (stream), Server → Agent (subscribe/control) | Container / agent logs |
metrics |
Agent → Server | Resource and container metrics |
event |
Agent → Server | Host / Docker lifecycle events |
error |
Either | Protocol or command errors |
system |
Either | Reserved for protocol control (ping beyond heartbeat, etc.) |
Action verbs¶
Prefer these verbs for consistency:
| Verb | Meaning |
|---|---|
register / registered |
Request / success acknowledgement |
heartbeat |
Liveness signal |
list / listed |
Query / result |
get / got |
Fetch one / result |
start / stop / restart / remove |
Commands |
subscribe / unsubscribe |
Stream control |
chunk / frame |
Streamed data |
result |
Command outcome |
error |
Failure |
Request/response pairs SHOULD use related names: container.start → container.result (include action + requestId in the result payload).
4. Connection lifecycle¶
Agent Server
| |
|----------- WebSocket open ---------->|
| |
|------ agent.register --------------->|
|<----- agent.registered --------------|
| |
|------ agent.heartbeat -------------->| (every 30s)
| ... |
|----------- disconnect / reconnect ---|
Rules¶
- After the WebSocket opens, the agent MUST send
agent.registerbefore other domain messages. - The server SHOULD reject or ignore non-registration messages until registration succeeds (v0.1: only register + heartbeat are implemented).
- Heartbeats MUST be sent every 30 seconds while connected.
- On disconnect, the agent MUST reconnect with backoff (current client default: 5s) and re-register.
- On disconnect, the server MAY mark the agent
OFFLINE.
5. Implemented messages (v0.1)¶
5.1 agent.register¶
- Direction: Agent → Server
- When: Immediately after connect (and after each reconnect)
- Purpose: Create or update the agent record by
uuid
{
"type": "agent.register",
"payload": {
"uuid": "bc2bc07d-e456-4979-9f11-3293965d7436",
"hostname": "DESKTOP-9JDKFHI",
"os": "windows",
"architecture": "amd64",
"version": "0.1.0"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
uuid |
string |
yes | Durable agent identity (UUID) |
hostname |
string |
yes | Host name |
os |
string |
yes | OS identifier (e.g. linux, windows, darwin) |
architecture |
string |
yes | CPU arch (e.g. amd64, arm64) |
version |
string |
yes | Agent software version |
Server behavior: Upsert by uuid, set status=ONLINE, update lastSeen.
5.2 agent.registered¶
- Direction: Server → Agent
- When: Response to
agent.register - Purpose: Confirm registration
{
"type": "agent.registered",
"payload": {
"id": "cmry2k5gk0000esv7rx4mhkwb",
"uuid": "bc2bc07d-e456-4979-9f11-3293965d7436",
"status": "ONLINE",
"message": "Registration successful"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
yes* | Server-side agent id (cuid); empty on failure |
uuid |
string |
yes | Echo of agent uuid |
status |
string |
yes | e.g. ONLINE, UNKNOWN |
message |
string |
yes | Human-readable result |
* On validation failure, id may be an empty string and message describes the error.
5.3 agent.heartbeat¶
- Direction: Agent → Server
- When: Every 30 seconds after successful registration
- Purpose: Keep
lastSeenfresh and statusONLINE
| Field | Type | Required | Description |
|---|---|---|---|
uuid |
string |
yes | Agent identity |
Server behavior: If the agent exists, update lastSeen and status=ONLINE. Unknown uuids MAY be logged and ignored.
Note: v0.1 does not define agent.heartbeat.ack. Silence from the server is normal; connection health is observed via the WebSocket itself.
5.4 container.list¶
- Direction: Server → Agent
- When: After successful registration (and whenever the server requests discovery)
- Purpose: Ask the agent to query Docker Engine for containers (read-only)
5.5 container.listed¶
- Direction: Agent → Server
- When: Response to
container.list - Purpose: Return discovered containers (cached in-memory for the dashboard)
{
"type": "container.listed",
"payload": {
"containers": [
{
"id": "abc123...",
"name": "docksight-postgres",
"image": "postgres:17-alpine",
"status": "Up 2 hours (healthy)",
"state": "running"
}
]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
containers |
array |
yes | Container summaries |
containers[].id |
string |
yes | Docker container id |
containers[].name |
string |
yes | Container name |
containers[].image |
string |
yes | Image reference |
containers[].status |
string |
yes | Human status string from Docker |
containers[].state |
string |
yes | State (e.g. running, exited) |
5.6 container.inspect¶
- Direction: Server to Agent
- When: API caller requests details for one container
- Purpose: Ask the agent to query Docker Engine inspect data for a container
- Correlation: Every command includes
requestId; the agent must echo it oncontainer.inspected
{
"type": "container.inspect",
"payload": {
"requestId": "3f1c9a2e-8b44-4d1a-9c2e-1a2b3c4d5e6f",
"containerId": "abc123def456..."
}
}
| Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
yes | Correlation id generated by the server |
containerId |
string |
yes | Docker container id (full or short) |
5.7 container.inspected¶
- Direction: Agent to Server
- When: Response to
container.inspect - Purpose: Return Docker inspect data for a correlated request
{
"type": "container.inspected",
"payload": {
"requestId": "3f1c9a2e-8b44-4d1a-9c2e-1a2b3c4d5e6f",
"container": {
"id": "abc123def456...",
"shortId": "abc123def456",
"name": "/web",
"image": "nginx:latest",
"state": {
"status": "running",
"running": true,
"paused": false,
"restarting": false
},
"created": "2026-07-25T10:00:00Z",
"startedAt": "2026-07-25T10:01:00Z",
"ports": [],
"mounts": [],
"networks": [],
"workingDir": "",
"cmd": ["nginx", "-g", "daemon off;"],
"restartPolicy": "unless-stopped",
"entrypoint": ["/docker-entrypoint.sh"]
},
"ok": true,
"error": null
}
}
| Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
yes | Same id as container.inspect |
container |
object \| null |
yes | Inspect data, or null on failure |
ok |
boolean |
yes | Whether Docker inspect succeeded |
error |
string \| null |
yes | Error detail when ok is false |
5.8 container.start / container.stop / container.restart¶
- Direction: Server → Agent
- When: API caller requests a lifecycle action
- Purpose: Ask the agent to start, stop, or restart a container
- Correlation: Every command includes
requestId; the agent must echo it oncontainer.result
{
"type": "container.start",
"payload": {
"requestId": "3f1c9a2e-8b44-4d1a-9c2e-1a2b3c4d5e6f",
"containerId": "abc123def456..."
}
}
Same payload shape for container.stop and container.restart.
| Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
yes | Correlation id generated by the server |
containerId |
string |
yes | Docker container id (full or short) |
5.9 container.result¶
- Direction: Agent → Server
- When: After executing start/stop/restart
- Purpose: Report success or failure for a correlated command
{
"type": "container.result",
"payload": {
"requestId": "3f1c9a2e-8b44-4d1a-9c2e-1a2b3c4d5e6f",
"action": "start",
"containerId": "abc123def456...",
"ok": true,
"message": "start succeeded",
"error": null
}
}
| Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
yes | Same id as the command |
action |
string |
yes | start | stop | restart |
containerId |
string |
yes | Target container id |
ok |
boolean |
yes | Whether Docker accepted the operation |
message |
string |
yes | Short human summary |
error |
string \| null |
yes | Error detail when ok is false |
5.10 logs.subscribe¶
- Direction: Server → Agent
- When: A dashboard client opens a container log stream
- Purpose: Stream historical + live container logs (not persisted)
{
"type": "logs.subscribe",
"payload": {
"requestId": "req-123",
"containerId": "abc123",
"tail": 100,
"follow": true
}
}
| Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
yes | Correlation id for chunks / unsubscribe |
containerId |
string |
yes | Docker container id |
tail |
number |
no | Historical lines to send first (default 100) |
follow |
boolean |
no | Continue live streaming (default true) |
5.11 logs.chunk¶
- Direction: Agent → Server
- When: While a subscribe stream is active
- Purpose: Deliver batched log entries
{
"type": "logs.chunk",
"payload": {
"requestId": "req-123",
"containerId": "abc123",
"entries": [
{
"timestamp": "2026-07-25T10:00:00Z",
"stream": "stdout",
"message": "Application started"
}
]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
yes | Same id as logs.subscribe |
containerId |
string |
yes | Container id |
entries |
array |
yes | Batched lines (up to ~50 or ~200ms) |
entries[].timestamp |
string |
yes | RFC3339 / RFC3339Nano |
entries[].stream |
string |
yes | stdout or stderr |
entries[].message |
string |
yes | Log line body |
5.12 logs.unsubscribe¶
- Direction: Server → Agent
- When: Client closes the log view / SSE disconnects
- Purpose: Stop only that stream and release Docker reader resources
6. Reserved message types (not implemented yet)¶
These types are reserved so future work does not invent incompatible shapes. Payloads below are draft contracts and may gain fields, but type names SHOULD stay stable.
6.1 Errors — error.report¶
- Direction: Either
- Purpose: Structured error without closing the socket
{
"type": "error.report",
"payload": {
"code": "VALIDATION_ERROR",
"message": "Missing uuid",
"retriable": false,
"relatedType": "agent.register",
"details": {}
}
}
6.2 Containers (extensions — not in this increment)¶
Beyond list/inspect/lifecycle (list, listed, inspect, inspected, start, stop, restart, result), these remain reserved:
| Type | Direction | Purpose |
|---|---|---|
container.remove |
Server → Agent | Remove container |
6.3 Metrics¶
| Type | Direction | Purpose |
|---|---|---|
metrics.subscribe |
Server → Agent | Enable periodic metrics |
metrics.unsubscribe |
Server → Agent | Disable metrics |
metrics.sample |
Agent → Server | Point-in-time sample |
Draft sample:
{
"type": "metrics.sample",
"payload": {
"uuid": "bc2bc07d-e456-4979-9f11-3293965d7436",
"collectedAt": "2026-07-23T22:00:00Z",
"host": {
"cpuPercent": 12.5,
"memoryUsedBytes": 8589934592,
"memoryTotalBytes": 17179869184
},
"containers": [
{
"containerId": "abc123def456",
"cpuPercent": 1.2,
"memoryUsedBytes": 67108864
}
]
}
}
6.4 Events¶
| Type | Direction | Purpose |
|---|---|---|
event.docker |
Agent → Server | Docker / host event |
Draft:
{
"type": "event.docker",
"payload": {
"uuid": "bc2bc07d-e456-4979-9f11-3293965d7436",
"time": "2026-07-23T22:00:00Z",
"action": "start",
"containerId": "abc123def456",
"attributes": {
"image": "nginx:latest",
"name": "/web"
}
}
}
7. Status values¶
Agent status strings used in registration / DB:
| Value | Meaning |
|---|---|
ONLINE |
Connected and recently seen |
OFFLINE |
Disconnected |
UNKNOWN |
Unspecified / error path |
New status values MUST be documented here before use.
8. Versioning policy¶
- This document carries the protocol revision (currently v0.1).
- Additive changes (new optional payload fields, new message types) do not require a major bump.
- Breaking changes (renamed/removed fields, changed semantics of existing types) require a major bump and a migration note in this file.
- When envelope field
vis activated, agents and server negotiate compatibility using that field.
9. Implementation map¶
| Side | Location |
|---|---|
| Shared TS contracts | packages/protocol (@docksight/protocol) |
| Human protocol spec | docs/protocol.md (this file) |
| Server message types | Prefer @docksight/protocol (legacy: apps/server/src/agents/messages.ts) |
| Server gateway | apps/server/src/agents/agents.gateway.ts |
| Agent client | agent/internal/communication/client.go |
| Agent identity | agent/internal/identity/ |
When adding a message type:
- Update this document and
packages/protocolin the same change. - Add types/handlers on server and agent.
- Keep envelope
{ type, payload }unchanged.
10. Non-goals (current)¶
- Authentication / authorization of agent connections
- TLS / message encryption
- Multiplexing multiple logical streams on one socket beyond message
type - Binary payloads
These may be added later under the reserved envelope extensions and system.* / error.* domains without replacing the core envelope.