forked from molecule-ai/molecule-core
Forked clean from public hackathon repo (Starfire-AgentTeam, BSL 1.1) with full rebrand to Molecule AI under github.com/Molecule-AI/molecule-monorepo. Brand: Starfire → Molecule AI. Slug: starfire / agent-molecule → molecule. Env vars: STARFIRE_* → MOLECULE_*. Go module: github.com/agent-molecule/platform → github.com/Molecule-AI/molecule-monorepo/platform. Python packages: starfire_plugin → molecule_plugin, starfire_agent → molecule_agent. DB: agentmolecule → molecule. History truncated; see public repo for prior commits and contributor attribution. Verified green: go test -race ./... (platform), pytest (workspace-template 1129 + sdk 132), vitest (canvas 352), build (mcp). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
336 lines
7.4 KiB
Markdown
336 lines
7.4 KiB
Markdown
# 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
|