Skip to content

[Feature]: Accept a per-chat traceparent so opencode spans nest under the caller's trace #109

Description

@jesse-schein

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:

  1. 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.
  2. A field the caller can set on the message, for example metadata.traceparent on the user message, read in the chat.message handler.
  3. 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

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

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