// The Clawmates Event Taxonomy — the stable contract between the live feed (the // cm-api /api/world/live SSE) and the visualizations (World + Observe). Mirrors // realtime/events.schema.json. The runner emits many internal events; the server // normalizes them into this small, typed set; the browser only ever sees these. export type AgentStatus = "online" | "working" | "idle" | "offline"; export interface TaxonomyEvents { /** Status dots everywhere; the list rail. */ "agent.status": { agentId: string; status: AgentStatus; role?: string }; /** Observe → "WORKING ON NOW" card. */ "agent.task.update": { agentId: string; taskId: string; title: string; elapsedMs?: number; steps?: { label: string; state: "done" | "active" | "pending" }[]; }; /** Observe → REASONING STREAM (append). */ "agent.reasoning.delta": { agentId: string; text: string; channel?: "think" | "say" | "tool" }; /** World → pawn scales with brain-file size (log curve). Sent by the * server periodically for each agent whose `.brain` file has changed. * Fresh agents start small ("size of their letters"); loaded agents * visibly bloom out. */ "agent.memory": { agentId: string; bytes?: number; count?: number }; /** Observe → the live computer-screen tile. */ "agent.computer.frame": { agentId: string; app: "browser" | "terminal" | "slack" | "claw-chat"; url?: string; lines?: { text: string; kind?: "plain" | "add" | "del" | "ok" | "warn" | "cursor" }[]; }; /** Reasoning-stream "tool" lines. */ "agent.tool.call": { agentId: string; tool: string; target?: string; doorRequired?: boolean }; /** Door-approval toast + "doors awaiting approval" badge. */ "door.request": { doorId: string; agentId: string; action: string; target?: string; summary?: string }; /** Dismisses the toast; logs to history. */ "door.resolve": { doorId: string; decision: "approve" | "deny"; by?: string }; /** Observe System → INTER-AGENT COMMS; World → message-in-flight on a connector. */ "agent.message": { fromAgentId: string; toAgentId: string; text: string; ts?: string }; /** Observe → INTER-AGENT COMMS; a message posted to an N-way group room. */ "room.message": { fromAgentId: string; threadId: string; subject?: string; text: string; participantIds: string[]; ts?: string; }; /** Observe → an external A2A caller invoked this agent (a new ingress). */ "a2a.invoked": { agentId: string; caller?: string; skill?: string; ts?: string }; /** Observe → a claw delegated a sub-task to another claw (A→B handoff). */ "agent.delegate": { fromAgentId: string; toAgentId: string; toName?: string; task?: string; ts?: string }; /** World → Live (Gource): the agent pawn retargets to nodeId and beams. */ "world.touch": { agentId: string; nodeId: string; kind?: "service" | "event"; weight?: number }; /** World → node glow/pulse intensity. */ "node.activity": { nodeId: string; label?: string; kind?: "service" | "event"; heat?: number }; /** World → re-layout (add/remove org units, agents, projects). */ "topology.update": { formation: "hierarchy" | "flat" | "live"; nodes?: { id: string; tier: NodeTier; label?: string; parentId?: string }[]; edges?: { from: string; to: string; kind?: "parent" | "peer" | "flow" }[]; }; /** Top-bar pills + Observe System telemetry strip. With `agentId` it's the * per-agent slice for the command-center metric band; without, workspace-wide. */ telemetry: { agentId?: string; tokensPerMin?: number; costPerHr?: number; loops?: number; doorsPending?: number }; /** Observe System → ROUTINES & LOOPS; the scheduler view. */ "routine.update": { routineId: string; name: string; kind: "cron" | "loop"; owner?: string; schedule?: string; progress?: number; state?: "running" | "scheduled" | "done" | "failed" | "door"; }; } export type NodeTier = "org" | "company" | "team" | "agent" | "service" | "event"; export type TaxonomyType = keyof TaxonomyEvents; export type TaxonomyPayload = TaxonomyEvents[T]; /** Every taxonomy type, in one array (drives the EventSource listeners). */ export const TAXONOMY_TYPES: TaxonomyType[] = [ "agent.status", "agent.task.update", "agent.reasoning.delta", "agent.memory", "agent.computer.frame", "agent.tool.call", "door.request", "door.resolve", "agent.message", "room.message", "a2a.invoked", "agent.delegate", "world.touch", "node.activity", "topology.update", "telemetry", "routine.update", ]; /** Stateful types whose last value is replayed to late subscribers (mirrors the * runner's resume-at-checkpoint), vs. ephemeral streaming events (deltas, * touches, messages) which are not replayed. */ export const STATEFUL_TYPES = new Set([ "agent.status", "agent.task.update", "agent.memory", "node.activity", "telemetry", "topology.update", "routine.update", ]); /** The replay key per stateful event (one retained value per agent/node/routine). */ export function stateKey(type: T, d: TaxonomyPayload): string { if (type === "agent.status" || type === "agent.task.update" || type === "agent.memory") return `${type}:${(d as TaxonomyEvents["agent.status"]).agentId}`; if (type === "node.activity") return `${type}:${(d as TaxonomyEvents["node.activity"]).nodeId}`; if (type === "routine.update") return `${type}:${(d as TaxonomyEvents["routine.update"]).routineId}`; if (type === "telemetry") { const a = (d as TaxonomyEvents["telemetry"]).agentId; return a ? `telemetry:${a}` : "telemetry"; // per-agent slice vs workspace-wide } return type; // topology.update }