Skip to content

[Feature]: otel-llm / openllmetry compatible spans #19

Closed

Description

@strangemonad

Is your feature request related to a problem?

Love that you've created this plugin to actually allow exporting OTEL signals.

Are there any plans to emit traces and spans? Having dug in to the opencode hooks available, I'm not sure how feasible it would be to actually report fully valid spans using a plugin (the native integration makes pushing session and prompt span context a bit easier e.g. anomalyco/opencode#6629) but from what I can seem to infer, OTEL support isn't a top priority for the core team.

Describe the solution you'd like

I want to be able to collect full traces so I can build evals, scorers, and test if our internal coding harness improves things for the team.

Describe alternatives you've considered

No response

Telemetry type

Other

Additional context

No response

Checklist

  • I have searched existing issues and discussions for similar feature requests
  • I have checked the changelog to see if this already exists

Activity

  1. dialupdisaster commented on Mar 24, 2026

    @dialupdisaster
    Contributor

    Thanks for the kind words, and yes — this is something we can implement from the plugin layer with some caveats worth being upfront about.

    What's feasible

    Adding a TracerProvider backed by an OTLP/gRPC span exporter is straightforward — the SDK supports all three signals independently and otel.ts just needs a third provider wired up alongside the existing meter and logger providers.

    The data needed for OpenLLMetry / gen_ai.* semantic conventions is already being captured (token counts, cost, duration, model, provider, tool names, timing). The gap is purely shape — right now it's emitted as logs and metrics; it needs to become traces with properly nested spans.

    The target trace structure would look something like:

    session (root span)
      └─ user_prompt (span)
           └─ api_request / gen_ai.* (span)
                ├─ tool: read_file (child span)
                └─ tool: bash (child span)
    

    The core constraint: manual span context stitching

    The plugin receives discrete, stateless events (session.created, message.updated, message.part.updated) with no shared async context between them. There's no OTel Context propagating across calls automatically.

    To get properly nested spans, we'd store live Span objects in the existing context maps (the same maps currently used for timing — pendingToolSpans etc.) and manually wire parent/child relationships using stored span references. This is doable but requires careful handling of the edge cases below.

    Known edge cases to track

    • Orphaned tool spans — if a message.part.updated with status: running is received but the corresponding completed/error never arrives (e.g. process killed mid-tool), the span is never ended. Mitigation: end all pending spans with an ERROR status during session.error and session.idle sweep.
    • Orphaned session spans — same problem at the session level if session.idle or session.error is never fired. Harder to mitigate from a plugin; may need a TTL-based cleanup sweep.
    • Out-of-order events — message.part.updated with completed could theoretically arrive before running if events are buffered. The current timing code handles this gracefully with a fallback; span handling will need the same.
    • Subagent sessions — session.created events with a parentID should produce child spans of the parent session span, not new root spans. The parentID is available in the event payload.
    • gen_ai.* content attributes — OpenLLMetry conventionally includes prompt/completion content in spans. The plugin does not receive message content (only metadata), so those attributes will be absent. This is a hard limit of the plugin API and worth documenting explicitly.

    What's not possible from the plugin layer

    • Injecting span context into the outbound LLM HTTP call (would require hooking the transport layer inside opencode core)
    • Capturing prompt/completion content in spans
    • Instrumenting internal opencode operations (LSP, MCP, file I/O) — those would require the native integration approach referenced in the issue

    The referenced core PR (#6629) solves all of these, but it's a draft targeting the dev branch with no clear merge timeline.


    We'll go ahead and implement what's feasible here — a TracerProvider, session/message/tool spans with proper nesting, gen_ai.* semantic attributes where data is available, and documented limitations for the gaps. Will track the edge cases listed above and handle or document each one.

    -- Words by OpenCode

  2. dialupdisaster commented on Mar 24, 2026

    @dialupdisaster
    Contributor

    @strangemonad, if you want to test the PR I have up, I will be doing some testing later.

  3. strangemonad commented on Mar 25, 2026

    @strangemonad
    Author

    Sweet, I can take a look in a bit. Have spent a good amount of time digging in to how one would implement otel traces in opencode, I think I mostly agree with the notes and caveats above.

    I wonder if proposing missing extension points might get support for the OC core team.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions