Observation events
The versioned ObserveEvent protocol — every event type, its identity fields, and the canonical orderings.
Le contenu des pages de documentation est en anglais. La navigation, la recherche et l’interface suivent la langue choisie.
Every event extends ObserveEventBase:
| Field | Meaning |
|---|---|
schemaVersion | Always 1 |
eventId / sequence / timestamp | Unique id (since 1.6.1 derived as `${executionId}:${sequence}` — the format is not part of the contract) · 0-based monotonic counter per execution leg · deps.clock.now() |
runId | Logical run. Durable runs adopt the session runId; sub-agents inherit the parent's |
executionId | One execution leg — a resume keeps runId but gets a fresh executionId |
spanId / parentSpanId | Timeline node: run → step → (model | tool | compaction) → sub-agent |
agentPath? / stepIndex? | Sub-agent chain · loop step counter (continues across durable legs) |
metadata? / truncated? | App metadata from ObservationOptions · set when limits truncated a payload |
Event catalog
| Event | Key fields |
|---|---|
run.started | operation (stream-chat/generate-text/generate-object/stream-object/embed/embed-many), provider, model, surface, durable, resumed, resumeFromStepId? |
run.completed | finishReason, endReason (natural/stop-condition/max-steps/runaway-tool-errors), stoppedBy?, step/model/tool/retry/approval/checkpoint/sub-agent counters, usage (this leg), cumulativeUsage? (durable), costUsd? |
run.suspended | reason (approval/client-tool/sub-agent-approval), pendingApprovalCount (0 is legal for pure client-tool breaks), pendingToolCount, checkpointStepId? |
run.aborted | User abort is a resolution, never a failure; usage is honest (often zeros mid-stream) |
run.failed | error: ObservedError, counters, partialUsage? |
model.started | Effective per-step model (post-prepareStep), purpose?: 'compaction-summary', maxRetries, responseFormat?, promptCaching? |
model.first-content | contentType (text/reasoning/tool-call), ttftMs — tool-call-first responses clear TTFT as of 1.6 |
model.retry | failedAttempt, nextAttempt, delayMs, retryAfterMs?, reason (network/rate-limit/overloaded/server-error — timeouts never retry), statusCode? |
model.completed | durationMs, ttftMs?, retryCount, finishReason, usage, output/reasoning lengths, toolCallCount |
model.failed | error — emitted once; the run terminal reports the same failure once more at run level |
step.started / step.completed | Effective model, message count, estimatedInputTokens? (compaction estimate), per-step durationMs + usage + cumulativeUsage, tool counters, stoppedBy? |
tool.started | toolCallId, toolName, needsApproval, derived executionMode (server/client — provider-executed tools emit no tool events), parallel |
tool.completed | durationMs, outputType, outputSize? |
tool.failed | The ORIGINAL thrown error (normalized), selfHealed, consecutiveFailureCount |
tool.denied | cause (server-denied/response-denied/no-response/client-tool-no-result), reason? |
approval.requested | approvalId (= toolCallId today), mode (server/client) |
approval.resolved | approved: boolean, source (server/client-response/default-deny), waitDurationMs? (resume legs, from checkpoint age) |
checkpoint.saved | stepId, checkpointStepIndex (1-based boundary counter), checkpointStatus, save durationMs, cumulative usage |
checkpoint.loaded | Emitted by the resume entry points BEFORE run.started; checkpointAgeMs? |
checkpoint.failed | operation (save/load), runContinued (save failures never kill the run) |
compaction | One per APPLIED layer: layer, threshold, contextWindow, tokensBefore/After (calibrated ESTIMATES), messageCountBefore/After, durationMs |
compaction.skipped | layer, reason (e.g. a throwing summarizer — never fails the run) |
subagent.started/completed/suspended/failed | agentName, depth, parentToolCallId, childRunId? (durable store key), child usage (already folded into the parent — never sum twice) |
cost.calculated | Async priceProvider resolution; may arrive after the terminal event |
operation.started/completed/failed | Aux subsystems (image, midjourney); operation strings like image.generate |
ObservedError
{ name, category, code?, statusCode?, retryable?, provider?, message? } — category derives from stable DeuzError.code strings (authentication/authorization split on 401/403, rate-limit, overloaded, timeout, network, provider, validation, tool, checkpoint, aborted, unknown). message exists only when capture.errorMessages is on, and always passes redaction.
Canonical orderings
Single call: run.started → model.started → model.first-content → model.completed → run.completed
With retries: … model.started → model.retry × N → model.first-content → … (one model.started per call)
Tool loop: run.started → (step.started → model.* → tool.started → tool.completed → step.completed) × N → run.completed
Suspension: … approval.requested → step.completed → checkpoint.saved{suspended} → run.suspended
Resume: checkpoint.loaded → run.started{resumed} → approval.resolved → tool.* (step-less settle) → step.started → …
Abort: … model.completed{finishReason:'aborted'} → run.aborted (never model.failed / run.failed)Parallel tools emit tool.started in batch order and tool.completed in REAL completion order, each with its own spanId under the shared step span.