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

    VendorOne tree per turnTool callsTool outputPrompt and replySubagentsTokens 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 fullyes, nested under the Agent tool calltokens on the span, cost from the api_request event
    codexyes — the turn span; Codex’s ~150 internal plumbing spans are dropped by the receiverwhen Codex emits them--capture fullprompt with --capture fullnosse_event tokens, turn_cost
    gemini-cliyes — agent_call / llm_call / tool_call spansyesno--capture fullagent_call spanstokens yes, cost no
    cursoryes — built by the hooks with deterministic span idsyes--capture full--capture fullyes (subagentStart / subagentStop)no — Cursor’s hooks carry neither
    windsurfyes — same constructionyes--capture full--capture fullno (Cascade has no subagent events)no
    opencodeyes — built by the pluginyes--capture full--capture fullchild sessions nest under their parent turnwhen OpenCode reports them on the assistant message

    What the installer writes

    VendorFilesRemoved by uninstall
    claude-codeenv 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
    codexan [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 markersthe block
    gemini-clia 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/.envthe previous telemetry values restored and the marker block removed
    cursor12 hook entries (evalkit coding hook --vendor cursor --event …, 5 s timeout) in ~/.cursor/hooks.json or .cursor/hooks.json; other hooks untouchedonly entries whose command contains coding hook --vendor cursor; a reinstall also drops entries for events no longer used
    windsurf6 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 directorythe 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.

    EvalKit is built by Syntropylabs. Published on PyPI and npm.