diff --git a/content/docs/api-protocol/websocket-events.md b/content/docs/api-protocol/websocket-events.md new file mode 100644 index 0000000..c11d89b --- /dev/null +++ b/content/docs/api-protocol/websocket-events.md @@ -0,0 +1,335 @@ +# WebSocket Events + +The canvas subscribes to the platform's WebSocket at `/ws` and receives real-time structure events as JSON messages. + +## Message Format + +Every WebSocket message has this structure: + +```json +{ + "event": "EVENT_TYPE", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { ... } +} +``` + +## Event Reference + +### WORKSPACE_PROVISIONING + +Workspace is being spun up. Canvas shows a spinner on the node. + +```json +{ + "event": "WORKSPACE_PROVISIONING", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { + "name": "Vancouver SEO Agent", + "tier": 1, + "config": "seo-agent" + } +} +``` + +### WORKSPACE_ONLINE + +First heartbeat received, or workspace returned from offline. + +```json +{ + "event": "WORKSPACE_ONLINE", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { + "url": "http://ws-abc-123:8000", + "agent_card": { + "name": "Vancouver SEO Agent", + "version": "1.0.0", + "skills": ["generate-seo-page", "audit-seo-page"], + "capabilities": { "streaming": true } + } + } +} +``` + +### WORKSPACE_OFFLINE + +Heartbeat TTL expired. Node turns gray. + +```json +{ + "event": "WORKSPACE_OFFLINE", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:01:00Z", + "payload": {} +} +``` + +### WORKSPACE_PROVISION_FAILED + +Provisioning timed out or errored. Node turns red with retry button. + +```json +{ + "event": "WORKSPACE_PROVISION_FAILED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:03:00Z", + "payload": { + "reason": "provisioning timeout -- no heartbeat received" + } +} +``` + +### WORKSPACE_DEGRADED + +Workspace is online but experiencing errors. Node shows warning indicator. + +```json +{ + "event": "WORKSPACE_DEGRADED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:05:00Z", + "payload": { + "error_rate": 0.87, + "sample_error": "anthropic API rate limit exceeded" + } +} +``` + +### WORKSPACE_REMOVED + +User deleted the workspace. Node removed from canvas. + +```json +{ + "event": "WORKSPACE_REMOVED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:10:00Z", + "payload": { + "forwarded_to": null + } +} +``` + +### AGENT_REPLACED + +AI model swapped inside a workspace. + +```json +{ + "event": "AGENT_REPLACED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { + "old_model": "anthropic:claude-sonnet-4-6", + "new_model": "openai:gpt-4o" + } +} +``` + +### AGENT_CARD_UPDATED + +Workspace republished its Agent Card (new skill added, description changed, capabilities changed). The platform broadcasts this to all peer workspaces (siblings, children, parent) so they can rebuild their system prompts. + +```json +{ + "event": "AGENT_CARD_UPDATED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { + "agent_card": { + "name": "Vancouver SEO Agent", + "version": "1.1.0", + "skills": ["generate-seo-page", "audit-seo-page", "monitor-rankings"], + "capabilities": { "streaming": true } + } + } +} +``` + +### WORKSPACE_EXPANDED + +Workspace expanded into a team of sub-workspaces. + +```json +{ + "event": "WORKSPACE_EXPANDED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { + "sub_workspace_ids": [ + "ws-frontend-001", + "ws-backend-001", + "ws-qa-001" + ] + } +} +``` + +### WORKSPACE_COLLAPSED + +Team collapsed back to a single agent. Sub-workspaces are stopped and removed. + +```json +{ + "event": "WORKSPACE_COLLAPSED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { + "removed_sub_workspace_ids": [ + "ws-frontend-001", + "ws-backend-001", + "ws-qa-001" + ] + } +} +``` + +### WORKSPACE_MOVED + +Workspace moved to a new parent (dragged into a different team on canvas). + +```json +{ + "event": "WORKSPACE_MOVED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { + "old_parent_id": "ws-team-a", + "new_parent_id": "ws-team-b" + } +} +``` + +### AGENT_ASSIGNED + +A new AI agent assigned to a workspace (first time or after removal). + +```json +{ + "event": "AGENT_ASSIGNED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { + "agent_id": "agent-xyz-789", + "model": "anthropic:claude-sonnet-4-6" + } +} +``` + +### AGENT_REMOVED + +Agent removed from a workspace (workspace becomes empty). + +```json +{ + "event": "AGENT_REMOVED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { + "agent_id": "agent-xyz-789", + "reason": "user removed" + } +} +``` + +### AGENT_MOVED + +Agent moved from one workspace to another. + +```json +{ + "event": "AGENT_MOVED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { + "agent_id": "agent-xyz-789", + "from_workspace_id": "ws-abc-123", + "to_workspace_id": "ws-def-456" + } +} +``` + +### TASK_UPDATED + +Agent's current task changed (via heartbeat). WebSocket-only — not persisted to `structure_events`. + +```json +{ + "event": "TASK_UPDATED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { + "current_task": "Analyzing quarterly report", + "active_tasks": 2 + } +} +``` + +Canvas shows the current task as an amber banner on the workspace node and side panel header. Only broadcast when the task actually changes (not on every heartbeat). + +### ACTIVITY_LOGGED + +New activity log entry created (A2A communication, webhook-triggered task ingress, agent log, error). WebSocket-only — not persisted to `structure_events` (stored in `activity_logs` table instead). + +```json +{ + "event": "ACTIVITY_LOGGED", + "workspace_id": "ws-abc-123", + "timestamp": "2026-03-30T12:00:00Z", + "payload": { + "activity_type": "a2a_receive", + "method": "message/send", + "summary": "message/send → ws-abc-123", + "status": "ok", + "source_id": "ws-def-456", + "target_id": "ws-abc-123", + "duration_ms": 1500 + } +} +``` + +Canvas ActivityTab uses this event as a refresh hint. The event is informational — the full activity details (request/response bodies) are fetched via `GET /workspaces/:id/activity`. + +## Subscribers + +Both canvas clients and workspace agents subscribe to the same WebSocket endpoint (`/ws`): + +| Subscriber | Identifies via | Receives | Purpose | +|------------|---------------|----------|---------| +| Canvas client | No header (unrestricted) | All events | UI updates | +| Workspace agent | `X-Workspace-ID` header | Filtered — only events about reachable peers | System prompt rebuilds | + +The platform filters server-side using `CanCommunicate()` — each workspace only receives events about workspaces it can talk to. + +## Event Flow + +``` +Structure change occurs + | + v +Platform writes event to structure_events (Postgres) + | + v +Platform publishes to Redis pub/sub (events:broadcast) + | + v +WebSocket handler receives from Redis + | + v +WebSocket pushes JSON to subscribers (filtered per workspace) + | + +-> Canvas clients: update Zustand state -> React Flow re-renders + +-> Workspace agents: rebuild system prompt if peer changed +``` + +## Related Docs + +- [Canvas UI](../frontend/canvas.md) — How events drive the UI +- [Event Log](../architecture/event-log.md) — Persistent event storage +- [Registry & Heartbeat](./registry-and-heartbeat.md) — Events from registration +- [Provisioner](../architecture/provisioner.md) — Events from provisioning +- [Communication Rules](./communication-rules.md) — Hierarchy-based peer broadcasting