AgentDock streams visibility events for coding-agent runs. Postgres remains authoritative; Redis Pub/Sub + WebSockets are for responsiveness only.
Worker (state transitions + sandbox exec)
→ AgentEventPublisher
→ Redis Pub/Sub channel agentdock:run:{run_id}
→ FastAPI WebSocket /ws/agent-runs/{run_id}
→ Authenticated browser
REST remains the source of truth for run status, steps, diffs, and cancellation.
Choice: Redis Pub/Sub.
Why: Phase 6 reconnect recovers from REST (agent_runs + agent_steps). Replayable Streams would add complexity without changing correctness. Sequence numbers still come from Redis INCR (agentdock:runseq:{run_id}) so clients can order/dedupe live events.
GET /ws/agent-runs/{run_id}
Same origin/port as the API (Compose: localhost:8000). Override with NEXT_PUBLIC_WS_URL if the browser cannot derive ws:// / wss:// from NEXT_PUBLIC_API_URL.
- Resolve HttpOnly cookie
agentdock_session - Resolve current user
- Load
agent_run - Require
agent_run.user_id == current_user.id
Reject before accepting the socket when possible.
| Condition | Close code | Reason |
|---|---|---|
| Unauthenticated | 4401 | Not authenticated |
| Wrong owner | 4403 | Forbidden |
| Missing run | 4404 | Run not found |
| Connection limit | 4429 | Connection limit |
| Server error | 1011 | Server error |
Limits (defaults): WS_MAX_CONNECTIONS_PER_USER=10, WS_MAX_CONNECTIONS_PER_RUN=5.
WebSocket messages from the browser cannot invoke tools. Cancel remains POST /api/agent-runs/{id}/cancel.
{
"version": 1,
"event": "agent.tool.completed",
"run_id": "…",
"sequence": 17,
"timestamp": "2026-08-10T12:00:00+00:00",
"data": {}
}Clients must ignore unsupported version values without crashing.
| Event | Notes |
|---|---|
agent.snapshot |
Sent once on connect (local; not fan-out). Compact status + changed_files. |
agent.run.queued / started / status |
Lifecycle |
agent.step.started / completed |
Loop progress |
agent.tool.started / completed |
Safe operational summaries only |
agent.command.output |
Ephemeral stdout/stderr chunks |
agent.validation.started / completed |
Validation |
agent.files.changed |
Filename list |
agent.diff.ready |
Notification — fetch diff via REST |
agent.approval.required / agent.approved / agent.rejected |
Human approval boundary |
publication.started / validation.* / commit.created / branch.pushed / pr.created / failed |
Trusted publication progress |
agent.run.completed / failed / cancelled / timed_out / step_limit_reached |
Terminal |
agent.ping / agent.pong |
Heartbeat (~25s) |
No chain-of-thought. No secrets. No full file contents in tool events.
| Durable (Postgres) | Ephemeral (stream only) |
|---|---|
| Run status, steps, validation, changed files, diff metadata, errors | Partial command stdout/stderr chunks |
Ephemeral chunks may be dropped under backpressure; final bounded tool summaries remain in agent_steps.
Monotonic sequence per run via Redis INCR. Safe for single-run orchestration. Do not rely only on WebSocket arrival order.
Frontend:
- Connect WebSocket
- Apply events (dedupe by sequence)
- On disconnect: exponential backoff (0.5s → 8s)
- Refetch run/steps/diff over REST
- Resume live updates
Correctness does not require continuous connectivity.
- Worker never waits on browsers (Redis decouples)
- Per-socket outbound queue capped (256)
- Ephemeral
agent.command.outputdropped first when full - Durable events may displace oldest queue items
Sandbox exec demuxes Docker streams and calls on_chunk(stream, text, truncated) incrementally. Limits from Phase 4 (sandbox_max_output_bytes) still apply. Chunks are UTF-8 with replacement and ~512-char slices for the wire.
/agent live workspace:
- Status, activity timeline, command output panel, changed files, diff (REST on
agent.diff.ready), Cancel (REST) - Auto-scroll logs only while near bottom
- Plain-text rendering (no
dangerouslySetInnerHTML) — repository output is untrusted - Polling fallback when WebSocket is down
Proxies must forward:
Upgrade: websocketConnection: upgrade- Idle/read timeouts long enough for heartbeats (~25s) plus agent runtime
- Prefer
wss://behind TLS terminators
Without correct Upgrade handling, clients fall back to REST polling.
| Failure | Behavior |
|---|---|
| Event publish fails | Agent continues; run state still persisted; UI may miss live events |
| Queue unavailable | Existing Phase 4/5 behavior (cannot claim new work) |
Distinguish queue failure from event-publish failure.
Metrics: websocket_active_connections, agent_events_published_total, agent_event_publish_failures_total, websocket_disconnects_total.
Logs: websocket.connected, websocket.disconnected, websocket.unauthorized, agent.event.published, agent.event.publish_failed.
Phase 7 approval and publication state is recoverable from the REST run endpoint after a browser disconnect. The publication fields are persisted by migration 0005; Postgres remains authoritative.
Bump version for breaking changes. Keep v1 readers ignoring unknown event names when safe.