Deuz SDK

Observation events

The versioned ObserveEvent protocol — every event type, its identity fields, and the canonical orderings.

Every event extends ObserveEventBase:

FieldMeaning
schemaVersionAlways 1
eventId / sequence / timestampUnique 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()
runIdLogical run. Durable runs adopt the session runId; sub-agents inherit the parent's
executionIdOne execution leg — a resume keeps runId but gets a fresh executionId
spanId / parentSpanIdTimeline 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

EventKey fields
run.startedoperation (stream-chat/generate-text/generate-object/stream-object/embed/embed-many), provider, model, surface, durable, resumed, resumeFromStepId?
run.completedfinishReason, 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.suspendedreason (approval/client-tool/sub-agent-approval), pendingApprovalCount (0 is legal for pure client-tool breaks), pendingToolCount, checkpointStepId?
run.abortedUser abort is a resolution, never a failure; usage is honest (often zeros mid-stream)
run.failederror: ObservedError, counters, partialUsage?
model.startedEffective per-step model (post-prepareStep), purpose?: 'compaction-summary', maxRetries, responseFormat?, promptCaching?
model.first-contentcontentType (text/reasoning/tool-call), ttftMs — tool-call-first responses clear TTFT as of 1.6
model.retryfailedAttempt, nextAttempt, delayMs, retryAfterMs?, reason (network/rate-limit/overloaded/server-error — timeouts never retry), statusCode?
model.completeddurationMs, ttftMs?, retryCount, finishReason, usage, output/reasoning lengths, toolCallCount
model.failederror — emitted once; the run terminal reports the same failure once more at run level
step.started / step.completedEffective model, message count, estimatedInputTokens? (compaction estimate), per-step durationMs + usage + cumulativeUsage, tool counters, stoppedBy?
tool.startedtoolCallId, toolName, needsApproval, derived executionMode (server/client — provider-executed tools emit no tool events), parallel
tool.completeddurationMs, outputType, outputSize?
tool.failedThe ORIGINAL thrown error (normalized), selfHealed, consecutiveFailureCount
tool.deniedcause (server-denied/response-denied/no-response/client-tool-no-result), reason?
approval.requestedapprovalId (= toolCallId today), mode (server/client)
approval.resolvedapproved: boolean, source (server/client-response/default-deny), waitDurationMs? (resume legs, from checkpoint age)
checkpoint.savedstepId, checkpointStepIndex (1-based boundary counter), checkpointStatus, save durationMs, cumulative usage
checkpoint.loadedEmitted by the resume entry points BEFORE run.started; checkpointAgeMs?
checkpoint.failedoperation (save/load), runContinued (save failures never kill the run)
compactionOne per APPLIED layer: layer, threshold, contextWindow, tokensBefore/After (calibrated ESTIMATES), messageCountBefore/After, durationMs
compaction.skippedlayer, reason (e.g. a throwing summarizer — never fails the run)
subagent.started/completed/suspended/failedagentName, depth, parentToolCallId, childRunId? (durable store key), child usage (already folded into the parent — never sum twice)
cost.calculatedAsync priceProvider resolution; may arrive after the terminal event
operation.started/completed/failedAux 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.

On this page