Skip to content

Set up tracing

This page shows how to send traces from your application. You need an application and an ingest key for it.

Open Tracing, choose the application, and open Setup (a new application opens on it). The wizard has two paths.

Do it yourself:

  1. Framework. The WeHarness SDK, plain OpenTelemetry, or a framework from the catalog that has a tracing recipe. Tracing verified means a WeHarness check runs that recipe end to end. The others follow the framework’s or the instrumentation’s own documentation.
  2. Language and Package manager. Python with pip, uv or poetry; TypeScript with npm, pnpm, bun or yarn.
  3. Install. The install command.
  4. Configure. Choose the environment (Local, Staging or Production) and select Generate key (owners and admins). The key is filled into the variables, as shell exports or a .env file. Copy it now: it is not shown again.
  5. Start. A short program that sends a first trace.

The page says Waiting for traces… and moves on by itself when the first one arrives.

Or let your coding agent do it: connect Claude Code, Codex, Cursor or VS Code to WeHarness over MCP (see MCP overview), then ask it: Set up WeHarness tracing for this project. It calls tracing_setup for the endpoint and a new key, and adds the SDK to your code. Review its change as you would any other.

You can also ask WeHarness in a session to add tracing to your application.

Endpoint https://api.weharness.io/otlp/v1/traces
Protocol OTLP over HTTP, protobuf or JSON, gzipped or not
Authentication Authorization: Bearer <ingest key> (keys start with whk_)
Request size Up to 5 MB

Keep the key in an environment variable or a secret store. Never put it in code that runs in a browser.

Requires Python 3.10 or later.

Terminal window
pip install weharness-tracing

The wizard shows the same package for uv add and poetry add.

Terminal window
export WEHARNESS_INGEST_KEY=whk_... # from Generate key
export OTEL_SERVICE_NAME=refund-agent
import weharness_tracing as wh
wh.init(service="refund-agent") # the key comes from WEHARNESS_INGEST_KEY
with wh.agent_run("refunds"):
with wh.model_call("gemini-3.8-flash", provider="gcp.gemini") as call:
... # call your model
call.usage(input_tokens=120, output_tokens=40)
with wh.tool_call("lookup_order"):
... # run the tool
wh.shutdown() # send what is queued before the process exits

If your application already has an OpenTelemetry tracer provider, init() adds WeHarness’s exporter to it, so your existing instrumentation is sent too. See Python SDK.

Requires Node.js 18 or later. The package is an ES module.

Terminal window
npm install @weharness/tracing

The wizard shows the same package for pnpm, bun and yarn.

import { init, agentRun, modelCall, toolCall, shutdown } from "@weharness/tracing";
init({ service: "refund-agent" }); // the key comes from WEHARNESS_INGEST_KEY
await agentRun("refunds", async () => {
await modelCall("gemini-3.8-flash", { provider: "gcp.gemini" }, async (call) => {
// call your model
call.usage({ inputTokens: 120, outputTokens: 40 });
});
await toolCall("lookup_order", async () => { /* run the tool */ });
});
await shutdown(); // send what is queued before the process exits

Already using OpenTelemetry? Add spanProcessor() to your own tracer provider instead of calling init(). See Node.js SDK.

Any OpenTelemetry SDK works with two variables:

Terminal window
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.weharness.io/otlp/v1/traces
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="Authorization=Bearer%20whk_..."
export OTEL_SERVICE_NAME=refund-agent

The space after Bearer is written %20: header values in this variable are URL-encoded.

Python (pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http):

from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
# The endpoint and the key come from OTEL_EXPORTER_OTLP_TRACES_ENDPOINT and OTEL_EXPORTER_OTLP_TRACES_HEADERS.
provider = TracerProvider(resource=Resource.create({"service.name": "refund-agent"}))
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("refund-agent")
with tracer.start_as_current_span("invoke_agent refund-agent", attributes={"gen_ai.operation.name": "invoke_agent"}):
... # your agent; use the GenAI conventions (gen_ai.request.model, gen_ai.tool.name, token usage)
provider.shutdown()

Node.js (npm install @opentelemetry/api @opentelemetry/resources @opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-proto):

import { trace } from "@opentelemetry/api";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
// The endpoint and the key come from OTEL_EXPORTER_OTLP_TRACES_ENDPOINT and OTEL_EXPORTER_OTLP_TRACES_HEADERS.
const provider = new NodeTracerProvider({
resource: resourceFromAttributes({ "service.name": "refund-agent" }),
spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter())],
});
provider.register();
const tracer = trace.getTracer("refund-agent");
await tracer.startActiveSpan("invoke_agent refund-agent", { attributes: { "gen_ai.operation.name": "invoke_agent" } },
async (span) => {
// your agent; use the GenAI conventions (gen_ai.request.model, gen_ai.tool.name, token usage)
span.end();
});
await provider.shutdown();

