Export traces
Every call's spans in Datadog, Grafana, Langfuse, Honeycomb, Cekura or your own OpenTelemetry collector.
Every spoken call's spans can go to an OpenTelemetry collector of your own: the model's requests, speech in and out, every tool the agent ran, one trace per call. You name the collector once, per org — from the terminal below, or in the console at Settings ▸ Telemetry, where the same card shows where traces go and stops them; from the next call on, the spans arrive there over OTLP. The headers are a credential: sent once, kept sealed, read back by name alone.
It is a copy, and it is off until you ask for it. The console's call page, its Observability screen, the judges and the monitors read the call's log, which Pinecall writes for every call whether or not a collector of yours exists; nothing on Pinecall's side changes when you name one or clear it.
Which calls are traced#
The spans are written by the worker that holds a call's audio, so a call is traced when it is spoken:
| the call | traced |
|---|---|
| a phone call, inbound or outbound | yes |
| a voice call from the browser (the widget, the console) | yes |
pinecall simulate --voice, a spoken eval |
yes |
a chat, pinecall chat, pinecall simulate without --voice |
no — it has no worker; its log is written as for any call |
Both worlds export to the org's one collector; deployment.environment.name says which world a
trace came from.
What arrives#
| span | attributes |
|---|---|
| the session, root of the trace | on every span: pinecall.org, pinecall.env, pinecall.agent, pinecall.call, pinecall.holder; and the OpenTelemetry conventions any tool reads — deployment.environment.name (sandbox or production), session.id (the call), user.id (the contact's id when the call has one, never the number) |
llm_node, one per model request |
gen_ai.provider.name, gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.response.time_to_first_chunk |
tts_node, one per sentence spoken |
the time to the first byte of audio |
function_tool, one per tool the agent ran |
the tool's name, its duration, its arguments and result when PII is allowed |
The attributes follow the OpenTelemetry GenAI conventions, which Datadog, Grafana, Langfuse,
Honeycomb and Cekura all read. Without --pii the words — what was said, what a tool got — are
stripped before export; the timings, tokens and names stay.
Langfuse#
Langfuse takes OpenTelemetry natively, so nothing of Langfuse's runs in Pinecall: the same spans
go to the project's OTLP endpoint. There each call is a trace, its environment is the world
(sandbox or production, from deployment.environment.name), its session is the call and its
user is the contact; every model request is a generation with its model, tokens and time to first
token, and with --pii each step's input and output.
In the console, Settings ▸ Telemetry ▸ Langfuse takes the region and the project's two keys
(Langfuse ▸ project ▸ Settings ▸ API keys) and builds the rest. In a terminal, the two headers are
read off stdin one line each — Authorization as Basic auth of the two keys, and
x-langfuse-ingestion-version: 4, without which Langfuse shows a trace up to ten minutes late:
printf 'Basic %s\n4\n' "$(printf %s "$LANGFUSE_PUBLIC_KEY:$LANGFUSE_SECRET_KEY" | base64 | tr -d '\n')" \
| pinecall telemetry set https://cloud.langfuse.com/api/public/otel/v1/traces \
--header Authorization --header x-langfuse-ingestion-version --pii
traces go to https://cloud.langfuse.com/api/public/otel/v1/traces · headers Authorization, x-langfuse-ingestion-version · with what was said
The US region is us.cloud.langfuse.com, Japan jp.cloud.langfuse.com; a self-hosted Langfuse
(v3.22 or later) is its own address with the same path.
Name the collector#
The URL goes on the command line; each header's value is read from stdin, one per --header,
never from a flag:
printf %s "$DD_API_KEY" | pinecall telemetry set https://otlp.datadoghq.eu/v1/traces --header dd-api-key
printf %s "$GRAFANA_OTLP_AUTH" | pinecall telemetry set https://otlp-gateway-prod-eu-west-2.grafana.net/otlp/v1/traces --header Authorization
pinecall telemetry set https://otel.example.com/v1/traces
traces go to https://otlp.datadoghq.eu/v1/traces · headers dd-api-key
The gateway keeps the headers sealed and never reads them back.
Read it back#
pinecall telemetry
traces go to https://otlp.datadoghq.eu/v1/traces · headers dd-api-key · words stripped
The URL and the headers' names; never a value.
Make a call, find the trace#
A spoken call, since a written one is not traced — a persona calling the agent for two turns:
pinecall simulate --persona <name> --voice --turns 2
In your tool, search traces by pinecall.call (or session.id) with the call id the run prints,
or by pinecall.agent. A trace takes a few seconds to show up after the call ends. The trace is the call: one span per
model request under the session's, and its trace id is the call's: for a call id of the form
call_<32 hex digits> the trace id is those digits; for any other (a phone call's carrier id) it
is the first 32 hex digits of the SHA-256 of the id. The console's call page shows it in its
Trace card, with a copy and the collector it went to, once the org exports.
Stop it#
pinecall telemetry clear
The calls' logs stay on Pinecall either way: the console's call page and pinecall sessions read
them whether or not a collector of yours exists.