pi-phoenix-otel
Pi coding agent extension that streams OpenTelemetry GenAI traces to Arize Phoenix. Zero dependencies.
Package details
Install pi-phoenix-otel from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-phoenix-otel- Package
pi-phoenix-otel- Version
0.2.0- Published
- Sep 8, 2026
- Downloads
- 365/mo · 23/wk
- Author
- lalitmadan
- License
- MIT
- Types
- extension
- Size
- 32.1 KB
- Dependencies
- 0 dependencies · 0 peers
Pi manifest JSON
{
"extensions": [
"./extensions"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-phoenix-otel
Stream pi coding-agent sessions to Arize Phoenix as OpenTelemetry traces — every prompt, tool call, token, and cent rendered as a queryable trace waterfall.
Why This Exists
Agent sessions are black boxes: you see the final answer, but not the twelve tool calls, two dead ends, and 40k cache-tokens it took to get there. LLM observability tools solve this — but wiring a coding agent into one usually means an SDK dependency, a collector daemon, or both.
This extension is none of that. It subscribes to pi's lifecycle events, hand-encodes OTLP protobuf in ~400 lines of zero-dependency TypeScript, and POSTs straight to Phoenix. If Phoenix is down, exports fail silently and your agent never notices.
Zero dependencies. No install-time scripts. One auditable file.
Requirements
pi installed and working
Phoenix, running locally (no Docker needed):
uvx arize-phoenix serve # or: pip install arize-phoenix && phoenix serveUI opens on
http://localhost:6006. Projects are auto-created on first trace.
Install
pi install npm:pi-phoenix-otel
Then restart pi (or run /reload) and send a message.
First run
| You have... | What happens |
|---|---|
| Phoenix already running | Traces flow immediately into your configured project |
| Phoenix not running | Run /otel-start — launches Phoenix via uvx in the background, waits for health, notifies you |
What You Get
One trace per pi session (default):
pi.session · my-repo
├── pi.run 1 · fix the login bug
│ ├── pi.turn 0 (tokens, cost, model)
│ │ ├── execute_tool read
│ │ └── execute_tool edit
│ └── pi.turn 1
├── pi.run 2 · now write tests
│ └── pi.turn 0 ── execute_tool bash
└── ...
| Level | Captured |
|---|---|
| Turn | input/output tokens, cache read/write tokens, reasoning tokens, cost (total + prompt/completion breakdown), request/response model, finish reason, invocation parameters (temperature, max_tokens, …), system prompt, tool schema count |
| Tool call | name, arguments, result text, error flag, duration — with descriptive span names (bash: git status…, read src/index.ts) |
| Run | prompt text (incl. steers & queued follow-ups), final response, image count |
| Session | session id/path, cwd, run count, total duration |
Prefer one trace per user prompt instead of per session? Set "trace": "run" in the config.
After the first export of a session, pi's status bar shows a clickable phoenix ↗ link that opens the session's trace view in Phoenix.
Configuration
Optional config file at ~/.pi/agent/phoenix-otel.config.json:
{
"endpoint": "http://localhost:6006/v1/traces",
"service": "pi-coding-agent",
"project": "my-project",
"apiKey": "",
"captureContent": true,
"trace": "session"
}
| Key | Default | Meaning |
|---|---|---|
endpoint |
http://localhost:6006/v1/traces |
OTLP/HTTP endpoint — any OTLP backend works (Grafana Tempo, Jaeger, SigNoz…) |
service |
pi-coding-agent |
service.name resource attribute |
project |
pi |
Phoenix project name — auto-created on first trace |
apiKey |
unset | Sent as Authorization: Bearer … — for Phoenix Cloud or auth-protected proxies |
captureContent |
true |
false = metadata-only tracing (no prompts/responses/tool text in attribute values; short prompt previews remain in span names) |
trace |
session |
"session" = one trace per session · "run" = one trace per user prompt |
A project-local config at .pi/phoenix-otel.config.json overrides the global file for that repo. Environment variables override everything: PHOENIX_OTEL_ENDPOINT, PHOENIX_SERVICE_NAME, PHOENIX_PROJECT, PHOENIX_API_KEY, PHOENIX_TRACE, PHOENIX_CAPTURE_CONTENT=0.
Slash Commands
| Command | Action |
|---|---|
/otel-start |
Launch Phoenix via uvx arize-phoenix serve as a detached background process; polls until healthy (survives pi exiting; logs to /tmp/pi-phoenix.log) |
/otel-status |
Server status, endpoint, project, service, capture mode, trace mode, auth mode |
/otel-flush |
Flush pending spans immediately |
Privacy & Security
- Zero dependencies — no supply-chain surface; one source file (~700 lines), fully auditable
- No install-time code — no npm lifecycle scripts; runs only when pi loads it
- Local-first — the only network call is a POST of spans to the endpoint you configure (
http://localhost:6006/v1/tracesby default); nothing is sent anywhere else. IfapiKeyis set, it is sent only to that endpoint as a bearer token - Content capture is on by default so traces are useful for debugging — prompts, responses, and tool results are included and can contain sensitive material (e.g., secrets echoed by a command you ran). Set
"captureContent": falseorPHOENIX_CAPTURE_CONTENT=0for metadata-only tracing - No HTTPS enforcement — if you point
endpointat a remotehttp://URL, traffic is unencrypted; use HTTPS for anything non-local
Development
npx -p typescript@5.9 tsc -p tsconfig.check.json # strict typecheck against pi's API
npx tsx smoke-test.mjs # full-capture mode end-to-end
npx tsx smoke-test-metadata.mjs # metadata-only mode, retry, auth, redaction
Two smoke tests need no Phoenix server: both stub pi's extension API, capture the protobuf POST on a local HTTP server, and assert on decoded span names and attributes — smoke-test.mjs (full capture) and smoke-test-metadata.mjs (metadata-only mode, export retry, bearer auth, redaction).
License
MIT