You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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:
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.
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.
Activity
dialupdisaster commentedon Mar 24, 2026
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
TracerProviderbacked by an OTLP/gRPC span exporter is straightforward — the SDK supports all three signals independently andotel.tsjust 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:
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 OTelContextpropagating across calls automatically.To get properly nested spans, we'd store live
Spanobjects in the existing context maps (the same maps currently used for timing —pendingToolSpansetc.) 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
message.part.updatedwithstatus: runningis received but the correspondingcompleted/errornever arrives (e.g. process killed mid-tool), the span is never ended. Mitigation: end all pending spans with anERRORstatus duringsession.errorandsession.idlesweep.session.idleorsession.erroris never fired. Harder to mitigate from a plugin; may need a TTL-based cleanup sweep.message.part.updatedwithcompletedcould theoretically arrive beforerunningif events are buffered. The current timing code handles this gracefully with a fallback; span handling will need the same.session.createdevents with aparentIDshould produce child spans of the parent session span, not new root spans. TheparentIDis 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
The referenced core PR (#6629) solves all of these, but it's a draft targeting the
devbranch 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
feat(tracing): add OpenTelemetry traces with gen_ai.* and tool spans
dialupdisaster commentedon Mar 24, 2026
@strangemonad, if you want to test the PR I have up, I will be doing some testing later.
strangemonad commentedon Mar 25, 2026
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.