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

DataCaptured asNotes
User prompttrace inputimage block count in metadata
Each LLM API callgeneration observationplan (n tools) #N / response / think #N, named after what the model did
Thinking / text / toolCall blocksgeneration outputblock structure preserved, including reasoning text
Time to first tokencompletion_start_time on generationfrom the first streamed delta — powers Litefuse’s TTFT metric
Sampling parametersmodel_parameters on generationtemperature / 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 generationAnthropic-style keys, so Litefuse cost mapping works
Model name + provider costmodel + cost_details on generationPi’s own cost data when available, otherwise Litefuse computes from the model price table
Tool executionstool observationtool: bash (grep) #N, tool: read (index.ts) #N — key info in the name, full args in input
Subagents (single / parallel / chain)subtreetool (n subagents) #Nsubagent container → the child’s own plan/tool/response steps; child cost rolls up into the parent trace
Tool errorstool observation, level=ERRORwith status_message preview
LLM errors / abortsgeneration level=ERROR / WARNINGfrom stopReason, with HTTP status + request id in metadata
Context compactionevent observationexplains sudden input-token drops on the next call
Context window usagetrace metadata agent_context_usagetokens / window / percent at turn end
Session groupingtrace session_idPi session UUID; resumed sessions keep counting turns
User identitytrace user_id$LITEFUSE_USER_ID, falls back to the OS username
Tagstrace tagspi-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’s model attribute, so dashboards survive model swaps. Names are decided once, after the message completes; they are never renamed mid-flight.
  • response IS 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: #N is shared by generations and tools chronologically. Tool metadata agent_plan_step points at the agent_step_index of the plan that requested it — tool.agent_plan_step == generation.agent_step_index is the join. Each subagent container restarts at #1; the tree expresses the hierarchy.
  • Subagent propagation via env: when a subagent tool starts, the extension exports LITEFUSE_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 with null.
  • Fail-open: any unexpected error is logged to ~/.pi/agent/litefuse.log and 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.ts

The 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.json

Replace 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.

VariableRequiredDescription
LITEFUSE_PUBLIC_KEYYes*Litefuse project public key (pk-lf-...).
LITEFUSE_SECRET_KEYYes*Litefuse project secret key (sk-lf-...).
LITEFUSE_BASE_URLNoDefaults to https://litefuse.cloud. Alias: LITEFUSE_HOST.
LITEFUSE_TRACING_ENVIRONMENTNoTrace environment for the env-configured target. Default production.
LITEFUSE_USER_IDNoOverrides the trace user_id. Falls back to the OS username.
LITEFUSE_EXTRA_TARGETSNoJSON array of additional targets (same shape as the targets file).
PI_LITEFUSE_DEBUGNoSet to "true" for verbose logging in ~/.pi/agent/litefuse.log.
PI_LITEFUSE_MAX_CHARSNoTruncation threshold (in characters) for span inputs/outputs. Default 1000000 (~1MB of text).
LITEFUSE_TRACEPARENTSet 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_provider
  • agent_api_calls, agent_tool_calls, agent_steps, agent_message_count, agent_duration_ms — turn totals
  • agent_context_usage{tokens, contextWindow, percent} at turn end
  • agent_thinking_level — when the user changed it
  • agent_image_blocks, agent_prompt_truncated, agent_prompt_orig_len — when applicable

Generation observations:

  • agent_step_index — the #N in the name
  • agent_provider, agent_api, agent_stop_reason
  • agent_api_duration_ms, agent_time_to_first_token_ms
  • agent_http_status, agent_request_id, agent_retry_after — from the provider HTTP response
  • agent_tool_call_count, agent_thinking_chars
  • agent_input_truncated, agent_output_truncated, agent_output_orig_len — only when truncated

Tool observations:

  • agent_tool_name, agent_tool_call_id
  • agent_step_index — the tool’s own #N
  • agent_plan_step — the agent_step_index of the plan generation that requested this tool
  • agent_duration_ms, agent_is_error
  • agent_details — Pi’s structured tool result details, embedded as an object when ≤ 2 KB; otherwise dropped with agent_details_omitted_len (subagent details embed the whole child history, which the child subtree already captures)

Subagent container:

  • agent_subagent: true plus 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:

EventUsed for
session_startsession id; resume turn numbering from existing user messages
before_agent_startcapture the user prompt
agent_startopen the turn: new traceId, root span ids, step counter
before_provider_requestgeneration start time, model, request messages, sampling params
message_updatefirst streamed token → completion_start_time (TTFT)
after_provider_responseHTTP status, request id, retry-after headers
message_endemit the generation span: named by content, with usage + cost
tool_execution_start / tool_execution_endtool spans; subagent traceparent export
session_compactcontext-compaction event observation
agent_endemit the root span: trace output, turn totals, context usage; flush
session_shutdowndefensively 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.log

An 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.log

Local + 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

Was this page helpful?