Pi Tracing with Litefuse
Pi is a minimal terminal-based coding agent. This integration is a Pi extension installed at ~/.pi/agent/extensions/litefuse/ — a single TypeScript file with zero npm dependencies. Pi loads it in-process and the extension subscribes to lifecycle events (agent_start, before_provider_request, message_end, tool_execution_start/end, agent_end), emitting one Litefuse trace per user turn with real per-event timestamps.
Spans are sent as raw OTLP/HTTP JSON using Node’s built-in fetch — no SDK, no node_modules, no build step (Pi loads TypeScript directly).
For AI — automated install
If you’re chatting with Pi right now, paste this prompt and the agent will handle the whole install end-to-end:
Read https://litefuse.ai/SKILL.md and follow the instructions to install and configure Litefuse for Pi.
The skill will ask for your Litefuse API keys (or walk you through signing up if you don’t have an account yet), then configure everything in place. For step-by-step manual setup instead, continue below.
What gets captured
| Data | Captured as | Notes |
|---|---|---|
| User prompt | trace input | image block count in metadata |
| Each LLM API call | generation observation | plan (n tools) #N / response / think #N, named after what the model did |
| Thinking / text / toolCall blocks | generation output | block structure preserved, including reasoning text |
| Time to first token | completion_start_time on generation | from the first streamed delta — powers Litefuse’s TTFT metric |
| Sampling parameters | model_parameters on generation | temperature / max_tokens / top_p, when the provider payload carries them |
Token usage (input, output, cache_read_input_tokens, cache_creation_input_tokens) | usage_details on generation | Anthropic-style keys, so Litefuse cost mapping works |
| Model name + provider cost | model + cost_details on generation | Pi’s own cost data when available, otherwise Litefuse computes from the model price table |
| Tool executions | tool observation | tool: bash (grep) #N, tool: read (index.ts) #N — key info in the name, full args in input |
| Subagents (single / parallel / chain) | subtree | tool (n subagents) #N → subagent container → the child’s own plan/tool/response steps; child cost rolls up into the parent trace |
| Tool errors | tool observation, level=ERROR | with status_message preview |
| LLM errors / aborts | generation level=ERROR / WARNING | from stopReason, with HTTP status + request id in metadata |
| Context compaction | event observation | explains sudden input-token drops on the next call |
| Context window usage | trace metadata agent_context_usage | tokens / window / percent at turn end |
| Session grouping | trace session_id | Pi session UUID; resumed sessions keep counting turns |
| User identity | trace user_id | $LITEFUSE_USER_ID, falls back to the OS username |
| Tags | trace tags | pi-agent + model:<name> |
Trace structure
A typical turn that delegates to a subagent produces a trace shaped like:
Pi Agent — Turn 1 (AGENT root — real turn duration)
├── plan (1 tool) #1 (generation, usage_details, real latency)
├── tool: bash (grep) #2 (tool)
├── plan (1 tool) #3 (generation)
├── tool (1 subagent) #4 (tool — the delegation as seen by the parent)
│ └── subagent (AGENT container — the child pi process)
│ ├── plan (1 tool) #1 (child-local numbering)
│ ├── tool: ls (demo-src) #2
│ └── subagent response (generation — the child's final answer)
└── response (generation — final answer, carries its own usage)Design notes:
- Generations are named after what the model did, not which model did it (
plan (2 tools),response,think) — the model name is the generation’smodelattribute, so dashboards survive model swaps. Names are decided once, after the message completes; they are never renamed mid-flight. responseIS the last LLM call. Pi’s agent loop ends when an assistant message carries no tool calls — that message is the final answer, so it carries its real token usage and latency. No duplicate closing observation.- One step counter per agent container:
#Nis shared by generations and tools chronologically. Tool metadataagent_plan_steppoints at theagent_step_indexof the plan that requested it —tool.agent_plan_step == generation.agent_step_indexis the join. Each subagent container restarts at#1; the tree expresses the hierarchy. - Subagent propagation via env: when a
subagenttool starts, the extension exportsLITEFUSE_TRACEPARENT=00-<traceId>-<toolSpanId>-01. Spawned child Pi processes inherit it, detect it on startup, and join the parent’s trace as a subtree instead of creating their own — recursively, for nested delegation. The tool span vs. container duration gap exposes the real delegation overhead (process spawn, runtime load). - Every span is sent exactly once, when it ends — OTel spans are immutable; no provisional sends, no upserts. Trace headers (name / session / user / input / tags) ride on every span, so the trace appears in Litefuse as soon as the first observation (typically
plan #1, seconds in) completes — long turns are visible while still running. - Real wall-clock timestamps from the moment each event fired — the timeline reflects true LLM latency, tool durations, and gaps.
- Flat
agent_*metadata: all Pi-specific fields are top-level metadata keys with one uniform prefix (agent_step_index,agent_plan_step,agent_duration_ms, …) — the same dashboard filter works across every Litefuse agent integration. Sparse: absent fields are not padded withnull. - Fail-open: any unexpected error is logged to
~/.pi/agent/litefuse.logand swallowed — the extension never blocks Pi’s loop. Unreachable targets are skipped silently.
Quick Start
Prerequisites
- Pi installed — check with
pi --version. - A Litefuse project at https://litefuse.cloud with public + secret keys.
No other dependencies: the extension is a single file using only Node built-ins, and Pi loads TypeScript directly.
Download the extension
mkdir -p ~/.pi/agent/extensions/litefuse
curl -fsSL https://litefuse.ai/integrations/pi/index.ts \
-o ~/.pi/agent/extensions/litefuse/index.tsThe source is also browseable at the same URL — feel free to read it before deploying.
Configure credentials
Create ~/.pi/agent/litefuse-targets.json (no shell-profile edits needed):
cat > ~/.pi/agent/litefuse-targets.json <<'EOF'
[
{
"publicKey": "pk-lf-xxx",
"secretKey": "sk-lf-xxx",
"baseUrl": "https://litefuse.cloud",
"environment": "production"
}
]
EOF
chmod 600 ~/.pi/agent/litefuse-targets.jsonReplace the placeholders with your real keys. Alternatively, set LITEFUSE_PUBLIC_KEY / LITEFUSE_SECRET_KEY / LITEFUSE_BASE_URL in your shell environment (LANGFUSE_* names are accepted as a fallback). The targets file may list multiple targets — the same trace is then written to every Litefuse instance, each with its own environment.
Verify
PI_LITEFUSE_DEBUG=true pi --no-session -p "Reply with exactly: ok"
tail -3 ~/.pi/agent/litefuse.log
# Expected: "extension loaded, 1 target(s): https://litefuse.cloud"
# + "turn complete session=... turn=1 api_calls=1 tool_calls=0"Open https://litefuse.cloud — the latest trace is named Pi Agent — Turn 1 with the structure shown above.
An already-running interactive Pi session loads extensions at startup: run /reload in Pi (or start a new session) to pick the extension up.
Environment variables
LITEFUSE_* variables take precedence; same-named LANGFUSE_* variables are accepted as an ecosystem-compatible fallback.
| Variable | Required | Description |
|---|---|---|
LITEFUSE_PUBLIC_KEY | Yes* | Litefuse project public key (pk-lf-...). |
LITEFUSE_SECRET_KEY | Yes* | Litefuse project secret key (sk-lf-...). |
LITEFUSE_BASE_URL | No | Defaults to https://litefuse.cloud. Alias: LITEFUSE_HOST. |
LITEFUSE_TRACING_ENVIRONMENT | No | Trace environment for the env-configured target. Default production. |
LITEFUSE_USER_ID | No | Overrides the trace user_id. Falls back to the OS username. |
LITEFUSE_EXTRA_TARGETS | No | JSON array of additional targets (same shape as the targets file). |
PI_LITEFUSE_DEBUG | No | Set to "true" for verbose logging in ~/.pi/agent/litefuse.log. |
PI_LITEFUSE_MAX_CHARS | No | Truncation threshold (in characters) for span inputs/outputs. Default 1000000 (~1MB of text). |
LITEFUSE_TRACEPARENT | — | Set automatically by the extension for subagent child processes (W3C format). Don’t set it yourself. |
* Required only when not using ~/.pi/agent/litefuse-targets.json. Credentials in the targets file work without any environment variables.
Trace metadata reference
All Pi-specific fields are flat, top-level metadata keys with the agent_ prefix — one uniform key set across every Litefuse agent integration, so the same filters and dashboards work everywhere. Litefuse-standard fields (sessionId, userId, tags) stay at the trace level via OTel attributes. Sparse: fields without a value are absent entirely, never null.
Trace level:
agent_turn_number,agent_session_id,agent_cwd,agent_model,agent_provideragent_api_calls,agent_tool_calls,agent_steps,agent_message_count,agent_duration_ms— turn totalsagent_context_usage—{tokens, contextWindow, percent}at turn endagent_thinking_level— when the user changed itagent_image_blocks,agent_prompt_truncated,agent_prompt_orig_len— when applicable
Generation observations:
agent_step_index— the#Nin the nameagent_provider,agent_api,agent_stop_reasonagent_api_duration_ms,agent_time_to_first_token_msagent_http_status,agent_request_id,agent_retry_after— from the provider HTTP responseagent_tool_call_count,agent_thinking_charsagent_input_truncated,agent_output_truncated,agent_output_orig_len— only when truncated
Tool observations:
agent_tool_name,agent_tool_call_idagent_step_index— the tool’s own#Nagent_plan_step— theagent_step_indexof the plan generation that requested this toolagent_duration_ms,agent_is_erroragent_details— Pi’s structured tool result details, embedded as an object when ≤ 2 KB; otherwise dropped withagent_details_omitted_len(subagent details embed the whole child history, which the child subtree already captures)
Subagent container:
agent_subagent: trueplus the same per-run totals as the trace level (agent_api_calls,agent_tool_calls,agent_steps,agent_duration_ms)
How it works
The extension subscribes to Pi’s in-process extension events:
| Event | Used for |
|---|---|
session_start | session id; resume turn numbering from existing user messages |
before_agent_start | capture the user prompt |
agent_start | open the turn: new traceId, root span ids, step counter |
before_provider_request | generation start time, model, request messages, sampling params |
message_update | first streamed token → completion_start_time (TTFT) |
after_provider_response | HTTP status, request id, retry-after headers |
message_end | emit the generation span: named by content, with usage + cost |
tool_execution_start / tool_execution_end | tool spans; subagent traceparent export |
session_compact | context-compaction event observation |
agent_end | emit the root span: trace output, turn totals, context usage; flush |
session_shutdown | defensively close aborted turns; final flush |
Every completed span is POSTed immediately as OTLP/HTTP JSON to <baseUrl>/api/public/otel/v1/traces with Basic auth — fire-and-forget with a timeout, flushed at turn end. Each span is sent exactly once; in-flight steps become visible when they complete.
For subagents, Pi’s subagent extension spawns child pi processes. The parent’s Litefuse extension exports LITEFUSE_TRACEPARENT while the subagent tool runs; the child’s extension (same file, loaded by the child process) detects it, suppresses its own trace headers, and parents its container span under the subagent tool span. Nested delegation recurses the same way. One known transient: a child’s steps can appear in the live view before their container arrives (the container is only sent when the child finishes) — the tree is complete and correctly nested once the turn ends.
Troubleshooting
No traces appear in Litefuse. Check the extension log:
tail -20 ~/.pi/agent/litefuse.logAn empty log means the extension isn’t loading — confirm the file is at ~/.pi/agent/extensions/litefuse/index.ts and that you didn’t start Pi with --no-extensions. No extension loaded line but the file exists? The extension found no credentials: check ~/.pi/agent/litefuse-targets.json or the LITEFUSE_* env vars.
Traces stop after editing credentials. Interactive Pi sessions load extensions at startup — run /reload in Pi or start a new session.
Tool calls show but the subagent subtree is missing. The child process couldn’t reach Litefuse or wasn’t running this extension. Confirm the subagent spawns plain pi (the default) and check ~/.pi/agent/litefuse.log for child-process lines (session=ephemeral).
totalCost is 0. Your Litefuse project has no price entry for the model (Pi custom model ids won’t match the default price table). Add one under Settings → Models in the Litefuse UI, or rely on Pi’s own cost data which the extension forwards as cost_details when the model is in Pi’s registry.
Enable debug logging:
PI_LITEFUSE_DEBUG=true pi
# every send / skip / error now lands in ~/.pi/agent/litefuse.logLocal + cloud double-write: add a second entry to litefuse-targets.json (e.g. a local Litefuse instance with "environment": "test"). Unreachable targets are skipped silently and never block Pi.
Resources
- Pi website
- Pi extensions documentation
- Litefuse Cloud
- Extension source:
index.ts