Skip to content

Trace events

trace.jsonl, one JSON object per line, in the order things happened.

The trace is the raw material behind every report. One object per line, each with ts and type plus event-specific fields.

quickstarted run tasks/x.yaml --agent claude --out results/
jq -r 'select(.type == "docs_fetch") | .url' results/x/trace.jsonl

Lifecycle

Type Fields When
run_start task, agent, backend, enforced, attempt, affordance_policy First line
setup command, exit_code, output Per setup command
run_end stop_reason, passed, classification Last line

Agent activity

Type Fields When
agent_turn turn, stop_reason, model, token counts Per model call
tool_call tool, command or url Before a tool runs
tool_result tool, exit_code, duration, timed_out, output After bash
agent_final text The agent's closing summary
api_retry turn, attempt, sleep, error A transient fault was retried

agent_final is the agent's own account of what it did. It is recorded and never scored.

Documentation

Type Fields When
docs_fetch url, chars, original_chars, truncated, from_cache, content_hash, resolved_to A page was read
docs_redirect_followed requested, resolved_to The page was a client-side redirect
docs_changed url, content_hash --refresh saw different bytes
fetch_blocked url, reason Outside the allowlist, robots, or offline
fetch_error url, error The fetch itself failed
affordance_withheld url Blocked by --affordances none
affordances entrypoint, found --probe-affordances

truncated is worth watching. A page too large to read in one call is an agent-experience defect in its own right, and original_chars tells you how far past the limit it was.

docs_redirect_followed means the requested URL answered with a page whose only content was a <meta http-equiv="refresh">, and the real page was fetched instead. DuckDB's versioned URLs do this: 938 bytes of HTML that render as 73 characters of "Redirecting...", with the actual documentation at another path. A browser follows it, so an agent with a browser reads the docs and an agent without reads nothing, and the harness follows it to keep the two comparable.

The hop is only taken within the same host. A stub pointing elsewhere is left alone, because reading a page the task never allowlisted is the attribution hole the proxy exists to close. Both URLs are recorded, so a run never claims to have read a page it did not get.

Network policy

Type Fields When
egress_allowed host, port, method, path The proxy passed a request
egress_blocked host, reason, method The proxy refused one
egress_error host, error, method Upstream connection failed

reason is one of docs_host_requires_read_docs (the shell tried to read documentation), not_allowlisted (an unknown host), or explicit_network_override on the allowed side.

Under Docker these come from the proxy sidecar and are merged into the trace when the run tears down, so ordering against agent events is approximate.

Success check

Type Fields
success_check exit_code, passed, output

The only event that determines the verdict.

Worked example

# Which pages did the agent read, in order?
jq -r 'select(.type=="docs_fetch") | .url' trace.jsonl

# Did it try to read docs through the shell?
jq 'select(.type=="egress_blocked" and .reason=="docs_host_requires_read_docs")' trace.jsonl

# What did the last command do before the run failed?
jq -r 'select(.type=="tool_result") | "\(.exit_code) \(.output[0:120])"' trace.jsonl | tail -3

# Tokens per turn.
jq -r 'select(.type=="agent_turn") | "\(.turn) \(.output_tokens)"' trace.jsonl