traceId, ordered by seq), the graph-projection source (delegation / status / cost), and the metric + error-taxonomy source. This page is the reference for the two typed contracts that define it: the event kinds (the discriminated union in @clawboo/obs) and the runtime error classes (the Cursor-model classifier).
Both contracts live in @clawboo/obs, pure, browser-safe, zero runtime dependency on the OTel SDK. Events are persisted insert-only with secrets scrubbed; one trace per board task, spans per run / tool.
At a glance
The kinds are an append-only enum; kinds are added (e.g.
session_rotated, the routine_* family, the team_chat_post family), never renamed or removed, so old traces keep parsing. Each addition lands in @clawboo/obs before any emit site.The correlation envelope
Every event, regardless of kind, validates againstorchestrationEventSchema. The runtime schema validates the envelope strictly but treats data as an open object (z.record), so an emit site can never drop an event on a minor data-shape drift. Observability captures best-effort; the kind → data shapes below are TypeScript interfaces (a discriminated union) that keep producers and projection reducers typed without risking event loss at the wire.
parseOrchestrationEvent(value) validates and defaults data before persistence.
Event kinds
The 23 members ofORCHESTRATION_EVENT_KINDS, grouped by concern. Each data shape is the TypeScript interface from KindToData. Fields without ? are always present at the producer; ? fields are optional. data is open at the wire, so any consumer must tolerate missing fields.
Board lifecycle
task_created
A board task was created.
task_claimed
A task was atomically claimed by an assignee.
status_changed
A task transitioned state. to is always present; from may be null (first transition).
comment_added
A comment / system note landed on a task.
dep_linked
A dependency edge (blocks / blocked-by) was added.
Execution
execution_started
An execution process opened for a task run.
execution_completed
An execution closed with its outcome. The costUsd / token fields carry the run’s final total (authoritative for the run, see cost reconciliation).
tool_call
A runtime invoked a tool. toolCallId correlates with the matching tool_result.
tool_result
A tool returned. isError drives the tool-error-rate metric.
cost
An incremental cost/token tick during a run. These accumulate per run; see cost reconciliation.
Approvals
approval_requested
A tool / delegation approval was requested.
approval_resolved
An approval was resolved (allow / deny / expire).
Errors
error
A runtime / tool failure. The errorClass and harnessBug fields are filled at the emit site by running the failure through classifyError and isHarnessBug. A harnessBug: true event additionally fires a structured harness-bug alert.
errorClass is typed string (not the RuntimeErrorClass union) because the executor runner also emits PolicyDenied for a brokered-tool denial, a non-fatal denial path that is not a runtime failure. Every other value is one of the eight error classes.Spans
span_start
Opens a span in the trace tree.
span_end
Closes a span.
Session rotation
session_rotated
A run rotated to a fresh successor session (context exhaustion / max-turns). Continuity rides a short handoff note, not the transcript.
Routines (scheduler)
routine_fired
A scheduled_runs ledger row fired.
routine_dispatched
A fire materialized (or bound to) a board task and dispatched it. dispatchPath records the wake-bridge branch.
routine_completed
A fire’s dispatch reached a terminal outcome. nextRunAt is null when disarmed (a spent once@ or an errored recurring routine).
routine_error
A fire failed (the routine is parked / disarmed until a human resumes).
Peer chat
team_chat_post
A post landed in a team room. authorAgentId is resolved from the MCP connection binding, never from tool args (anti-spoof).
speaker_selected
The speaker-selection policy nominated the next agent to talk in a bounded exchange.
turn_bound_hit
A bounded peer-chat exchange ended (the chatter-forever guard).
Cost reconciliation
cost events are incremental and execution_completed carries the run’s final total. A runtime that reports cost only at completion (no mid-run cost events) would otherwise read $0 / 0 tokens in the metrics while the graph showed the real total. Both the metrics fold (summarizeMetrics) and the graph projection (projectGraph) reconcile per run (keyed by taskId): they take max(sum of cost events, execution_completed total), so the two code paths converge regardless of how a runtime reports cost, no double counting, and the completion total supplies the value when there were no cost events at all.
Error taxonomy
Every runtime / tool failure is classified: a failure is mapped to a baseline of expected classes; anything that doesn’t match isUnknown, and an Unknown is treated as a harness bug, surfaced as an alert (a flagged error event plus an error-level structured log) rather than silently swallowed. Expected classes get baselined per runtime so anomalies in their rate can be alerted on later; an Unknown alerts immediately.
Classes
RUNTIME_ERROR_CLASSES, the eight members of RuntimeErrorClass:
classifyError(code, message)
code and message into one haystack (`${code ?? ''} ${message ?? ''}`, trimmed). An empty haystack returns Unknown. Otherwise the rules are tried in order, first match wins (the order is RateLimited → UserAborted → Timeout → UnexpectedEnv → ContextOverflow → InvalidArgs → ProviderError), so the more specific / overloaded signals (rate-limit, abort) are checked before the broader provider / env buckets. ContextOverflow sits deliberately before InvalidArgs: an oversized-input 400 is a context overflow, not a generic bad request. No match returns Unknown.
isHarnessBug(cls)
true only for Unknown. An unknown class is, by definition, a defect in the harness itself, so it alerts immediately rather than being absorbed as expected noise.
Per-runtime baselines
BASELINE_EXPECTED_CLASSES maps a runtime id to the classes whose mere occurrence is not an alert (only a spike in their rate would be). The baseline for openclaw, claude-code, codex, and hermes is identical, six classes (InvalidArgs, Timeout, ProviderError, RateLimited, UserAborted, UnexpectedEnv). Any runtime not in the map (including clawboo-native) falls back to GENERIC_BASELINE, which is those six plus ContextOverflow (seven). Unknown is never in any baseline; it always alerts.
true when cls is unexpected for the runtime: always true for Unknown, otherwise true when cls is not in that runtime’s baseline.
How the taxonomy feeds the surfaces
- The executor runner classifies each failure as it drains a run’s events, fills
errorClass+harnessBuginto the emittederrorevent, and fires a harness-bug alert when the class isUnknown(a brokered-tool denial is emitted witherrorClass: 'PolicyDenied'andharnessBug: false, never alerting). - The observability error-taxonomy view breaks
errorevents down by class and surfaces theUnknown/harness-bug count. - The fleet-health view (
projectFleetHealth) folds the same log into the fleet-health triage taxonomyAgentHealthStatus:working/idle/stalled/zombie, by how long an agent has been quiet while an execution is open (idle= no open execution;working/stalled/zombieby quiet-time thresholds). - The graph projection (
projectGraph) folds the log into the task-delegation and agent graphs, applying the same per-run cost reconciliation as the metrics.
/api/obs/* endpoints that serve the event log, traces, errors, graph, and fleet health.
See also
- Observability dashboard, traces, errors, fleet health, evals
- Observability REST API,
/api/obs/*(events, traces, errors, graph, health, SSE stream) - Glossary, builder≠judge, harness bug, trace, span
- @clawboo/obs, the package these contracts live in