Pi coding agent
Trace pi agent loops, model calls, tool executions, usage, cost, and lifecycle events.
@telemetry-dev/pi adds telemetry.dev to @earendil-works/pi-coding-agent. It targets pi 0.82.1 and accepts pi 0.80.4 or later.
What it captures
A session shares one trace across prompts:
session
└── invoke_agent
├── chat {model}
│ ├── execute_tool {toolName}
│ └── execute_tool {toolName}
└── chat {model}
sessionwraps the full pi session; each prompt adds aninvoke_agentchild.invoke_agentrecords the prompt, final assistant content, result, and session ID.chat {model}records provider, models, response ID, finish reason, token usage, pi’s estimated USD cost, the provider-formatted request, and all returned content blocks.execute_tool {toolName}records the tool-call ID, arguments, result, and error state.- Session, turn, compaction, model-change, and shutdown events become logs.
Tool spans nest under the chat span that issued the tool call. A tool without a matching chat span nests under invoke_agent.
Each event reads the current pi session ID and records it as gen_ai.conversation.id, so session changes keep their own telemetry.
Content capture
Prompt input includes image blocks. Chat input comes from before_provider_request, after pi’s context handlers and model-message conversion. System instructions come from the effective ctx.getSystemPrompt() at that request boundary.
Assistant output includes text, thinking, tool-call blocks, and provider signatures. Tool arguments and results are also captured. captureInput and captureOutput default to true; use them to disable the corresponding content, or use mask to remove selected values before serialization. Model names, usage, cost, session IDs, and lifecycle metadata are still recorded when content capture is off.
The request is not necessarily the final wire payload: a later handler can replace it. Custom providers must call pi’s onPayload callback for request and system-instruction capture. Without it, those fields are absent; prompt, output, and tool capture still work.
Set up the integration
Make a project at telemetry.dev. Then copy an API key from the project’s setup page.
Set these variables in the environment that starts pi:
export TELEMETRY_DEV_API_KEY=td_live_...
export TELEMETRY_DEV_ENVIRONMENT=development
export OTEL_SERVICE_NAME=pi
The integration reads all setup values from the environment when pi loads the package entry.
Pi package
Add the package to ~/.pi/agent/settings.json:
{
"packages": ["npm:@telemetry-dev/pi"]
}
The package manifest points to ./dist/register.mjs. The public @telemetry-dev/pi/register entry exports telemetryDevExtension() with environment setup.
Extension file
If pi can resolve the package, add ~/.pi/agent/extensions/telemetry-dev.ts:
import { telemetryDevExtension } from "@telemetry-dev/pi";
export default telemetryDevExtension();
Use the factory form when you must set options in code:
import { telemetryDevExtension } from "@telemetry-dev/pi";
export default telemetryDevExtension({
agentName: "pair-programmer",
captureInput: false,
captureOutput: true,
});
Span lifecycle
agent_start opens the invoke_agent span. Retries and compaction can start the agent loop again, but they keep the same span.
This behavior lets a later retry replace a retryable result before the prompt settles. session_shutdown closes an unfinished prompt and the session span, then waits for the final flush. A session change does not shut down the SDK.
Pi does not supply TTFT or request-duration values. The integration measures chat duration from message_start to message_end with the local clock.
Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
TELEMETRY_DEV_API_KEY |
Yes | None | Ingest API key. Without a key, the integration is a no-op. |
TELEMETRY_DEV_BASE_URL |
No | https://ingest.telemetry.dev |
OTLP ingest base URL. |
TELEMETRY_DEV_ENVIRONMENT |
No | production |
Environment on exported telemetry. |
OTEL_SERVICE_NAME |
No | pi |
OpenTelemetry service name. |
Options
telemetryDevExtension() accepts TelemetryDevExtensionOptions. The type contains SDK options except registerGlobal, plus agentName.
| Option | Type | Default | Description |
|---|---|---|---|
agentName |
string |
pi |
Value for gen_ai.agent.name. |
apiKey |
string |
TELEMETRY_DEV_API_KEY |
Ingest API key. |
baseUrl |
string |
TELEMETRY_DEV_BASE_URL or production ingest |
Ingest base URL. |
environment |
string |
TELEMETRY_DEV_ENVIRONMENT or production |
Deployment environment. |
serviceName |
string |
OTEL_SERVICE_NAME or pi |
Service name on each trace. |
enabled |
boolean |
true |
Set to false for a complete no-op. |
exportMode |
"batched" | "immediate" |
batched |
Span export mode. |
captureInput |
boolean |
true |
Capture prompts, images, provider requests, system instructions, and tool arguments. |
captureOutput |
boolean |
true |
Capture assistant content blocks and tool results. |
mask |
(value, context) => unknown |
None | Change captured values before serialization. |
maxAttributeLength |
number |
65536 |
Maximum content attribute length. |
batch |
BatchOptions |
SDK defaults | Batch size, delay, queue, and timeout settings. |
spanFilter |
(span) => boolean |
None | Select spans for export. |
resourceAttributes |
Record<string, AttributeValue> |
None | Add OpenTelemetry resource attributes. |
logLevel |
"debug" | "info" | "warn" | "error" | "silent" |
warn |
SDK diagnostic level. |
fetch |
typeof fetch |
globalThis.fetch |
Custom fetch implementation. |
waitUntil |
(promise) => void |
None | Extend serverless work for export. |
onError |
(error) => void |
None | Receive integration errors. The extension does not throw them into pi. |
The integration forces registerGlobal to false. The first initialization in a process supplies the SDK options.
Open the trace explorer after a prompt settles. For API key setup, refer to the quickstart.