Observation events
The versioned ObserveEvent protocol — every event type, its identity fields, and the canonical orderings.
ドキュメント本文は英語です。ナビゲーション・検索・UI は選択した言語に従います。
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 (the request's identity-bound id, 2.3), 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 | subsystem, operation, itemCount?; on completion durationMs, resultCount?, usage?; on failure error. Aux subsystems (image, midjourney, …) use operation strings like image.generate; the 2.4 subsystems name the operation by its span and add attributes? (see 2.4 spans) |
2.4 spans
The 2.4 subsystems report each unit of work as an operation.started event followed by operation.completed or operation.failed. The operation is the span name, and createOtelObserver opens a span of that name. ObservedSubsystem gains 'context', 'goal', 'schedule', 'process' and 'stream'; versioned memory reports under the existing 'memory'. Work inside a run leg shares the leg's runId and executionId and hangs under the step or run span; work outside one (a supervisor tick, a scheduler pass, a process runner call) is reported as a leg of its own. Without an observer or tracer, no event is built.
| Span | Subsystem | Reported for |
|---|---|---|
deuz.context.compile | context | A context package compiled for a model step, or compiled again on overflow; a child of the step's span in a native run |
deuz.memory.op | memory | A versioned memory remember, extract, revise, retract, forget, reembed or import, and the SQL stores' prunes |
deuz.goal.claim, deuz.goal.dispatch, deuz.goal.retry | goal | The supervisor took a goal's lease; started or resumed its run; retried the run after a retryable failure |
deuz.goal.cancel_requested, deuz.goal.cancel_applied | goal | A cancel was recorded; it took effect on the goal's run |
deuz.goal.drain, deuz.goal.generation | goal | The supervisor drained; a goal moved to a new spec generation |
deuz.schedule.delivery | schedule | A schedule occurrence was delivered to a goal inbox, or disposed of |
deuz.process.start, deuz.process.poll, deuz.process.cancel, deuz.process.exit, deuz.process.reconcile | process | The Docker process runner's calls; the Docker sandbox reports deuz.process.start and deuz.process.exit for each run |
deuz.stream.batch, deuz.stream.fenced | stream | A durable stream batch committed before it is sent; a stale producer's write refused |
Each event can carry attributes: scalar values under deuz.<subsystem>.* keys, never content (no memory text, transcript, tool output or retrieved text). operation.started carries what is known up front, and operation.completed or operation.failed adds what the work found out. The OpenTelemetry observer copies them onto the span next to deuz.subsystem and deuz.operation.
| Subsystem | Attribute keys |
|---|---|
context | deuz.context.selected, .excluded, .tokens, .estimator, .refresh_reason, .fallback, .cache_prefix_changed, .manifest_digest, and per source deuz.context.source.<source>.selected, .excluded and .tokens |
memory | deuz.memory.operation, .record_count, .conflict_retries, .embedding_generation |
goal | deuz.goal.id, .revision, .spec_generation, .run_id, .attempt |
schedule | deuz.schedule.id, .occurrence_id, .disposition, .catch_up, .reason |
process | deuz.process.operation_id, .state, .replayed, .exit_code, .truncated, .reconcile_result, .orphans, .matched, .lost, .removed |
stream | deuz.stream.batch_frames, .batch_bytes, .commit_ms, .fencing_token |
Chat writes, transcript searches and webhook deliveries report no span.
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.