Skip to content

feat: export OpenTelemetry traces and metrics over OTLP - #74

Merged
sanchitrk merged 8 commits into
canaryfrom
claude/linear-cod-111-otel-9496e4
Oct 13, 2026
Merged

sanchitrk merged 8 commits into
canaryfrom
claude/linear-cod-111-otel-9496e4

Conversation

@sanchitrk

@sanchitrk sanchitrk commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Every CLI command can now export the harness's spans and metrics over OTLP/HTTP using Effect's built-in exporter (effect/observability). No OpenTelemetry SDK packages are added.

  • Configuration is env only. OTEL_EXPORTER_OTLP_ENDPOINT turns it on. The standard variables cover headers, per-signal endpoints, protocol (http/protobuf by default, http/json), OTEL_SDK_DISABLED, OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES. Without an endpoint nothing is exported, and a configuration that fails to build runs the command without export.

  • Only high-level spans are exported by default. All spans default to Debug and export starts at Info. The command root, the agent loop (Loop.run → runTurn → runTurnAttempt → settleTools → handleTool), LLM.run, and sandbox create/wake are marked Info. A run exports about 11 spans instead of about 490, which were mostly Effect's sql.execute, file checks and event publishes. CODEWORK_TRACE_LEVEL=debug exports every span again. Metrics are unaffected.

  • Spans (LLM.run client span, Loop.handleTool, Loop.run) carry the OpenTelemetry GenAI attributes: provider, model, session (gen_ai.conversation.id), response id, finish reason, token counts and time to first chunk. A failed provider request ends its span with an error status. Prompts, file contents and model output are never exported.

  • Metrics use codework's own names, because the GenAI metric conventions are still in development and keep renaming:

    Metric Type Labels
    codework.llm.duration histogram, s provider, model, error
    codework.llm.ttft histogram, s provider, model
    codework.llm.tokens counter provider, model, type (input, output, cache_read, cache_write, reasoning)
    codework.llm.cost counter, USD provider, model
    codework.agent.duration histogram, s error
    codework.tool.duration histogram, s tool, error
    codework.sandbox.provision.duration histogram, s driver, operation, error
    codework.sandbox.exec.duration histogram, s driver, error
    codework.sandbox.active gauge driver
    child_fibers_* Effect's fiber runtime metrics none
  • Resource attributes: service.instance.id, deployment.environment.name (the release channel) and codework.command.

  • aikit is unchanged. All data comes from its terminal assistant message.

Notes

  • Exporter default. Effect 4.0.1 only exports when OTEL_{TRACES,METRICS}_EXPORTER explicitly lists otlp. The spec default is otlp, so the CLI supplies that default; anything set in the environment still wins.
  • Units. Effect reads a metric's unit from its unit attribute, so unit also appears as a data point attribute.
  • Unreachable collector. The CLI exits about 3 s later while Effect's exporter retries; OTEL_EXPORTER_OTLP_TIMEOUT shortens that.
  • Remote-only metrics. codework.sandbox.provision.duration and codework.sandbox.active only record for remote and managed sandboxes, so the local E2E test doesn't cover them.

Test plan

  • New E2E packages/codework/test/telemetry.test.ts:
    • Spawns the real CLI against a local fake OpenAI-compatible provider that is also the OTLP collector.
    • The provider fails the first request with a 503, then returns one bash call, then a text reply.
    • Asserts the auth header, a single consistent resource, and no prompt, command, file or model content in any exported trace.
    • Pins the default exported span tree: only the Info spans, no sql.*.
    • Snapshots spans and per-series metric counts and values to __artifacts__/telemetry.json. Stable over 8 runs.
  • Checked by hand: protobuf is the default content type, grpc exports nothing, an invalid endpoint is ignored.
  • vp check, vp run typecheck, harness and codework test suites.

🤖 Generated with Claude Code

sanchitrk and others added 8 commits October 13, 2026 15:23
Every CLI command can export the harness's spans and metrics through
Effect's built-in OTLP exporter, configured only by the standard OTEL_*
environment variables. Without an endpoint nothing is exported, and a
configuration that fails to build runs the command without export.

- harness: GenAI attributes on the LLM, tool and agent spans; a failed
  provider request now ends its span with an error status.
- harness: metrics for token usage, request/tool/agent duration, time to
  first token, time per output token, cost, retries, sandbox provisioning
  and tool output truncation, plus Effect's fiber runtime metrics.
- cli: per-signal http/protobuf (default) or http/json, OTLP exporters
  default to otlp as the spec says, and resource attributes for the
  instance, release channel and command.
- E2E test against a fake provider and collector, snapshotted to
  __artifacts__/telemetry.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also adds the blank line the formatter expects before the Internal heading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GenAI semantic conventions are still in development and have renamed
their metrics more than once, so metrics now use codework's own names and
short labels; spans keep the gen_ai.* attributes trace views read.

- codework.llm.{duration,ttft,tokens,cost}, codework.agent.duration,
  codework.tool.duration, codework.sandbox.{provision,exec}.duration and
  codework.sandbox.active, plus Effect's fiber runtime metrics.
- Spans: gen_ai.usage.cache_write/reasoning.output_tokens names,
  conversation id, stream flag, time to first chunk, agent name.
- Drop the retries and truncation counters and per-signal protocol vars.
- Test drives a bash call so the sandbox exec metric is covered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A single run produced ~500 spans, a third of them Effect's own sql.execute.
Spans now default to Debug and export starts at Info. The command, agent
loop, model and tool calls, and sandbox create/wake are marked Info, so a
run exports about a dozen spans. CODEWORK_TRACE_LEVEL=debug exports every
span again.

Effect drops a span's children with it, so every span on the path from a
command to a model or tool call is Info, Loop.settleTools included. The
E2E test pins the exported span tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…11-otel-9496e4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Most instruction candidates are missing, and a stat on a missing path
fails its span even when the caller treats absence as normal: every debug
trace carried ~54 SandboxFileSystem.stat errors per run. Candidates are now
probed with exists, which answers absence without failing, and only the
files that exist are stat'ed. Not-found stays silent and other failures
still warn on every driver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Export sandbox provisioning: CLI.selectSandbox and Sandbox.resolve are
  Info, and the server RPCs that provision a sandbox start their own Info
  trace, since RpcServer's Debug request span would drop them.
- Record tokens and cost of an interrupted model call from the terminal
  the consumer drains.
- E2E covers CODEWORK_TRACE_LEVEL=debug; the instruction test asserts
  stat only runs for existing files.
- Drop comments that restated the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sanchitrk
sanchitrk merged commit c4473cc into canary Oct 13, 2026
2 checks passed
@sanchitrk
sanchitrk deleted the claude/linear-cod-111-otel-9496e4 branch October 13, 2026 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant