---
title: "Instrument your agent"
description: "Initialize Judgment tracing, capture one real agent path, export it safely, and verify the trace."
seo:
  title: "Instrument an AI Agent | Judgment Tracing Docs"
  description: "Initialize Judgment tracing, use supported instrumentation, flush completed spans, and verify an AI agent trace."
---

Instrument one real agent path so its root, model calls, tool calls, input,
output, and session reach the intended Judgment project. Start with supported
instrumentation, then add manual spans only for work it does not capture.

## Prerequisites

- Install the Judgeval SDK for your language.
- Configure `JUDGMENT_API_KEY` and `JUDGMENT_ORG_ID` as described in
  [Authentication](/documentation/reference/authentication).
- Choose an explicit project name for this environment. See
  [Project routing](/documentation/reference/project-routing).
- Identify the code path that performs real agent work and what one trace
  should cover. See the [tracing data model](/documentation/tracing).

## Use auto-instrumentation

Two integration families cover most applications:

| Integration | Captures | Use it when |
| --- | --- | --- |
| [Agent framework](/documentation/integrations#framework-auto-instrumentation) | Agent steps, tool calls, and the model calls exposed by the framework | The application uses a supported framework. Start here. |
| [Model provider](/documentation/integrations#model-provider-instrumentation) | Provider calls, token usage, and cost | The application calls a supported provider client directly. |

Enable the integration that matches the application. Open one app root around
the work so integration spans become its children, then add `Tracer.observe`
only for missing work such as custom tools and retrievals.

> **Record each model call once**
>
> Do not wrap a client that the framework integration already records or enable
> provider instrumentation on top of it. Either combination duplicates the
> model span, tokens, and cost.

TypeScript OpenTelemetry auto-instrumentation requires all three of these:

1. A file the runtime loads before the application.
2. Instrumentations registered on `Tracer` before `Tracer.init()`.
3. A CommonJS build, because the instrumentations patch modules when they are
   required.

```typescript title="instrumentation.ts"
import { Tracer } from "judgeval";
import { OpenAIInstrumentation } from "@opentelemetry/instrumentation-openai";

Tracer.registerOTELInstrumentation(new OpenAIInstrumentation());

await Tracer.init({ projectName: process.env.JUDGMENT_PROJECT_NAME });
```

Import this file first in the entrypoint, or preload it with the runtime's
require or import flag. For bundlers, also follow
[Bundled and serverless runtimes](/documentation/tracing/serverless-runtimes).

## Initialize tracing first

Initialize once in every process that creates spans, before constructing
wrapped clients, observed functions, workers, or the application.

**Python**

```python title="tracer.py"
import os
from judgeval import Tracer

Tracer.init(project_name=os.environ["JUDGMENT_PROJECT_NAME"])
```

**TypeScript**

```typescript title="tracer.ts"
import { Tracer } from "judgeval";

await Tracer.init({
  projectName: process.env.JUDGMENT_PROJECT_NAME,
});
```

Do not silently guess a fallback project. A missing project name can leave
monitoring disabled, while a guessed name can send traces somewhere nobody is
watching. Log the selected project and deployment environment at startup, but
never log the API key.

Tracing is enabled when `JUDGMENT_MONITORING` is absent. Set
`JUDGMENT_MONITORING=false` only when you intentionally need the deployment to
stop exporting traces; do not require a separate `true` value that can be
forgotten in production.

## Trace the agent path

This example assumes no framework integration. It creates one root, traces one
tool, and wraps one supported model client. Keep only the pieces your selected
integration does not already provide.

Every span is named explicitly. Production bundlers can strip function names,
which otherwise leaves blank names in the trace view.

> **Info**
>
> Set `OPENAI_API_KEY` before running this example.

**Python**

```python title="trace_agent.py"
import tracer  # initializes tracing before the client is created

from judgeval import Tracer
from openai import OpenAI

openai = Tracer.wrap(OpenAI())

@Tracer.observe(span_type="tool", span_name="retrieve_context")
def retrieve_context(question: str) -> str:
    return f"Reference material relevant to: {question}"

@Tracer.observe(span_type="agent", span_name="run_agent")
def run_agent(question: str) -> str:
    Tracer.set_session_id("example-session")
    context = retrieve_context(question)
    response = openai.chat.completions.create(
        model="gpt-5.6-luna",
        messages=[{
            "role": "user",
            "content": f"Question: {question}\n\nContext: {context}",
        }],
    )
    return response.choices[0].message.content

if __name__ == "__main__":
    print(run_agent("What is the capital of the United States?"))
    Tracer.shutdown()
```

**TypeScript**

```typescript title="traceAgent.ts"
import "./tracer";

import { Tracer } from "judgeval";
import OpenAI from "openai";

const openai = Tracer.wrap(new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
}));

const retrieveContext = Tracer.observe(async function retrieveContext(
  question: string,
): Promise<string> {
  return `Reference material relevant to: ${question}`;
}, { spanType: "tool", spanName: "retrieve_context" });

const runAgent = Tracer.observe(async function runAgent(
  question: string,
): Promise<string> {
  Tracer.setSessionId("example-session");
  const context = await retrieveContext(question);
  const response = await openai.chat.completions.create({
    model: "gpt-5.6-luna",
    messages: [{
      role: "user",
      content: `Question: ${question}\n\nContext: ${context}`,
    }],
  });
  return response.choices[0]?.message.content || "No answer";
}, { spanType: "agent", spanName: "run_agent" });

await runAgent("What is the capital of the United States?");
await Tracer.shutdown();
```

## Activate the root span

A span becomes the parent of new work only while it is active:

| Language | Active boundary |
| --- | --- |
| Python | `Tracer.observe`, `Tracer.span()`, or `Tracer.start_as_current_span()` |
| TypeScript | `Tracer.observe` or `Tracer.getOTELTracer().startActiveSpan` |

In TypeScript, `Tracer.startSpan` returns a span without activating it.
`Tracer.setSessionId()` then has no active span to update, and later work does
not become a child. Raw OpenTelemetry `context.with` and `trace.setSpan` also do
not activate the SDK's own context. Use an active boundary from the table.

Python has the same lifecycle distinction: `Tracer.start_span()` returns a
span that you must manage. Prefer an active boundary whenever work should
inherit that span.

Long-lived background work started while a request span is active can inherit
the request forever. Start the background loop outside any active span and
give each unit its own root.

## Flush completed roots

Judgment starts evaluation when the completed root arrives, so every child must
be sent before the root. End the root with a real duration, output, and status
before flushing. Spans wait in a batch queue for about five seconds; anything
still queued when a process stops is lost.

| Runtime shape | Where to flush |
| --- | --- |
| Script or CLI | After the final root, then shut down before exit. `shutdown` is synchronous in Python and a promise in TypeScript. |
| Serverless handler | After the root ends and before the runtime can freeze. |
| Worker | After each job if the process can stop before the next export. |
| Long-running server | At graceful shutdown; flush after each root if the process can be killed or restarted without warning. |
| Durable loop | Before saving the checkpoint for each completed part. |

**Python**

```python
result = run_job(job)  # the observed root ends when this returns
Tracer.force_flush()
```

**TypeScript**

```typescript
const result = await runJob(job);
await Tracer.forceFlush();
```

## Verify before you claim success

1. Run real agent work with monitoring enabled.
2. Confirm the trace reached the expected project and environment.
3. Open the trace and confirm the named app root contains the expected model
   and tool spans, input, output, and session ID.
4. Read the trace health results. Judgment checks root structure, nesting,
   timing, input/output, and LLM metadata. A check with no status is still
   pending, not passing.
5. Confirm plumbing did not create extra traces and each request produced one
   expected trace.
6. Test one hard case: restart, stream abort, worker retry, approval resume,
   handled tool failure, or serverless cold call.

A coding agent can read the same health results with `get_trace_health`,
`get_trace_health_summary`, and `list_trace_health_failures` through the
Judgment MCP server.

> **A healthy trace is the proof**
>
> A successful `Tracer.init()` only proves setup ran. The trace in Judgment
> proves the application executed traced code and Judgment received it.

![A basic trace in Judgment](/blume-assets/content/docs/images/platform/trace_popout_light.png)

## Troubleshooting

| Symptom | Check |
| --- | --- |
| No project or traces | Check credentials, organization, monitoring state, network access, and whether real traced code ran. |
| Project exists but is empty | Check that the process handling real traffic received the tracing settings and initialization code. |
| Traces reached the wrong project | Set and log the active project before the root starts. |
| Root has zero duration or no output | End the root before flushing or stopping the worker. |
| Named root has no integration children or session | Check initialization order and whether the root was active. |
| One request breaks into roots across services | Propagate OpenTelemetry context between services. |
| Model spans or cost appear twice | Choose manual or automatic LLM instrumentation, not both. |
| Returned failures stay green | Mark handled failures explicitly. See [Add attributes and context](/documentation/tracing/attributes#record-handled-and-returned-failures). |
| Test traces reach production | Use a separate project per environment. |

## Next step

[Add attributes and context](/documentation/tracing/attributes) once the trace
structure is healthy. The [Tracer SDK Reference](/sdk-reference) contains the
complete Python and TypeScript APIs.
