> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracepilotai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Traces and spans: how TracePilot AI models your agents

> TracePilot AI organizes every agent run into traces and spans. Learn how parent-child span links build a full, inspectable execution tree.

TracePilot AI organizes every agent run into a structured hierarchy of **traces** and **spans**. A trace represents the full lifecycle of a single agent execution. Each individual operation within that run — an LLM call, a tool invocation, or any async step — is captured as a span. Together, these form a tree you can inspect, replay, and debug from the TracePilot dashboard.

## What is a trace?

A trace is one complete agent run. You start a new trace at the entry point of your agent with `tp.startTrace('agent-name')`. Every span created after that call belongs to that trace, until the agent finishes.

```typescript theme={null}
import { TracePilot } from 'tracepilot-sdk';

const tp = new TracePilot('tp_live_YOUR_KEY');

async function runAgent() {
  await tp.startTrace('customer-support-agent');
  // All spans created here belong to this trace
}
```

Traces are named after the agent they represent. The name appears in the dashboard to help you identify and filter runs across your fleet.

## What is a span?

A span is one captured operation within a trace. TracePilot creates a span every time you call `tp.wrapOpenAI()` or `tp.wrapToolCall()`. Each span records:

| Field          | Description                                       |
| -------------- | ------------------------------------------------- |
| `input`        | The messages or arguments passed into the call    |
| `output`       | The response returned from the LLM or tool        |
| `tokens`       | Prompt and completion token counts                |
| `latency`      | Wall-clock time for the operation in milliseconds |
| `error`        | Any exception thrown during the call              |
| `parentSpanId` | The ID of the parent span, if any                 |

Every call to `wrapOpenAI` or `wrapToolCall` returns a `spanId`. You pass that ID as the `parentSpanId` of subsequent calls to link child spans to their parent.

## Building a span tree

When your agent has multiple steps, spans form a tree. The root span has no parent. Child spans pass the parent's `spanId` as their `parentSpanId`. This mirrors the actual execution flow and makes it easy to see which step triggered which downstream call.

The `stepOrder` parameter numbers each span in the tree. TracePilot uses this to display spans in the correct sequence in the dashboard, regardless of when they were captured.

```typescript theme={null}
async function researchAgent(query: string) {
  await tp.startTrace('research-agent');

  const messages = [{ role: 'user', content: query }];

  // Step 1 — root span (no parent)
  const { result: plan, spanId: planSpanId } = await tp.wrapOpenAI(
    () => openai.chat.completions.create({ model: 'gpt-4o', messages }),
    messages
    // no parentSpanId — this is the root
    // no stepOrder — defaults to 1
  );

  // Step 2 — child of step 1 (tool call)
  const { result: searchResult, spanId: searchSpanId } = await tp.wrapToolCall(
    'web-search',
    () => webSearch(plan.choices[0].message.content),
    planSpanId, // parent span
    2           // step order
  );

  // Step 3 — child of step 2 (synthesis)
  const followUp = [
    ...messages,
    plan.choices[0].message,
    { role: 'tool', content: JSON.stringify(searchResult) }
  ];

  const { result: answer } = await tp.wrapOpenAI(
    () => openai.chat.completions.create({ model: 'gpt-4o', messages: followUp }),
    followUp,
    searchSpanId, // parent span
    3             // step order
  );

  return answer.choices[0].message.content;
}
```

This produces a three-level tree in the dashboard:

```
research-agent (trace)
└── Step 1: gpt-4o (planSpanId)
    └── Step 2: web-search (searchSpanId)
        └── Step 3: gpt-4o
```

<Note>
  `stepOrder` does not affect execution. It only controls the display order in the dashboard. Always set it to reflect the logical sequence of your agent's steps.
</Note>

## Span data in the dashboard

Every span in the dashboard is expandable. Click a span to see its full input and output, the exact token breakdown (prompt vs. completion), latency in milliseconds, and any error message if the call failed.

<CardGroup cols={2}>
  <Card title="LLM spans" icon="brain">
    Created by `wrapOpenAI`. Captures model name, messages in, completion out, and full token usage.
  </Card>

  <Card title="Tool spans" icon="wrench">
    Created by `wrapToolCall`. Captures tool name, arguments, return value, and latency. Can be marked destructive.
  </Card>
</CardGroup>

## Wrapping calls

<Steps>
  <Step title="Start a trace">
    Call `tp.startTrace('agent-name')` at the top of your agent function. This initializes the trace context for all subsequent spans.
  </Step>

  <Step title="Wrap LLM calls">
    Replace direct OpenAI calls with `tp.wrapOpenAI(call, messages, parentSpanId?, stepOrder?)`. The `result` field contains the unmodified OpenAI response.
  </Step>

  <Step title="Wrap tool calls">
    Replace tool invocations with `tp.wrapToolCall(toolName, call, parentSpanId, stepOrder, isDestructive?)`. Pass the `spanId` from the preceding LLM span as the `parentSpanId`.
  </Step>

  <Step title="Chain spans with parentSpanId">
    Each call returns a `spanId`. Pass it as `parentSpanId` to the next call to establish the parent-child link and build the execution tree.
  </Step>
</Steps>

<Tip>
  You don't need to change any logic in your agent. TracePilot wraps your existing calls and returns the original result untouched. Adding tracing is purely additive.
</Tip>
