Setup Coval Tracing
Set up tracing in the customer's agent with the smallest additive change that produces a real Coval trace, then verify that trace against the agent's actual Coval connection path.
Operate from the customer's agent side. Do not assume access to Coval internal
backend, frontend, docs, wizard, research, or example source repositories, and
do not ask the customer for them. Use only the customer's repo, public Coval
docs, public SDK examples, the Coval CLI/API, and fetched public OpenAPI specs.
Code edits belong in the customer's agent/service repo. Coval-side changes must
be limited to documented configuration through the Coval CLI, public API, or
dashboard, and should be explained before mutation.
Preflight
- Confirm the agent repo or service scope. In a monorepo, do not instrument every service unless the user explicitly asks.
- Check Coval authentication without reading secret files:
coval whoami
coval agents list --format json
If the CLI is unavailable, use COVAL_API_KEY from the user's shell environment and direct API calls. Never print or store the raw key. When listing agents through the API, redact metadata before showing output because some provider integrations store model API keys there.
- Fetch current API shape before writing API integrations:
curl -fsS https://api.coval.dev/v1/openapi
For broader docs discovery, fetch https://docs.coval.dev/llms.txt and then
the specific public docs page needed for the task.
- Stay inside the customer-owned code surface. Do not reference or require
coval-ai/backend, coval-ai/frontend, coval-ai/docs,
coval-ai/wizard, internal engineering docs, or private Coval repos as
local source code. Public docs, public SDK examples, and installed skill
reference files are the support material available to the customer-side
agent.
- Read the shared references before editing:
../references/coval-tracing-reference.md
../references/agent-type-routing.md
../references/span-schema.md
- if the agent is Vapi-based,
../references/vapi-artifact-tracing.md
Phase 1: Read-Only Analysis
Do not edit files until this phase is complete.
Identify:
- language and package manager
- app start command and one representative local invocation
- agent framework or provider stack: Pipecat, LiveKit, Vapi, Twilio, WebSocket, direct OpenAI/Anthropic, custom loop, etc.
- nearby same-stack agent implementations (if available in the same workspace)
that already emit richer traces; use them as additive instrumentation
references instead of reinventing span structure
- Coval agent type and connection path: SIP inbound, PSTN inbound, outbound voice, WebSocket voice/chat, or conversation monitoring
- where a per-call Coval ID can enter the agent process
- existing telemetry owner: OpenTelemetry, Sentry, Datadog, Honeycomb, Langfuse, Arize, LangSmith, Traceloop, or custom OTLP exporter
- short-lived process behavior and shutdown/flush path
- customer-owned files that need edits, and any Coval configuration changes
that must be done through CLI/API/dashboard rather than code changes
Return a concise analysis summary. If the target service or repo scope is
ambiguous, ask before editing. Do not ask the customer to choose a correlation
route from a menu. Customers usually cannot know whether SIP headers, pre-call
webhooks, or trigger payloads are supported from the outside; infer the best
route from the discovered agent configuration and the decision rules below.
Phase 2: Pick The Correlation Path
Use exactly one target header per export:
- simulations:
X-Simulation-Id with the simulation output ID
- conversation monitoring:
X-Conversation-Id with the conversation_id returned by POST /v1/conversations:submit
Pick the route yourself when the repo and Coval agent configuration make one
route clearly safer. Do not present an open-ended "which route should I use?"
question. Use this decision order:
- If the current connection path already delivers a Coval ID into the process,
use that path. Examples: SIP participant attributes, outbound trigger
payload, WebSocket initialization payload, or a monitoring
conversation_id.
- If the current Coval agent record or fetched OpenAPI confirms an existing
pre-call/registration webhook field and the service already exposes a
suitable endpoint, configure or reuse that webhook.
- For inbound PSTN phone agents with no verified SIP headers and no confirmed
Coval pre-call webhook wiring, default to a self-contained registration
endpoint plus a smoke launcher that registers
simulation_output_id right
before the call. This is the safest first validation because it does not
guess at Coval agent metadata shape or mutate customer agent config. After it
works, state the production upgrade: wire the same endpoint into the verified
Coval pre-call webhook field when available, or provision SIP if the customer
needs Coval-managed per-call injection.
- Ask a customer question only when every viable route requires a policy or
deployment decision you cannot infer, such as whether you may expose a new
public webhook, update the Coval agent configuration, or provision a SIP
route. In that case, give one recommendation first, explain why, and ask for
the smallest approval needed.
Coval-side config changes that are purely additive to a documented field
that is currently empty (e.g., setting initialization_json from "{}" to the
documented {"simulation_id":"{{simulation_id}}"} placeholder, or filling an
empty pre_call_webhook_url) do not need a consent prompt — the customer's
"get traces working" intent implies it, and the change is trivially reversible
via the same PATCH. Show the before/after value in the handoff but proceed
without asking. Always ask before changes that delete config, downgrade auth,
overwrite a non-empty field, or modify shared resources like phone numbers,
SIP routes, test sets, or webhook secrets.
Choose the route from ../references/agent-type-routing.md:
- SIP inbound voice: extract
X-Coval-Simulation-Id from SIP headers or framework participant attributes.
- PSTN inbound phone: do not expect SIP headers. Add or configure
pre_call_webhook_url / registration-webhook correlation, or guide the customer to provision a SIP address.
- Outbound voice: carry
simulation_output_id in the trigger payload and read it in the trigger handler.
- WebSocket voice/chat: carry the simulation output ID in the initial setup payload or initialization JSON.
- Conversation monitoring: buffer spans during the call, submit the conversation at call end, then export buffered spans with
X-Conversation-Id.
For direct WebSocket agents, prefer configuring Coval metadata with an explicit initialization payload such as:
{"simulation_id": "{{simulation_id}}"}
Then treat the first non-audio frame as setup metadata, extract simulation_id or
simulation_output_id, and only export with X-Simulation-Id after that ID is
present. Current production examples use {{simulation_id}}; if docs mention
{{simulation_output_id}}, verify current agent metadata behavior before
hardcoding either name.
Phase 3: Implement Additive Instrumentation
Prefer an existing telemetry owner. If the app already has a TracerProvider,
add a Coval exporter/processor to it instead of replacing the provider. If
there is no telemetry setup, create one central module such as
coval_tracing.py, covalTracing.ts, or coval_tracing.go and keep the agent
entry point focused on business/webhook routing. Do not leave the final
implementation as a large inline tracing block inside server.py, app.ts, or
the main webhook handler unless the repo is a tiny single-file prototype.
Implementation requirements:
- Endpoint:
https://api.coval.dev/v1/traces
- Auth header:
x-api-key or X-API-Key from an environment variable, never a literal secret
- Timeout: 30 seconds
- Resource: set
service.name to the agent or service name. Also add stable
customer-visible resource attributes when known: service.namespace,
agent.name, agent.provider, coval.agent_type, and
coval.correlation.method.
- Export one target header only:
X-Simulation-Id or X-Conversation-Id
- Buffer spans only when the Coval ID is not yet available; bound the buffer
- Use the standard OpenTelemetry SDK where available. For call flows where the
Coval target ID arrives after spans start, use an SDK-backed buffer such as
InMemorySpanExporter, then export the finished spans with an
OTLPSpanExporter after the ID is known. Avoid hand-rolled OTLP JSON
exporters unless the language/runtime has no usable SDK.
- Use
BatchSpanProcessor or equivalent for high-volume agents and keep batches comfortably below 3-4 MB
- Flush/shutdown tracing before short-lived processes exit
- Retry only failed batches; Coval stores spans append-only and does not deduplicate successful retries
- Update deployment packaging. Dockerfiles, serverless bundles, Pipecat Cloud
packages, and Fly/Render/Heroku deploys must include any new tracing helper
module and dependency files.
- Emit the canonical span names from
../references/span-schema.md first:
llm, tts, stt, stt.provider.<name>, vad, llm_tool_call,
turn, conversation, pipeline, and transport. These names drive
semantic UI labels, colors, and built-in trace metrics.
- When adding tool or workflow spans, include metric-ready numeric attributes
from the first implementation:
tool.latency_ms, numeric tool.error,
numeric tool.dependency_unavailable, tool.call.count,
tool.failure.count, numeric workflow.completed, numeric
workflow.dependency_blocked, and numeric workflow.fallback_used when
available. These keep configure-trace-metrics from having to settle for
proof-only metrics.
- Add one vertical-specific workflow span or root attribute for the customer's
most important failure mode during the first pass. Examples:
roadside.dispatch.latency_ms, reservation.date.changed,
identity.verification.completed, payment_plan.blocked, or
handoff.required. Generic tool metrics are not enough when the repo clearly
exposes a business-critical path.
- Emit OTel span events on the conversation root for moment-in-time business
milestones such as
simulation_id_received, dispatch_called,
delay_acknowledged, callback_offered, fnol_started, fnol_completed,
booking_attempted, or escalation_triggered. Use events for timeline dots;
use spans for work with duration.
- For webhook-style voice agents, do not rely only on a final end-of-call event
if tool-call or turn webhooks already have the Coval target ID. Export the
per-event spans when the target ID is known, or buffer them until it is known,
then flush once. Avoid replaying spans after a successful export because Coval
trace ingest is append-only.
- For Vapi-hosted PSTN agents, parse
end-of-call-report.artifact.messages
after basic tool-call tracing works. Use Vapi message windows for turn
spans, but do not fabricate provider latency. If you emit stt, llm, or
tts spans from artifact messages, mark them as metadata markers with
trace.source=vapi_artifact, trace.timing=metadata_marker, and a
trace.duration_note; alternatively use marker-suffixed span names. Do not
create LLM/STT/TTS latency metrics from these marker spans.
For Python voice agents, an existing generated coval_tracing.py helper in the
customer repo is an acceptable baseline, but improve it for the discovered
connection path and existing telemetry.
Phase 4: Minimum Span Coverage
The first working trace should contain:
- a root
conversation or equivalent session span when the framework gives a call boundary
- at least one
stt, llm, tts, or llm_tool_call span that matches the actual agent path
- correct parent/child relationships where the framework exposes them
service.name resource metadata
- Coval-compatible target ID routing
If the app cannot expose STT/TTS/LLM internals yet, ship the minimum useful trace first, then use optimize-trace-observability for enrichment.
Anti-pattern: per-chunk or per-frame transport spans. Voice/realtime agents
stream audio in many small chunks (often 20-100 ms). Emitting one OTel span per
chunk produces hundreds of micro-spans that visually drown the trace viewer and
collapse the real turn/tts/llm spans into invisible slivers — the trace
will look empty even when it is not. Aggregate per-chunk activity into
counter attributes on the parent stream span instead:
audio.chunks_sent (count) and audio.chunk_target_ms (configured cadence)
on the tts span
audio.chunks_received on the turn or stt span
audio.payload_bytes, audio.duration_s already capture the totals
The same rule applies to per-frame transport.recv_audio, per-event WebSocket
pings, and per-token streaming spans. One span per high-level operation; per-
chunk detail goes into numeric attributes.
Phase 5: Verification
Run local checks first:
python -m compileall .
npm test
npm run typecheck
go test ./...
Use the checks that match the repo; do not invent package-manager commands.
Then start the Coval validation through CLI/API and do useful work while it
runs. Coval simulations and monitoring conversations are asynchronous; do not
sit idle after launching one.
- Use the discovered Coval CLI/API path to launch one real test through the
same Coval agent type the customer uses. Prefer CLI commands when available;
otherwise use the public API after fetching OpenAPI. Capture the run ID,
simulation output ID, conversation ID, dashboard URL, or polling endpoint
returned by the launch.
- Start a bounded poll loop through the CLI/API. Record the command or endpoint
used, poll interval, and timeout. Do not print API keys or raw provider
metadata from Coval agent responses.
For simulations, do not wait only for
GET /v1/runs/{run_id} to expose
results.output_ids; that can lag until completion, which is too late for
PSTN/webhook registration. Also poll:
GET /v1/simulations?filter=run_id%3D%22<RUN_ID>%22, register the first
returned simulation_id / simulation output ID immediately, then continue
watching the run.
- While the run is pending, continue with non-blocking trace improvement:
- add safe span enrichment visible from the code path, such as stable
conversation, turn, stt, llm, tts, llm_tool_call,
transport, or provider spans
- add bounded high-value attributes such as
metrics.ttfb, token counts,
finish reasons, tool names, safe tool argument summaries, status, and
errors
- add customer-signal numeric attributes that can become metrics, such as
tool.error, tool.latency_ms, tool.call.count,
workflow.dependency_blocked, workflow.completed, and
workflow.fallback_used
- improve flush/shutdown, buffering, batch size, retry, or deployment
packaging issues found during implementation
- prepare custom trace metric candidates from expected spans and any
historical Coval traces already available; treat generic duration or
span-count metrics as diagnostic proof unless they answer a customer
operating question
- prepare trace-aware LLM judge metric candidates with
include_traces=true
when trace context is needed to grade ordering, tool-output grounding,
verification before tool use, or recovery from tool errors
- Create custom trace metrics during the wait only when real trace data already
proves the span name and metric attribute exist, either from historical
traced runs or from the in-flight validation once spans appear in Trace
Search. If this validation is the first trace for the agent, stage the metric
definitions while waiting and create them immediately after the run produces
confirmed spans.
Create trace-aware LLM judge metrics before the follow-up validation run when
the Metrics OpenAPI supports
include_traces and their prompts depend on
trace context that the first trace already proved exists.
- When the run finishes, confirm the agent export returned 200 or that the
exporter logged a successful accepted batch. A 404 from the standalone test
script only proves auth/connectivity, not lifecycle wiring.
- Open the Coval run or conversation result and verify the OTel Traces card or
Trace Search result appears.
- Inspect the trace for expected spans and attributes. If it is missing, sparse,
duplicated, or attached to the wrong result, stop further metric creation and
apply
debug-traces before continuing.
- Trace density self-check. Before declaring success, count spans by name.
If a single span name is more than ~70% of total spans and those spans are
each shorter than ~50 ms, that name is almost certainly per-chunk/per-frame
noise. Collapse it into a counter attribute on the parent span and redeploy
before opening the result for the customer — the trace viewer will look
empty otherwise. Do not rely on the customer to flag visual noise.
- Verify correlation activation from the exporter, not from pre-existing
agent counters. Pre-existing counters like a
non_audio_messages or
non_setup_frames tally on the agent often increment after the
setup/init branch returns, so they read 0 even when correlation worked.
The authoritative signal that the simulation ID reached the exporter is
the OTel logger line (activated OTLP export simulation_id=<id>) or the
batch processor's accepted-batch log. Check those first; only chase
counters if the exporter never activated.
- After the initial trace is confirmed, finish any prepared metric creation
and run one follow-up calculation/preview/attached-run check through the
CLI/API when the public API supports it.
- If you changed metric definitions or attached new metrics, run a post-change
validation and poll until the run status is terminal (
COMPLETED, FAILED,
or CANCELLED) and each newly attached trace metric has a terminal output
on the simulation (COMPLETED or FAILED). Do not stop while outputs are
still IN QUEUE / IN PROGRESS, even when progress.completed_test_cases
already equals total_test_cases.
- When prior runs show
FAILED trace metrics, verify against the first run
launched after your instrumentation and metric changes instead of treating
older runs as current-state proof. Metric failures on older runs may reflect
stale definitions that no longer match the latest instrumentation.
For WebSocket agents, make the smoke interaction long enough to trigger the
agent's response threshold. A client that sends too little audio, or an agent
that waits for more silence/audio than Coval sends, can make tracing look broken
even when the exporter is wired correctly. If a tts span is marked ERROR
with a WebSocket disconnect after partial audio, shorten the canned response or
stop streaming when the client closes.
Optional connectivity-only check:
python skills/traces/setup-tracing/scripts/send-test-span.py \
--api-key "$COVAL_API_KEY" \
--simulation-id coval-tracing-test
A 404 with Simulation output not found means the key reached Coval and auth worked, but it is not proof that the agent call lifecycle is wired.
Handoff
End with:
- files changed and why
- exact correlation path used
- how the customer sets required env vars
- commands run, including the Coval launch and polling commands/API endpoints
- Coval trace URL or simulation/conversation ID used for proof
- direct Coval URLs for:
- runs list (
https://app.coval.dev/<org-slug>/runs?sort=createdAt%3Adescending)
- run details (
https://app.coval.dev/<org-slug>/runs/<run-id>)
- run result (
https://app.coval.dev/<org-slug>/runs/<run-id>/results/<simulation-output-id>)
- trace viewer (
https://app.coval.dev/<org-slug>/runs/<run-id>/results/<simulation-output-id>/traces)
- custom trace metrics created, staged, or deferred and why
- any remaining gaps to handle with
optimize-trace-observability, configure-trace-metrics, or debug-traces
1---2name: setup-tracing3description: Configure an AI agent to send OpenTelemetry traces to Coval. Use when a user wants to add Coval tracing, instrument an agent for simulations or conversation monitoring, make traces show up in Coval, handle SIP/PSTN/WebSocket trace correlation, or replace the one-command wizard with a security-reviewable manual setup.4---56# Setup Coval Tracing78Set up tracing in the customer's agent with the smallest additive change that produces a real Coval trace, then verify that trace against the agent's actual Coval connection path.910Operate from the customer's agent side. Do not assume access to Coval internal11backend, frontend, docs, wizard, research, or example source repositories, and12do not ask the customer for them. Use only the customer's repo, public Coval13docs, public SDK examples, the Coval CLI/API, and fetched public OpenAPI specs.14Code edits belong in the customer's agent/service repo. Coval-side changes must15be limited to documented configuration through the Coval CLI, public API, or16dashboard, and should be explained before mutation.1718## Preflight19201. Confirm the agent repo or service scope. In a monorepo, do not instrument every service unless the user explicitly asks.212. Check Coval authentication without reading secret files:22 ```bash23 coval whoami24 coval agents list --format json25 ```26 If the CLI is unavailable, use `COVAL_API_KEY` from the user's shell environment and direct API calls. Never print or store the raw key. When listing agents through the API, redact `metadata` before showing output because some provider integrations store model API keys there.273. Fetch current API shape before writing API integrations:28 ```bash29 curl -fsS https://api.coval.dev/v1/openapi30 ```31 For broader docs discovery, fetch `https://docs.coval.dev/llms.txt` and then32 the specific public docs page needed for the task.334. Stay inside the customer-owned code surface. Do not reference or require34 `coval-ai/backend`, `coval-ai/frontend`, `coval-ai/docs`,35 `coval-ai/wizard`, internal engineering docs, or private Coval repos as36 local source code. Public docs, public SDK examples, and installed skill37 reference files are the support material available to the customer-side38 agent.395. Read the shared references before editing:40 - `../references/coval-tracing-reference.md`41 - `../references/agent-type-routing.md`42 - `../references/span-schema.md`43 - if the agent is Vapi-based, `../references/vapi-artifact-tracing.md`4445## Phase 1: Read-Only Analysis4647Do not edit files until this phase is complete.4849Identify:50- language and package manager51- app start command and one representative local invocation52- agent framework or provider stack: Pipecat, LiveKit, Vapi, Twilio, WebSocket, direct OpenAI/Anthropic, custom loop, etc.53- nearby same-stack agent implementations (if available in the same workspace)54 that already emit richer traces; use them as additive instrumentation55 references instead of reinventing span structure56- Coval agent type and connection path: SIP inbound, PSTN inbound, outbound voice, WebSocket voice/chat, or conversation monitoring57- where a per-call Coval ID can enter the agent process58- existing telemetry owner: OpenTelemetry, Sentry, Datadog, Honeycomb, Langfuse, Arize, LangSmith, Traceloop, or custom OTLP exporter59- short-lived process behavior and shutdown/flush path60- customer-owned files that need edits, and any Coval configuration changes61 that must be done through CLI/API/dashboard rather than code changes6263Return a concise analysis summary. If the target service or repo scope is64ambiguous, ask before editing. Do not ask the customer to choose a correlation65route from a menu. Customers usually cannot know whether SIP headers, pre-call66webhooks, or trigger payloads are supported from the outside; infer the best67route from the discovered agent configuration and the decision rules below.6869## Phase 2: Pick The Correlation Path7071Use exactly one target header per export:72- simulations: `X-Simulation-Id` with the simulation output ID73- conversation monitoring: `X-Conversation-Id` with the `conversation_id` returned by `POST /v1/conversations:submit`7475Pick the route yourself when the repo and Coval agent configuration make one76route clearly safer. Do not present an open-ended "which route should I use?"77question. Use this decision order:78791. If the current connection path already delivers a Coval ID into the process,80 use that path. Examples: SIP participant attributes, outbound trigger81 payload, WebSocket initialization payload, or a monitoring82 `conversation_id`.832. If the current Coval agent record or fetched OpenAPI confirms an existing84 pre-call/registration webhook field and the service already exposes a85 suitable endpoint, configure or reuse that webhook.863. For inbound PSTN phone agents with no verified SIP headers and no confirmed87 Coval pre-call webhook wiring, default to a self-contained registration88 endpoint plus a smoke launcher that registers `simulation_output_id` right89 before the call. This is the safest first validation because it does not90 guess at Coval agent metadata shape or mutate customer agent config. After it91 works, state the production upgrade: wire the same endpoint into the verified92 Coval pre-call webhook field when available, or provision SIP if the customer93 needs Coval-managed per-call injection.944. Ask a customer question only when every viable route requires a policy or95 deployment decision you cannot infer, such as whether you may expose a new96 public webhook, update the Coval agent configuration, or provision a SIP97 route. In that case, give one recommendation first, explain why, and ask for98 the smallest approval needed.99100Coval-side config changes that are **purely additive** to a documented field101that is currently empty (e.g., setting `initialization_json` from `"{}"` to the102documented `{"simulation_id":"{{simulation_id}}"}` placeholder, or filling an103empty `pre_call_webhook_url`) do not need a consent prompt — the customer's104"get traces working" intent implies it, and the change is trivially reversible105via the same PATCH. Show the before/after value in the handoff but proceed106without asking. Always ask before changes that delete config, downgrade auth,107overwrite a non-empty field, or modify shared resources like phone numbers,108SIP routes, test sets, or webhook secrets.109110Choose the route from `../references/agent-type-routing.md`:111- SIP inbound voice: extract `X-Coval-Simulation-Id` from SIP headers or framework participant attributes.112- PSTN inbound phone: do not expect SIP headers. Add or configure `pre_call_webhook_url` / registration-webhook correlation, or guide the customer to provision a SIP address.113- Outbound voice: carry `simulation_output_id` in the trigger payload and read it in the trigger handler.114- WebSocket voice/chat: carry the simulation output ID in the initial setup payload or initialization JSON.115- Conversation monitoring: buffer spans during the call, submit the conversation at call end, then export buffered spans with `X-Conversation-Id`.116117For direct WebSocket agents, prefer configuring Coval metadata with an explicit initialization payload such as:118119```json120{"simulation_id": "{{simulation_id}}"}121```122123Then treat the first non-audio frame as setup metadata, extract `simulation_id` or124`simulation_output_id`, and only export with `X-Simulation-Id` after that ID is125present. Current production examples use `{{simulation_id}}`; if docs mention126`{{simulation_output_id}}`, verify current agent metadata behavior before127hardcoding either name.128129## Phase 3: Implement Additive Instrumentation130131Prefer an existing telemetry owner. If the app already has a `TracerProvider`,132add a Coval exporter/processor to it instead of replacing the provider. If133there is no telemetry setup, create one central module such as134`coval_tracing.py`, `covalTracing.ts`, or `coval_tracing.go` and keep the agent135entry point focused on business/webhook routing. Do not leave the final136implementation as a large inline tracing block inside `server.py`, `app.ts`, or137the main webhook handler unless the repo is a tiny single-file prototype.138139Implementation requirements:140- Endpoint: `https://api.coval.dev/v1/traces`141- Auth header: `x-api-key` or `X-API-Key` from an environment variable, never a literal secret142- Timeout: 30 seconds143- Resource: set `service.name` to the agent or service name. Also add stable144 customer-visible resource attributes when known: `service.namespace`,145 `agent.name`, `agent.provider`, `coval.agent_type`, and146 `coval.correlation.method`.147- Export one target header only: `X-Simulation-Id` or `X-Conversation-Id`148- Buffer spans only when the Coval ID is not yet available; bound the buffer149- Use the standard OpenTelemetry SDK where available. For call flows where the150 Coval target ID arrives after spans start, use an SDK-backed buffer such as151 `InMemorySpanExporter`, then export the finished spans with an152 `OTLPSpanExporter` after the ID is known. Avoid hand-rolled OTLP JSON153 exporters unless the language/runtime has no usable SDK.154- Use `BatchSpanProcessor` or equivalent for high-volume agents and keep batches comfortably below 3-4 MB155- Flush/shutdown tracing before short-lived processes exit156- Retry only failed batches; Coval stores spans append-only and does not deduplicate successful retries157- Update deployment packaging. Dockerfiles, serverless bundles, Pipecat Cloud158 packages, and Fly/Render/Heroku deploys must include any new tracing helper159 module and dependency files.160- Emit the canonical span names from `../references/span-schema.md` first:161 `llm`, `tts`, `stt`, `stt.provider.<name>`, `vad`, `llm_tool_call`,162 `turn`, `conversation`, `pipeline`, and `transport`. These names drive163 semantic UI labels, colors, and built-in trace metrics.164- When adding tool or workflow spans, include metric-ready numeric attributes165 from the first implementation: `tool.latency_ms`, numeric `tool.error`,166 numeric `tool.dependency_unavailable`, `tool.call.count`,167 `tool.failure.count`, numeric `workflow.completed`, numeric168 `workflow.dependency_blocked`, and numeric `workflow.fallback_used` when169 available. These keep `configure-trace-metrics` from having to settle for170 proof-only metrics.171- Add one vertical-specific workflow span or root attribute for the customer's172 most important failure mode during the first pass. Examples:173 `roadside.dispatch.latency_ms`, `reservation.date.changed`,174 `identity.verification.completed`, `payment_plan.blocked`, or175 `handoff.required`. Generic tool metrics are not enough when the repo clearly176 exposes a business-critical path.177- Emit OTel span events on the conversation root for moment-in-time business178 milestones such as `simulation_id_received`, `dispatch_called`,179 `delay_acknowledged`, `callback_offered`, `fnol_started`, `fnol_completed`,180 `booking_attempted`, or `escalation_triggered`. Use events for timeline dots;181 use spans for work with duration.182- For webhook-style voice agents, do not rely only on a final end-of-call event183 if tool-call or turn webhooks already have the Coval target ID. Export the184 per-event spans when the target ID is known, or buffer them until it is known,185 then flush once. Avoid replaying spans after a successful export because Coval186 trace ingest is append-only.187- For Vapi-hosted PSTN agents, parse `end-of-call-report.artifact.messages`188 after basic tool-call tracing works. Use Vapi message windows for `turn`189 spans, but do not fabricate provider latency. If you emit `stt`, `llm`, or190 `tts` spans from artifact messages, mark them as metadata markers with191 `trace.source=vapi_artifact`, `trace.timing=metadata_marker`, and a192 `trace.duration_note`; alternatively use marker-suffixed span names. Do not193 create LLM/STT/TTS latency metrics from these marker spans.194195For Python voice agents, an existing generated `coval_tracing.py` helper in the196customer repo is an acceptable baseline, but improve it for the discovered197connection path and existing telemetry.198199## Phase 4: Minimum Span Coverage200201The first working trace should contain:202- a root `conversation` or equivalent session span when the framework gives a call boundary203- at least one `stt`, `llm`, `tts`, or `llm_tool_call` span that matches the actual agent path204- correct parent/child relationships where the framework exposes them205- `service.name` resource metadata206- Coval-compatible target ID routing207208If the app cannot expose STT/TTS/LLM internals yet, ship the minimum useful trace first, then use `optimize-trace-observability` for enrichment.209210**Anti-pattern: per-chunk or per-frame transport spans.** Voice/realtime agents211stream audio in many small chunks (often 20-100 ms). Emitting one OTel span per212chunk produces hundreds of micro-spans that visually drown the trace viewer and213collapse the real `turn`/`tts`/`llm` spans into invisible slivers — the trace214will *look* empty even when it is not. Aggregate per-chunk activity into215counter attributes on the parent stream span instead:216217- `audio.chunks_sent` (count) and `audio.chunk_target_ms` (configured cadence)218 on the `tts` span219- `audio.chunks_received` on the `turn` or `stt` span220- `audio.payload_bytes`, `audio.duration_s` already capture the totals221222The same rule applies to per-frame `transport.recv_audio`, per-event WebSocket223pings, and per-token streaming spans. One span per high-level operation; per-224chunk detail goes into numeric attributes.225226## Phase 5: Verification227228Run local checks first:229```bash230python -m compileall .231npm test232npm run typecheck233go test ./...234```235Use the checks that match the repo; do not invent package-manager commands.236237Then start the Coval validation through CLI/API and do useful work while it238runs. Coval simulations and monitoring conversations are asynchronous; do not239sit idle after launching one.2402411. Use the discovered Coval CLI/API path to launch one real test through the242 same Coval agent type the customer uses. Prefer CLI commands when available;243 otherwise use the public API after fetching OpenAPI. Capture the run ID,244 simulation output ID, conversation ID, dashboard URL, or polling endpoint245 returned by the launch.2462. Start a bounded poll loop through the CLI/API. Record the command or endpoint247 used, poll interval, and timeout. Do not print API keys or raw provider248 metadata from Coval agent responses.249 For simulations, do not wait only for `GET /v1/runs/{run_id}` to expose250 `results.output_ids`; that can lag until completion, which is too late for251 PSTN/webhook registration. Also poll:252 `GET /v1/simulations?filter=run_id%3D%22<RUN_ID>%22`, register the first253 returned `simulation_id` / simulation output ID immediately, then continue254 watching the run.2553. While the run is pending, continue with non-blocking trace improvement:256 - add safe span enrichment visible from the code path, such as stable257 `conversation`, `turn`, `stt`, `llm`, `tts`, `llm_tool_call`,258 `transport`, or provider spans259 - add bounded high-value attributes such as `metrics.ttfb`, token counts,260 finish reasons, tool names, safe tool argument summaries, status, and261 errors262 - add customer-signal numeric attributes that can become metrics, such as263 `tool.error`, `tool.latency_ms`, `tool.call.count`,264 `workflow.dependency_blocked`, `workflow.completed`, and265 `workflow.fallback_used`266 - improve flush/shutdown, buffering, batch size, retry, or deployment267 packaging issues found during implementation268 - prepare custom trace metric candidates from expected spans and any269 historical Coval traces already available; treat generic duration or270 span-count metrics as diagnostic proof unless they answer a customer271 operating question272 - prepare trace-aware LLM judge metric candidates with `include_traces=true`273 when trace context is needed to grade ordering, tool-output grounding,274 verification before tool use, or recovery from tool errors2754. Create custom trace metrics during the wait only when real trace data already276 proves the span name and metric attribute exist, either from historical277 traced runs or from the in-flight validation once spans appear in Trace278 Search. If this validation is the first trace for the agent, stage the metric279 definitions while waiting and create them immediately after the run produces280 confirmed spans.281 Create trace-aware LLM judge metrics before the follow-up validation run when282 the Metrics OpenAPI supports `include_traces` and their prompts depend on283 trace context that the first trace already proved exists.2845. When the run finishes, confirm the agent export returned 200 or that the285 exporter logged a successful accepted batch. A 404 from the standalone test286 script only proves auth/connectivity, not lifecycle wiring.2876. Open the Coval run or conversation result and verify the OTel Traces card or288 Trace Search result appears.2897. Inspect the trace for expected spans and attributes. If it is missing, sparse,290 duplicated, or attached to the wrong result, stop further metric creation and291 apply `debug-traces` before continuing.2928. **Trace density self-check.** Before declaring success, count spans by name.293 If a single span name is more than ~70% of total spans and those spans are294 each shorter than ~50 ms, that name is almost certainly per-chunk/per-frame295 noise. Collapse it into a counter attribute on the parent span and redeploy296 *before* opening the result for the customer — the trace viewer will look297 empty otherwise. Do not rely on the customer to flag visual noise.2989. **Verify correlation activation from the exporter, not from pre-existing299 agent counters.** Pre-existing counters like a `non_audio_messages` or300 `non_setup_frames` tally on the agent often increment *after* the301 setup/init branch returns, so they read `0` even when correlation worked.302 The authoritative signal that the simulation ID reached the exporter is303 the OTel logger line (`activated OTLP export simulation_id=<id>`) or the304 batch processor's accepted-batch log. Check those first; only chase305 counters if the exporter never activated.30610. After the initial trace is confirmed, finish any prepared metric creation307 and run one follow-up calculation/preview/attached-run check through the308 CLI/API when the public API supports it.30911. If you changed metric definitions or attached new metrics, run a post-change310 validation and poll until the run status is terminal (`COMPLETED`, `FAILED`,311 or `CANCELLED`) and each newly attached trace metric has a terminal output312 on the simulation (`COMPLETED` or `FAILED`). Do not stop while outputs are313 still `IN QUEUE` / `IN PROGRESS`, even when `progress.completed_test_cases`314 already equals `total_test_cases`.31512. When prior runs show `FAILED` trace metrics, verify against the first run316 launched after your instrumentation and metric changes instead of treating317 older runs as current-state proof. Metric failures on older runs may reflect318 stale definitions that no longer match the latest instrumentation.319320For WebSocket agents, make the smoke interaction long enough to trigger the321agent's response threshold. A client that sends too little audio, or an agent322that waits for more silence/audio than Coval sends, can make tracing look broken323even when the exporter is wired correctly. If a `tts` span is marked `ERROR`324with a WebSocket disconnect after partial audio, shorten the canned response or325stop streaming when the client closes.326327Optional connectivity-only check:328```bash329python skills/traces/setup-tracing/scripts/send-test-span.py \330 --api-key "$COVAL_API_KEY" \331 --simulation-id coval-tracing-test332```333A 404 with `Simulation output not found` means the key reached Coval and auth worked, but it is not proof that the agent call lifecycle is wired.334335## Handoff336337End with:338- files changed and why339- exact correlation path used340- how the customer sets required env vars341- commands run, including the Coval launch and polling commands/API endpoints342- Coval trace URL or simulation/conversation ID used for proof343- direct Coval URLs for:344 - runs list (`https://app.coval.dev/<org-slug>/runs?sort=createdAt%3Adescending`)345 - run details (`https://app.coval.dev/<org-slug>/runs/<run-id>`)346 - run result (`https://app.coval.dev/<org-slug>/runs/<run-id>/results/<simulation-output-id>`)347 - trace viewer (`https://app.coval.dev/<org-slug>/runs/<run-id>/results/<simulation-output-id>/traces`)348- custom trace metrics created, staged, or deferred and why349- any remaining gaps to handle with `optimize-trace-observability`, `configure-trace-metrics`, or `debug-traces`