---
title: "Trace distributed agents"
description: "Keep one trace connected as a request crosses services."
seo:
  title: "Distributed AI Agent Tracing | Judgment Docs"
  description: "Keep one Judgment trace connected across services by propagating OpenTelemetry context and session identity."
---

Distributed tracing keeps one live request in a single Judgment trace as it
crosses services. It does this by propagating OpenTelemetry context between
them. Use [Durable work](/documentation/tracing/durable-work) instead
when a queue, retry, or human approval wait resumes the work later.

Use the same Judgment project across all services. Give each service a distinct
`service.name` resource attribute when you initialize its tracer, not on
individual spans. One trace then shows every hop while the services stay
distinguishable.

Set the session ID on the upstream root before injecting the outgoing headers.
The SDK includes that ID in Judgment baggage. `Tracer.continue_trace()` and
`Tracer.continueTrace()` restore the baggage with the trace context, so
downstream child spans inherit the same session ID automatically.

## Prerequisites

- Configure the same Judgment organization and project in every participating
  service. See [Project routing](/documentation/reference/project-routing).
- Give each service a distinct `service.name` resource attribute.
- Initialize the tracer before any service creates spans.
- Identify the upstream root and the outgoing and incoming request boundaries.

## Send trace state

Inject the current trace context into the outgoing request:

**Python**

```python
from judgeval.trace.propagation import inject

headers = {}
inject(headers)
response = httpx.post(DOWNSTREAM_URL, json=payload, headers=headers)
```

**TypeScript**

```typescript
import { propagation } from "judgeval";

const headers: Record<string, string> = {};
propagation.inject(headers);
await fetch(DOWNSTREAM_URL, { method: "POST", headers, body });
```

## Receive trace state

Continue the trace before starting child spans. A span that starts first gets a
new trace ID, which makes the request appear as two unrelated traces. Middleware
is a reliable place to continue the trace because it covers every incoming
request:

**Python**

```python
with Tracer.continue_trace(request.headers):
    result = handle_request(payload)
```

**TypeScript**

```typescript
await Tracer.continueTrace(req.headers, async () => {
    return handleRequest(payload);
});
```

Delayed queue work needs a stable job or session ID instead of the live
request's trace context. Start a new trace when the worker receives the task.
See [Durable work](/documentation/tracing/durable-work).

## Verify

First run the
[shared verification checklist](/documentation/tracing/instrumentation#verify-before-you-claim-success).
Then send one request through both services. The downstream spans should appear
under the upstream root in one trace. Two traces mean the context did not arrive
or was restored after a downstream span had already started.

## Troubleshooting

| Symptom | Check |
| --- | --- |
| Two traces appear for one request | Continue the incoming context before starting any downstream span. |
| Downstream spans are missing | Confirm the carrier headers reached the service and its tracer exports to the same project. |
| Downstream session is missing | Set the session ID before injecting upstream context and use `continue_trace` or `continueTrace` to restore baggage. |
| Queue work extends the live trace | Start a new trace when the queued job begins and reuse only the stable session ID. |

## Related pages

- [Group traces into sessions](/documentation/tracing#group-traces-into-sessions)
- [Durable work](/documentation/tracing/durable-work) for work that
  resumes after a queue or human approval wait
- [Trace conventions](/documentation/reference/trace-conventions)
