- TypeScript 75.8%
- JavaScript 24.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
An extension whose tool calls another process, such as an MCP client, emits a carrier with its toolCallId on pi-otel:tool-context. pi-otel fills in traceparent and tracestate of the open tool span before emit returns, so the callee's spans nest under the tool span. Assumed pi opens the tool span (tool_execution_start) before it calls the tool's execute; pi 0.85.1 awaits the extension handlers first. Flag if wrong. Refs carvers/gestell#46. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
| .github/workflows | ||
| .husky | ||
| assets | ||
| docs | ||
| samples | ||
| src | ||
| test | ||
| .gitignore | ||
| AGENTS.md | ||
| biome.json | ||
| CLAUDE.md | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
pi-otel
OpenTelemetry tracing for pi agent.
Full OTel GenAI semantic-convention coverage (gen_ai.*) for token usage, cost, model, finish reasons, and tool calls.
| Aspire dashboard | ||
|---|---|---|
![]() |
![]() |
![]() |
| Grafana LGTM | ||
![]() |
![]() |
![]() |
Install
pi install npm:pi-otel
Quickstart
/otel start # spawn local Aspire dashboard
Backend auto-detect: Aspire CLI first, then Docker / Podman. Install one:
- Aspire CLI
- Docker or Podman
Configuration
.pi/settings.json (project) or ~/.pi/agent/settings.json (global):
{
"otel": {
"enabled": true,
"endpoint": "http://localhost:4317",
"protocol": "grpc",
"headers": {},
"serviceName": "pi",
"captureContent": "metadata_only",
"spanNaming": "legacy",
"sampleRatio": 1.0,
"signals": { "traces": true, "metrics": false, "logs": false }
}
}
For the http/protobuf and http/json protocols, endpoint is the base URL — each signal appends its own resource path (/v1/traces, /v1/metrics, /v1/logs). For grpc the endpoint is used as-is.
spanNaming: "genai" (default "legacy") renames spans to the OTel GenAI agent conventions — invoke_agent pi / chat {model} / execute_tool {tool} — and adds gen_ai.operation.name plus the spec SpanKind, so backends recognise pi as an agent. It also adds gen_ai.agent.name on the interaction span and, on chat spans only, gen_ai.provider.name (the request's inference provider, e.g. openai, aws.bedrock). No existing attribute is ever removed in either mode; legacy keeps the pi.* names existing dashboards query.
Key env var overrides: OTEL_EXPORTER_OTLP_ENDPOINT, PI_OTEL_SPAN_NAMING=genai, PI_OTEL_METRICS=1, PI_OTEL_LOGS=1, PI_OTEL_DISABLED=1.
Custom providers that skip pi's onPayload hook (pi-vertex and others) still get a pi.llm_request span with tokens and cost, opened from the assistant message_start and tagged pi.llm_request.synthesized=true. See custom providers.
propagateToShell: true (default false) passes TRACEPARENT to processes started by the bash and powershell tools, so instrumented children nest under the tool span. It overrides the built-in tool, which pi reports with a one-time warning. See shell propagation.
A TRACEPARENT in pi's environment becomes the parent of every interaction span, so pi nests under the trace of the process that started it. See parent trace.
Another extension can ask for the W3C context of its running tool call on the pi-otel:tool-context channel, for example to pass it on in an MCP request. See tool span context.
Only one OpenTelemetry SDK can own a process. If another extension registers its providers first, pi-otel warns once and stays disabled instead of silently routing spans into the other SDK. See running alongside other OTel extensions.
Full reference — settings, env vars, content capture modes, sampling, logs signal, and extensibility: nikiforovall.blog/pi-otel/configuration





