Is your feature request related to a problem?
I drive opencode from a Python API (FastAPI) that is itself instrumented with OpenTelemetry and reports to SigNoz. Each user gets a long-lived container running opencode serve, and the API dispatches every chat turn over HTTP to that server. Both sides report to the same collector, so I have API spans and opencode spans in one backend, but no way to connect them: I want one end-to-end view of UI -> API -> opencode -> LLM/tool calls, and today the two halves are separate traces.
The plugin already knows how to adopt a remote parent, but only once per process: traceparent is resolved in config.ts from OPENCODE_TRACEPARENT at load time, turned into a context by remoteParentContext in trace-context.ts, and captured in the rootContext closure in index.ts. Every opencode.session span for the life of the process then hangs off that one fixed parent.
Because the server is long-lived and shared across many turns (and in my case many chats for the same user), a spawn-time environment variable cannot express "this turn was caused by that request". Setting it anyway puts every session, LLM, and tool span for the whole container into a single unbounded trace, which is worse than leaving them unparented.
Describe the solution you'd like
Let a traceparent be supplied per turn, and use it as the parent for that run's spans instead of the process-wide root.
Any one of these injection points would work for me, roughly in order of preference:
- A
traceparent (and optional tracestate) HTTP header on the chat request (POST /session/{id}/message), if opencode surfaces incoming request headers to plugins. This is the standard W3C path and needs no opencode-specific convention.
- A field the caller can set on the message, for example
metadata.traceparent on the user message, read in the chat.message handler.
- A plugin option callback, for example
resolveTraceparent({ sessionID, messageID, agent }), letting the embedder decide per run.
Behavior I would expect:
- When a per-turn traceparent is present and valid, the run's
opencode.session span (and its opencode.llm / opencode.tool.* children) parent to it, so the whole turn nests under the caller's span.
- When it is absent or invalid, fall back to today's behavior:
OPENCODE_TRACEPARENT if set, otherwise ROOT_CONTEXT. No change for existing users.
- Subagent sessions keep resolving through
resolveSessionTraceContext, so they stay nested under the parent run rather than reattaching to the remote parent directly.
Describe alternatives you've considered
No response
Telemetry type
Logs/Events
Additional context
Version: @devtheops/opencode-plugin-otel@1.4.0, protocol grpc, exporting to an OpenTelemetry Collector that forwards to SigNoz.
Checklist
Is your feature request related to a problem?
I drive opencode from a Python API (FastAPI) that is itself instrumented with OpenTelemetry and reports to SigNoz. Each user gets a long-lived container running
opencode serve, and the API dispatches every chat turn over HTTP to that server. Both sides report to the same collector, so I have API spans and opencode spans in one backend, but no way to connect them: I want one end-to-end view of UI -> API -> opencode -> LLM/tool calls, and today the two halves are separate traces.The plugin already knows how to adopt a remote parent, but only once per process:
traceparentis resolved inconfig.tsfromOPENCODE_TRACEPARENTat load time, turned into a context byremoteParentContextintrace-context.ts, and captured in therootContextclosure inindex.ts. Everyopencode.sessionspan for the life of the process then hangs off that one fixed parent.Because the server is long-lived and shared across many turns (and in my case many chats for the same user), a spawn-time environment variable cannot express "this turn was caused by that request". Setting it anyway puts every session, LLM, and tool span for the whole container into a single unbounded trace, which is worse than leaving them unparented.
Describe the solution you'd like
Let a traceparent be supplied per turn, and use it as the parent for that run's spans instead of the process-wide root.
Any one of these injection points would work for me, roughly in order of preference:
traceparent(and optionaltracestate) HTTP header on the chat request (POST /session/{id}/message), if opencode surfaces incoming request headers to plugins. This is the standard W3C path and needs no opencode-specific convention.metadata.traceparenton the user message, read in thechat.messagehandler.resolveTraceparent({ sessionID, messageID, agent }), letting the embedder decide per run.Behavior I would expect:
opencode.sessionspan (and itsopencode.llm/opencode.tool.*children) parent to it, so the whole turn nests under the caller's span.OPENCODE_TRACEPARENTif set, otherwiseROOT_CONTEXT. No change for existing users.resolveSessionTraceContext, so they stay nested under the parent run rather than reattaching to the remote parent directly.Describe alternatives you've considered
No response
Telemetry type
Logs/Events
Additional context
Version:
@devtheops/opencode-plugin-otel@1.4.0, protocol grpc, exporting to an OpenTelemetry Collector that forwards to SigNoz.Checklist