Skip to content

Observability: OTel spans from the orchestrator + failure-mode clustering across threads #280

Closed

Description

@currentbits

Inspired by LangSmith Insights and the OTel GenAI conventions.

Solenta is the one place a cross-provider trace tree exists (it sees claude/codex/kimi from the outside). Emit OpenTelemetry GenAI spans per thread/fork/tool call (session.id = thread, gen_ai.agent.id = provider+profile), exportable over OTLP, and ingest Claude Code's native OTel metrics directly. On top of the event log, run a periodic automation that clusters errored/stalled/retried threads into ranked recurring failure modes with links to the offenders.

Activity

  1. added
    enhancementNew feature or request
    and removed on Aug 17, 2026
  2. currentbits commented on Aug 17, 2026

    @currentbits
    OwnerAuthor

    Landed on coder/observability-otel-spans-from-the-orches-63d5e2.

    Spans. electron/otel.js — OTLP/HTTP with a JSON body, no @opentelemetry/* dependency: OTLP/JSON is a stable documented encoding, so the exporter is a batched fetch of a plain object (~270 lines, batching mirrors session-record.js). Ids are derived, never stored — traceId from the ROOT thread (walking handoffFrom), so an orchestrator and its whole forked crew share one trace, and a restart mid-thread keeps it. Attributes follow the GenAI conventions: session.id = thread, gen_ai.agent.id = provider+profile, gen_ai.provider.name, gen_ai.request.model, gen_ai.usage.*, plus solenta.* for cost/project/orch-worker.

    Emitted from electron/runner.js at the choke points that already existed, so all five provider paths are covered by one call site each: startRun opens, notifyRunTerminal closes with status+usage, and tool spans are bracketed by appendMessage (the only place every adapter mints a tool message, hence the only place a tool start is visible) and closed at the four adapter result sites. The orchestrator fork gets its own span parenting the worker's run — that's the cross-provider tree.

    Claude Code metrics. Redirected rather than received: otel.claudeEnv() returns CLAUDE_CODE_ENABLE_TELEMETRY=1 + OTEL_EXPORTER_OTLP_* pointed at the same collector, spread into the spawn via a new claude.js envExtra (same shape codex.js already had). Since it's env-only it joins the warm-CLI reuse key, otherwise a process spawned before the toggle would mask it. This is ~10 lines instead of standing up an in-process OTLP receiver; if you want the metrics displayed in-app rather than in your collector, that's the follow-up.

    Failure modes. electron/failuremodes.js groups errored/stalled/retried threads by a normalized signature (paths, ids, numbers, quoted strings, timestamps redacted; HTTP statuses kept distinct, exit codes collapsed), filters one-offs, ranks by count then recency. Surfaced at insights:failureModes and in a new Insights view with click-through to each offender.

    Deliberate deviation: the issue asks for a periodic automation. This computes on demand instead — it's deterministic and runs in milliseconds over the whole store, so a scheduler would add stored state and a retention policy for no extra information. The gap that buys: you only see a mode when you open the view, rather than being told proactively that something has now failed six times. automations.js already has the minute-ticker if that proactive notification is worth it — say so and it's a small follow-up.

    Everything is inert while settings.otel.endpoint is null: nothing buffered, no request made, no exception path into a run.

    Tests (+38 new): otel.test.js 8 (exporter against injected deps), failuremodes.test.js 10, otel-runner.test.js 4 (a real run reaching a stubbed collector, tool span parented to the run span in one trace, ERROR span carrying stderr, nothing posted with no endpoint, telemetry env arriving in the spawned CLI), insightsView.test.tsx + settingsModal.test.tsx + otelHeaders.test.ts 16.

    Full suite: core 38/38, renderer 789/789, memory 122/122, electron 913/914 — the one failure is the pre-existing wireClient × webBridge > reconnect baseline that also fails on main.

  3. added and removed on Aug 17, 2026
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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions