Coding agents
What each vendor exports
Per vendor: whether you get one tree per turn, tool calls and their output, prompts and replies, subagents, tokens and cost — and which file the installer writes.
After this page you know, before you install, what a given agent can and cannot send — and therefore what a tool row with no result or a turn with no dollars means.
What you get
| Vendor | One tree per turn | Tool calls | Tool output | Prompt and reply | Subagents | Tokens and cost |
|---|---|---|---|---|---|---|
claude-code (incl. VS Code and JetBrains) | yes — the agent’s own spans (--no-traces falls back to flat log events) | yes | --capture full | --capture full | yes, nested under the Agent tool call | tokens on the span, cost from the api_request event |
codex | yes — the turn span; Codex’s ~150 internal plumbing spans are dropped by the receiver | when Codex emits them | --capture full | prompt with --capture full | no | sse_event tokens, turn_cost |
gemini-cli | yes — agent_call / llm_call / tool_call spans | yes | no | --capture full | agent_call spans | tokens yes, cost no |
cursor | yes — built by the hooks with deterministic span ids | yes | --capture full | --capture full | yes (subagentStart / subagentStop) | no — Cursor’s hooks carry neither |
windsurf | yes — same construction | yes | --capture full | --capture full | no (Cascade has no subagent events) | no |
opencode | yes — built by the plugin | yes | --capture full | --capture full | child sessions nest under their parent turn | when OpenCode reports them on the assistant message |
What the installer writes
| Vendor | Files | Removed by uninstall |
|---|---|---|
claude-code | env keys in ~/.claude/settings.json (or .claude/settings.json with --scope project): telemetry on, OTLP exporters over http/protobuf, the endpoint, the Authorization=Bearer header, resource attribute evalkit.vendor=claude-code; span mode on by default (CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1, OTEL_TRACES_EXPORTER=otlp, OTEL_TRACES_EXPORT_INTERVAL=1000). Unless --no-subagents, two hooks (PostToolUse, SubagentStop) that run evalkit coding hook. The same file drives the VS Code and JetBrains flavours. | those keys, with previous values restored, and only the hooks whose command contains coding hook --vendor claude-code |
codex | an [otel] table between # evalkit-managed-begin/end markers in ~/.codex/config.toml (or .codex/config.toml) with log, trace and metrics exporters pointing at <endpoint>/v1/{logs,traces,metrics}; refuses to run if an [otel] table already exists outside the markers | the block |
gemini-cli | a telemetry block in ~/.gemini/settings.json (otlpProtocol: http, traces: true unless --no-traces, logPrompts false unless --capture full) and the OTEL_EXPORTER_OTLP_HEADERS line between # evalkit-begin/end markers in ~/.gemini/.env | the previous telemetry values restored and the marker block removed |
cursor | 12 hook entries (evalkit coding hook --vendor cursor --event …, 5 s timeout) in ~/.cursor/hooks.json or .cursor/hooks.json; other hooks untouched | only entries whose command contains coding hook --vendor cursor; a reinstall also drops entries for events no longer used |
windsurf | 6 hook entries in ~/.codeium/windsurf/hooks.json (project scope: .windsurf/hooks.json) | same rule |
opencode | ~/.config/opencode/plugins/evalkit.ts (the plugin, with no key inside — it reads ~/.config/evalkit/config.env at run time) and a "plugin" entry in ~/.config/opencode/opencode.json; project scope writes .opencode/plugins/evalkit.ts and opencode.json in the current directory | the plugin file and only our entry in "plugin" |
The spans Cursor, Windsurf and OpenCode build themselves
Because these vendors export nothing, the hook or plugin posts OTLP spans directly. Every id is derived from payload fields with SHA-256, so processes that never talk to each other still agree on the tree: for Cursor the trace id is sha256(conversation_id + generation_id)[:32], the turn root sha256(generation_id)[:16], a tool span sha256(tool_use_id)[:16], a subagent sha256(subagent_id)[:16]; Windsurf uses trajectory_id and execution_id; OpenCode uses sessionID and messageID, and a child session reuses its parent’s trace. Tool spans are written by the post hook with start = now − duration, so the waterfall shows the real interval. A turn root arrives with start == end when the prompt is submitted and is completed later by an enrichment record carrying the real end time and, with --capture full, the completion. Without full capture the turn root is named turn instead of the prompt’s first 80 characters.