@hachibox/genai-otel0.1.4
agent-harness observability: capture LLM/agent activity as OpenTelemetry spans with canonical gen_ai.* attributes and export over OTLP/HTTP JSON
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
@hachibox/genai-otel
Capture agent-harness activity as OpenTelemetry spans with canonical gen_ai.*
attributes and export them over OTLP/HTTP JSON. One trace is one agentic turn:
everything the agent does between one user message and the next. A multi-turn
session is a sequence of traces sharing a session.id attribute, never a
single trace.
What it captures
For every user message (one OTel trace, session.id = pi session uuid on every
span):
invoke_agent {pi} turn root: user prompt, final assistant text
├── chat {model} per LLM cycle: model, token+cost usage, input
│ │ system prompt, output text and thinking blocks
│ │ (as {type:"reasoning"} parts), reasoning token
│ │ count, response id/model
│ └── execute_tool {tool} per tool call: name, call id, arguments, result
└── ...one chat/tool subtree per provider request of the turn
Multiple LLM cycles inside one user message (tool-call loops, retries) stay inside the same trace: the trace boundary is the user message, never the session.
Install
Install as a pi package from npm:
pi install npm:@hachibox/genai-otel
or, project-local (written to .pi/settings.json):
pi install -l npm:@hachibox/genai-otel
or for a single run without installing:
pi -e npm:@hachibox/genai-otel
Verify the package is loaded with pi list; uninstall with
pi remove npm:@hachibox/genai-otel. npm-sourced pi packages have their
dependencies installed automatically; this package has no runtime npm
dependencies beyond the Node builtins.
What gets installed
The npm tarball contains only src/, README.md, and LICENSE.
Configuration
Precedence: environment variables override the credentials file.
| Source | Key | Notes |
|---|---|---|
| credentials file | ~/.pi/agent/genai-otel.json |
{"tracesUrl": "...", "apiKey": "..."} |
| env | GENAI_OTEL_TRACES_URL |
export URL, e.g. https://host/v1/traces |
| env | GENAI_OTEL_API_KEY |
sent as X-API-Key |
| env | GENAI_OTEL_HEADERS |
JSON object of extra headers (e.g. basic auth) |
| env | OTEL_SERVICE_NAME |
sets the service.name resource attribute |
| env | OTEL_RESOURCE_ATTRIBUTES |
comma-separated key=value resource attributes (URL-decoded per the OTel spec) |
| env | GENAI_OTEL_TRACING_ENABLED=false |
disables export only |
| env | PI_TRACING_ENABLED=false |
pi-scoped kill switch |
| env | PI_TRACE_CAPTURE_DIR |
debug: write raw pi events as JSONL (see below) |
The credentials file is optional: without it (and without
GENAI_OTEL_TRACES_URL) tracing stays unconfigured rather than erroring. If
the file exists but is unreadable or malformed, startup fails loudly instead
of silently dropping capture. Set PI_CREDENTIALS_FILE to use a different
credentials-file path.
Resource attributes
The export's resource carries service.name and any extra attributes the
OTel-standard environment variables provide, following OTel precedence
(OTEL_RESOURCE_ATTRIBUTES pairs first, OTEL_SERVICE_NAME overrides a
service.name pair):
OTEL_SERVICE_NAME=my-service \
OTEL_RESOURCE_ATTRIBUTES='workflow.phase=implement,deployment.environment.name=prod' \
pi
Downstream consumers read these from resourceSpans[].resource.attributes
(e.g. a collector transform keyed on resource.attributes["workflow.phase"],
or Langfuse's service identity). Malformed OTEL_RESOURCE_ATTRIBUTES input
(non-key=value pairs, invalid percent-encoding) fails loudly at startup
instead of exporting a mislabeled resource.
The emitter is sink-agnostic: any endpoint that accepts OTLP/HTTP JSON works (Langfuse, an S3/R2 OTLP exporter, Datadog, a self-hosted collector, …). Each completed turn is exported as soon as it ends rather than buffered until shutdown, so a long run cannot lose its traces to an ungraceful exit. Export transport failures are fail-open: they warn once and drop the batch, never blocking pi.
Enable and disable tracing
For a single run:
PI_TRACING_ENABLED=false pi
For a shell session:
export PI_TRACING_ENABLED=false # off until `unset PI_TRACING_ENABLED`
The two switches differ:
PI_TRACING_ENABLED=falseshort-circuits before the credentials file is read. Tracing is off, but debug capture (below) stays available viaPI_TRACE_CAPTURE_DIR.GENAI_OTEL_TRACING_ENABLED=falseonly stops the export; everything else behaves the same.
To disable entirely, remove the package (pi remove npm:@hachibox/genai-otel) or delete the credentials file.
Troubleshooting: no traces appearing
Kill switch set — check
PI_TRACING_ENABLEDandGENAI_OTEL_TRACING_ENABLEDin the current shell and in shell profiles (~/.bashrc,~/.zshrc, …); an exportedfalsewins over the credentials file.Plugin not loaded —
pi listmust show@hachibox/genai-otel.No sink URL — with neither
GENAI_OTEL_TRACES_URLnor atracesUrlin the credentials file, export is disabled, not errored.Credentials file malformed — a trailing comma or wrong value type makes startup fail loudly. Validate with:
node -e "JSON.parse(require('fs').readFileSync(require('os').homedir() + '/.pi/agent/genai-otel.json', 'utf8'))"Remember env vars override the file: a stale
GENAI_OTEL_TRACES_URLexport can shadow a corrected file.Sink rejects or unreachable — export failures are fail-open (warn once, drop the batch). Look for the warning in pi's stderr and verify the endpoint URL, the
X-API-Keyvalue, and anyGENAI_OTEL_HEADERS(e.g. proxy auth).Session ended ungracefully — traces flush per completed turn, so only the in-flight turn at kill time is lost; anything earlier should exist.
Debug capture (fixtures)
Set PI_TRACE_CAPTURE_DIR to have the plugin write the raw pi hook payloads it
consumes as JSONL, one file per process. This is a development tool for keeping
the committed replay fixtures honest when pi's SDK shapes change. It is
independent of the emitter, so it works with PI_TRACING_ENABLED=false and
needs no export endpoint:
PI_TRACE_CAPTURE_DIR=/tmp/pi-capture PI_TRACING_ENABLED=false \
pi -e /path/to/public/plugins/genai-otel/src/pi/index.ts ...
The captured files contain raw prompts, tool arguments, and tool results. They are git-ignored and must be anonymized before committing. Regenerate the replay fixture after a pi upgrade:
pnpm exec vite-node public/plugins/genai-otel/scripts/anonymize-capture.ts \
/tmp/pi-capture public/plugins/genai-otel/spec/pi/fixtures/pi-session.events.json
The plugin and server specs replay that fixture through the real adapter and
OtlpEmitter (no pi process, model, or network). When a pi binary is on
PATH, the replay spec compares the fixture's recorded piVersion to
pi --version and fails on mismatch, so a stale fixture cannot pass silently.
Attribution
The usage-validation and attribute-clamping techniques in
@hachibox/genai-otel are modeled on prior art (pi-otel, Apache-2.0; langfuse
codex plugin, MIT). This package is an independent implementation; no upstream
code is included.