Telemetry integration for the pi coding agent and telemetry.dev. It records pi agent loops, model calls, tool executions, and lifecycle events without changing pi's behavior.
Add the package to ~/.pi/agent/settings.json:
{
"packages": ["npm:@telemetry-dev/pi"]
}Pi reads the package's pi.extensions manifest and loads @telemetry-dev/pi/register
automatically.
Alternatively, install @telemetry-dev/pi where pi can resolve it and create
~/.pi/agent/extensions/telemetry-dev.ts:
import { telemetryDevExtension } from "@telemetry-dev/pi";
export default telemetryDevExtension();The integration targets @earendil-works/pi-coding-agent 0.82.1. The pi package is an optional peer
dependency because pi bundles its extension API and provides it to installed extensions.
| Variable | Required | Default | Notes |
|---|---|---|---|
TELEMETRY_DEV_API_KEY |
yes | — | Ingest key (td_live_…). No key ⇒ the integration is a complete no-op. |
TELEMETRY_DEV_BASE_URL |
no | https://ingest.telemetry.dev |
Trailing slashes are stripped. |
TELEMETRY_DEV_ENVIRONMENT |
no | production |
Environment label on every trace/log. |
OTEL_SERVICE_NAME |
no | pi |
OpenTelemetry service name. |
All four are also settable through SDK options when loading the extension directly:
import { telemetryDevExtension } from "@telemetry-dev/pi";
export default telemetryDevExtension({
agentName: "pair-programmer",
captureInput: false,
captureOutput: true,
});Initialization is one-shot; the first extension factory initialized in a process supplies the SDK options.
A typical session produces this hierarchy:
session
└── invoke_agent
├── chat {model} (assistant message)
│ ├── execute_tool {toolName} (tool calls from that message)
│ └── execute_tool {toolName}
└── chat {model} (final assistant message)
- one
sessionwrapper span for the full pi session; - one
invoke_agentspan for each prompt, with the prompt and the final assistant content; - one nested
chat {model}span for each completed assistant message, with the provider, models, response id, finish reason, usage, pi cost, provider request, and all returned content blocks; - one
execute_tool {toolName}span for each tool execution, under thechatspan that issued the call. If no message matches the tool call id, the span is under theinvoke_agentspan. The span includes the tool call id, arguments, result, and error state; - lifecycle logs for sessions, turns, compaction, and model changes.
Each span and log reads ctx.sessionManager.getSessionId() when its event occurs. The span or log
records the value as gen_ai.conversation.id. This keeps /new, /resume, /fork, and /reload
events with the new session. Logs also have gen_ai.agent.name and the pi event type as eventName.
agent_end records the last run result and starts an asynchronous flush. agent_settled closes
unfinished tool spans and the prompt span. session_shutdown closes an unfinished prompt and the
session span, then waits for the final flush. It does not shut down the SDK during a pi session change.
The prompt includes its image blocks. Chat input records the provider-formatted request from
before_provider_request, after all context handlers and pi's model-message conversion.
This conversion removes !! shell output and custom-message details from the model input.
System instructions come from the effective ctx.getSystemPrompt() at the request boundary, after all
before_agent_start handlers. Chat output contains all returned content blocks. The integration also
records tool arguments and tool results.
These values obey the telemetry.dev SDK captureInput and captureOutput settings, which default to
true. Use mask to remove values before capture. Use maxAttributeLength to limit serialized
attributes.
- Chat input uses each provider's request shape, not pi's raw session messages. Pi's public hooks do
not expose the final wire payload. A later
before_provider_requesthandler can replace the request after capture. Custom providers must call pi'sonPayloadcallback for request and system instruction capture. Without that callback, those fields are absent. Prompt, output, and tool capture still work. - Pi does not expose per-request TTFT or duration fields. Chat duration is measured from
message_starttomessage_endusing local wall-clock time, and TTFT is not recorded. tool_callandtool_resultare intentionally not intercepted. In pi, an uncaughttool_callhandler error blocks the user's tool, so this integration uses the fail-opentool_execution_start/tool_execution_endevents instead.- An assistant message with
stopReason: "aborted"is recorded with finish reasonaborted, not as an error. OnlystopReason: "error"marks chat and agent spans as errors. gen_ai.usage.costis pi's client-side USD estimate from its bundled model pricing, not a charge or server-calculated invoice value.- Initialization is one-shot. Loading the package more than once does not replace the first configuration.