Traces
A trace is one agent run. Spans nest underneath it in the order they executed, so reading a trace top to bottom tells you what the agent actually did — not what the prompt asked it to do.
The span model
Section titled “The span model”| Span kind | Emitted when | Key attributes |
|---|---|---|
run |
Root span, once per run | agent, revision, environment, status |
model |
Each model call | model, input/output tokens, stop reason, latency |
tool |
Each tool call | tool name, MCP server and version, side-effect class, status |
retrieval |
Each MCP retrieval call | result count, filters applied |
custom |
You call ctx.trace.span() |
whatever you set |
Reading one
Section titled “Reading one”velerion traces show run_01HQ8W3ZK7 --treerun support-triage rev 42 prod ok 4.1s 6,240 tok├─ model claude-opus-5 1.2s 1,880 tok stop: tool_use├─ tool postgres.query 0.4s ok│ velerion/postgres@2.4.1 read├─ model claude-opus-5 1.9s 3,910 tok stop: tool_use├─ tool escalate 0.5s error PagerDuty 429├─ model claude-opus-5 0.1s 450 tok stop: end_turn└─ custom record_triage 0.1s okTwo things to read off this immediately. The escalate tool returned a 429 and the run still
completed — the model saw the error and carried on, which is usually right but means a failed page
will not show up as a failed run. And 63% of the tokens are in the second model call, because the
query result was appended to the context in full.
Adding your own spans
Section titled “Adding your own spans”tool({ name: 'record_triage', input: z.object({ ticketId: z.string(), triage: Triage }), async run({ ticketId, triage }, ctx) { return ctx.trace.span('record_triage', async (span) => { span.setAttribute('ticket.id', ticketId); span.setAttribute('triage.urgency', triage.urgency); const result = await db.tickets.update(ticketId, triage); span.setAttribute('db.rows_affected', result.rowCount); return { ok: true }; }); },});Attribute values are truncated at 4 KB and must be primitives. Set attributes you would filter or group by; anything you only want to read once belongs in a log line.
Filtering
Section titled “Filtering”# Runs that called a tool which failed, in the last hour.velerion traces list --agent support-triage --since 1h \ --filter 'tool.status = "error"'
# Slow runs, grouped so you can spot a pattern.velerion traces list --since 24h --filter 'run.duration_ms > 8000' --json \ | jq -r '.[] | "\(.revision) \(.duration_ms) \(.id)"' | sort -n -k2Supported operators are =, !=, >, <, contains and exists. Filters run server-side against
indexed attributes; custom attributes are indexed from the first run that sets them, so a brand new
attribute will not match historical traces.
Exporting to your own stack
Section titled “Exporting to your own stack”Velerion emits OpenTelemetry. Point it at any OTLP collector and traces arrive alongside the rest of your services:
velerion env set OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.internal:4317 --env prodvelerion env set OTEL_EXPORTER_OTLP_HEADERS='authorization=Bearer ...' --env prodExport is additive: traces continue to be retained in Velerion for the 30-day window regardless. If your collector is unreachable, spans are dropped rather than buffered — export is not a durable pipeline and should not be the only copy of anything you need.
