Skip to content

feat: promptconduit collect — local-first OTLP receiver + dashboard#52

Merged
scotthavird merged 3 commits into
mainfrom
claude/agent-tracking-planning-83j6d
May 16, 2026
Merged

feat: promptconduit collect — local-first OTLP receiver + dashboard#52
scotthavird merged 3 commits into
mainfrom
claude/agent-tracking-planning-83j6d

Conversation

@scotthavird
Copy link
Copy Markdown
Contributor

Summary

Scaffolds the open-source half of an agent-tracing stack so we can start dogfooding today without any cloud backend.

  • New promptconduit collect command starts an OTLP/HTTP+JSON receiver on 127.0.0.1:4318 (/v1/traces, /v1/logs; /v1/metrics accepted-and-discarded).
  • Append-only NDJSON store at ~/.config/promptconduit/collect/ with 50MB rotation.
  • Embedded read-only dashboard at 127.0.0.1:4319 — single HTML/JS file served from go:embed, lists traces grouped by trace_id with parent/child indentation, 2s auto-refresh, dark mode.
  • Zero new go.mod dependencies — stdlib only.

This is the public, open-source side of the open-core split. The cloud-side OTLP receiver lives in promptconduit/platform (companion PR) and accepts the same wire format, so the same agent can ship traces to either endpoint by swapping OTEL_EXPORTER_OTLP_ENDPOINT.

Dogfood path

make build && ./promptconduit collect

# in another shell
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_EXPORTER_OTLP_PROTOCOL=http/json
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318

# use Claude Code, then open http://127.0.0.1:4319

Files

  • cmd/collect.go — cobra command
  • cmd/root.go — register collectCmd, add it to the update-check skip list
  • internal/collect/server.go — orchestrates OTLP receiver + dashboard + store
  • internal/collect/otlp.go — minimal OTLP/HTTP+JSON parser + normalization
  • internal/collect/store.go — NDJSON append store, span/log read & trace summaries
  • internal/collect/dashboard.go/, /api/traces, /api/spans
  • internal/collect/ui/index.html — embedded single-file dashboard
  • internal/collect/otlp_test.go — round-trip + 415 + 405 tests

Test plan

  • make test passes (3 new tests in internal/collect)
  • make build produces a binary that links cleanly (no new deps)
  • ./promptconduit collect starts both servers, prints addresses, exits cleanly on Ctrl-C
  • Send a sample OTLP/JSON POST via curl and confirm the dashboard shows it
  • Point Claude Code at the local endpoint and watch real traces accumulate
  • Sending Content-Type: application/x-protobuf returns 415 with the documented hint
  • Hitting /v1/metrics returns 200 (accept-and-discard)
  • After 50MB of spans, rotation produces spans.ndjson.1

Out of scope (follow-ups)

  • Protobuf support (only JSON for now).
  • Optional upstream forwarding to the cloud platform.
  • MCP server for in-IDE query of local traces.
  • promptconduit run <agent> wrapper that auto-sets OTEL env vars.

https://claude.ai/code/session_01ECtvFtsTx4QMVWKttzMQW2


Generated by Claude Code

Adds a self-contained agent-tracing dogfood stack:

  - OTLP/HTTP+JSON receiver on 127.0.0.1:4318 (/v1/traces, /v1/logs;
    /v1/metrics is accepted-and-discarded for now).
  - Append-only NDJSON store under ~/.config/promptconduit/collect/
    with 50MB rotation. No new go.mod dependencies — stdlib only.
  - Embedded read-only HTML dashboard on 127.0.0.1:4319 with a tiny
    JSON API (/api/traces, /api/spans) and 2s auto-refresh.

Point Claude Code at it:
  export CLAUDE_CODE_ENABLE_TELEMETRY=1
  export OTEL_EXPORTER_OTLP_PROTOCOL=http/json
  export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318

This is the public, open-source half of the open-core split — the
collector works without any platform backend. The cloud-side OTLP
receiver lives in promptconduit/platform.
The handler test wrote a SpanRow to NDJSON and read it back. Inside
anyValueToGo we emit int64(412) — but encoding/json renders that as a
bare JSON number, and decoding back into map[string]any (the type of
SpanRow.Attributes) rebuilds it as float64(412). The previous
assertion (int64(412)) was comparing the pre-encode type against the
post-decode value and failing.

Fix:
  - Round-trip test now asserts float64(412) (the value as the
    dashboard sees it after reading the NDJSON).
  - New TestAnyValueToGoTypes calls anyValueToGo directly and pins
    down the in-process types (string, bool, int64, float64, bytes)
    so a regression that drops the integer typing before encoding
    still gets caught.

Verified locally: `go test ./...` passes.
@scotthavird scotthavird marked this pull request as ready for review May 16, 2026 14:10
@scotthavird scotthavird merged commit 00a6416 into main May 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant