Set up tracing
This page shows how to send traces from your application. You need an application and an ingest key for it.
The setup wizard
Section titled “The setup wizard”Open Tracing, choose the application, and open Setup (a new application opens on it). The wizard has two paths.
Do it yourself:
- 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.
- Language and Package manager. Python with pip, uv or poetry; TypeScript with npm, pnpm, bun or yarn.
- Install. The install command.
- 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
.envfile. Copy it now: it is not shown again. - 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 and key
Section titled “Endpoint and key”| 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.
Python
Section titled “Python”Requires Python 3.10 or later.
pip install weharness-tracingThe wizard shows the same package for uv add and poetry add.
export WEHARNESS_INGEST_KEY=whk_... # from Generate keyexport OTEL_SERVICE_NAME=refund-agentimport 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 exitsIf 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.
TypeScript and Node.js
Section titled “TypeScript and Node.js”Requires Node.js 18 or later. The package is an ES module.
npm install @weharness/tracingThe 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 exitsAlready using OpenTelemetry? Add spanProcessor() to your own tracer provider instead of calling init(). See Node.js SDK.
Plain OpenTelemetry
Section titled “Plain OpenTelemetry”Any OpenTelemetry SDK works with two variables:
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.weharness.io/otlp/v1/tracesexport OTEL_EXPORTER_OTLP_TRACES_HEADERS="Authorization=Bearer%20whk_..."export OTEL_SERVICE_NAME=refund-agentThe 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 tracefrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk.resources import Resourcefrom opentelemetry.sdk.trace import TracerProviderfrom 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.
Model and tool calls
Section titled “Model and tool calls”Spans that follow the OpenTelemetry GenAI conventions show as model calls and tool calls with their tokens:
gen_ai.operation.nameset tochat(a model call),execute_tool(a tool call) orinvoke_agent(a run of your agent);gen_ai.request.modelfor model calls;gen_ai.tool.namefor tool calls;gen_ai.usage.input_tokensandgen_ai.usage.output_tokens.
Model and tool calls recorded by OpenInference instrumentations (LangChain, LlamaIndex, the OpenAI Agents SDK and others) are recognised too.
Framework recipes
Section titled “Framework recipes”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 whfrom openinference.instrumentation.langchain import LangChainInstrumentor
provider = wh.init(service="refund-agent") # the key comes from WEHARNESS_INGEST_KEYLangChainInstrumentor().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 exitsThe 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 whfrom 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 spansresult = 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();Environments
Section titled “Environments”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.
Name the code version
Section titled “Name the code version”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.revisionfromWEHARNESS_REVISION, or from the usual CI variables:GITHUB_SHA,CI_COMMIT_SHA,VERCEL_GIT_COMMIT_SHA,RENDER_GIT_COMMIT,SOURCE_VERSIONorGIT_COMMIT. You can also passrevisiontoinit(). - 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.
Content capture
Section titled “Content capture”By default only metadata is stored. To keep prompts, model outputs, tool arguments and results, both must be on:
- The key keeps content. When you make the key in Setup, turn on Keep prompts and outputs. The key shows a keeps content badge.
- The SDK sends content. Pass
capture_content=True(Python) orcaptureContent: true(Node.js) toinit(). 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.
Check that it works
Section titled “Check that it works”- 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.