With plain OpenTelemetry, nothing is removed before spans leave your process. WeHarness’s gateway removes content when it arrives, unless the key keeps content.

Spans that follow the OpenTelemetry GenAI conventions show as model calls and tool calls with their tokens:

  • gen_ai.operation.name set to chat (a model call), execute_tool (a tool call) or invoke_agent (a run of your agent);
  • gen_ai.request.model for model calls;
  • gen_ai.tool.name for tool calls;
  • gen_ai.usage.input_tokens and gen_ai.usage.output_tokens.

Model and tool calls recorded by OpenInference instrumentations (LangChain, LlamaIndex, the OpenAI Agents SDK and others) are recognised too.

The wizard offers these frameworks when the catalog includes them. Each recipe installs the WeHarness SDK and, where needed, the framework’s instrumentation, following the framework’s own documentation. WeHarness has not run these recipes end to end yet; its checks run the WeHarness SDK recipe.

Framework Language Extra package What becomes spans
OpenAI Agents SDK Python openinference-instrumentation-openai-agents Runs, model calls, handoffs and tool calls
LangChain and LangGraph Python openinference-instrumentation-langchain Chains, LangGraph nodes, model calls and tool calls
LlamaIndex Python openinference-instrumentation-llama-index Queries, retrievals, model calls and tool calls
smolagents Python openinference-instrumentation-smolagents Agent steps, model calls and tool calls
Pydantic AI Python None Runs, model and tool calls (instrument=True)
Google ADK Python None The agent, model and tool spans ADK records with OpenTelemetry
Vercel AI SDK TypeScript None Model and tool calls (experimental_telemetry)

For an OpenInference instrumentation, pass the provider init() returns:

import weharness_tracing as wh
from openinference.instrumentation.langchain import LangChainInstrumentor
provider = wh.init(service="refund-agent") # the key comes from WEHARNESS_INGEST_KEY
LangChainInstrumentor().instrument(tracer_provider=provider) # chains, LangGraph nodes, model calls and tool calls
... # run your agent as usual
wh.shutdown() # send what is queued before the process exits

The other OpenInference instrumentations work the same way: OpenAIAgentsInstrumentor from openinference.instrumentation.openai_agents, LlamaIndexInstrumentor from openinference.instrumentation.llama_index and SmolagentsInstrumentor from openinference.instrumentation.smolagents.

Pydantic AI:

import weharness_tracing as wh
from pydantic_ai import Agent
wh.init(service="refund-agent")
agent = Agent("provider:model", instrument=True) # Pydantic AI records its runs, model and tool calls as spans
result = agent.run_sync("Where is my order A-100?")
wh.shutdown()

Google ADK: call wh.init(service="refund-agent") before the runner starts. ADK’s own spans then go to WeHarness through that provider.

Vercel AI SDK:

import { init, shutdown } from "@weharness/tracing";
import { generateText } from "ai";
init({ service: "refund-agent" });
const { text } = await generateText({
model: yourModel, // your provider's model
prompt: "Where is my order A-100?",
experimental_telemetry: { isEnabled: true }, // the AI SDK records model and tool calls as spans
});
await shutdown();

Each ingest key belongs to one environment. Make one key per environment your application runs in, for example local, staging and production, and give each deployment its own key. In Tracing you choose the environment at the top of an application; Monitoring, alerts and online evaluators can be limited to one environment.

Tell WeHarness which commit produced each trace. Then Releases groups traces by commit, Find in code opens the right lines, and a trace shows whether it came from code WeHarness read or pushed, or from code it has not seen.

  • The SDKs set vcs.ref.head.revision from WEHARNESS_REVISION, or from the usual CI variables: GITHUB_SHA, CI_COMMIT_SHA, VERCEL_GIT_COMMIT_SHA, RENDER_GIT_COMMIT, SOURCE_VERSION or GIT_COMMIT. You can also pass revision to init().
  • With plain OpenTelemetry, set the resource attribute yourself, for example OTEL_RESOURCE_ATTRIBUTES=vcs.ref.head.revision=<commit sha>.
  • service.version (the SDKs’ version) is used as the release when no commit is given.

A missing version is shown as unknown, never guessed.

By default only metadata is stored. To keep prompts, model outputs, tool arguments and results, both must be on:

  1. The key keeps content. When you make the key in Setup, turn on Keep prompts and outputs. The key shows a keeps content badge.
  2. The SDK sends content. Pass capture_content=True (Python) or captureContent: true (Node.js) to init(). Plain OpenTelemetry sends whatever your instrumentation records.

Kept content is shown in the trace view and can be read by online evaluators and judges. It is still deleted after 14 days. Do not put secrets or personal data in span names, error messages or custom attributes: those are metadata and are always kept.

  • Run your application, then open Tracing and the application. The first trace appears within a minute.
  • Setup shows, for each key, what arrived in the last 24 hours: accepted, stored, refused and deferred spans. See Reading a trace.
  • Nothing arrives? See Troubleshooting.