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

# Trace your agent

> Create spans, record model usage and cost, wrap functions with observe, and derive deterministic trace IDs.

A trace is one agent run. Spans are the steps inside it. The SDK adds Atlan's
observation semantics on top of ordinary OpenTelemetry spans, so a step reads
as an LLM call or a tool call rather than an untyped span.

## Observation types

Set `as_type` / `asType` to classify a span. Agent Registry uses the type to
decide how to render the step and which metrics apply.

| Type       | Use it for                                                         |
| ---------- | ------------------------------------------------------------------ |
| `task`     | A unit of work, and the usual choice for a root span. The default. |
| `llm`      | A model call. Carries model, provider, usage, and cost.            |
| `tool`     | A call out to a tool or external system.                           |
| `function` | An internal function worth showing as its own step.                |
| `session`  | A conversation or long-running interaction.                        |
| `turn`     | One exchange inside a session.                                     |
| `score`    | A span whose purpose is recording an evaluation.                   |

## Create spans

`start_as_current_span` / `startAsCurrentSpan` opens a span, makes it the
active parent for anything started inside, and closes it on exit.

<CodeGroup>
  ```python Python theme={null}
  with client.start_as_current_span("summarize-thread", as_type="task") as span:
      span.update(input={"thread_id": "ticket-4821"})
      result = summarize(thread)
      span.update(output=result)
  ```

  ```typescript TypeScript theme={null}
  await client.startAsCurrentSpan("summarize-thread", { asType: "task" }, async (span) => {
    span.update({ input: { threadId: "ticket-4821" } });
    const result = await summarize(thread);
    span.update({ output: result });
  });
  ```
</CodeGroup>

Use `start_span` / `startSpan` for a detached span when the parent is not the
active context — a framework tracking its own run tree, a retroactive span with
an explicit `start_time`, or a child of a remote parent. Detached spans must be
ended explicitly.

## Record model usage and cost

`update` sets fields on the span that spent them. Record usage and cost on the
`llm` span, not the root.

<CodeGroup>
  ```python Python theme={null}
  with client.start_as_current_span("chat", as_type="llm") as generation:
      generation.update(
          model="claude-sonnet-5",
          provider="anthropic",
          usage={"input_tokens": 1200, "output_tokens": 340},
          cost={"input": 0.0036, "output": 0.0051},
          input=[{"role": "user", "parts": [{"type": "text", "content": "Summarize this thread"}]}],
          output="Three open questions remain.",
      )
  ```

  ```typescript TypeScript theme={null}
  await client.startAsCurrentSpan("chat", { asType: "llm" }, async (generation) => {
    generation.update({
      model: "claude-sonnet-5",
      provider: "anthropic",
      usage: { input_tokens: 1200, output_tokens: 340 },
      cost: { input: 0.0036, output: 0.0051 },
      input: [{ role: "user", parts: [{ type: "text", content: "Summarize this thread" }] }],
      output: "Three open questions remain.",
    });
  });
  ```
</CodeGroup>

`cost` accepts `input`, `output`, and `total`. Provide `total` when your
provider bills a single figure you cannot split.

## Wrap a function with observe

`observe` traces a function without restructuring it. It handles sync
functions, async functions, and generators.

<CodeGroup>
  ```python Python theme={null}
  from atlan_ai import observe


  @observe(as_type="tool")
  def search_docs(query: str) -> list[str]:
      return index.search(query)


  @observe  # bare form: as_type defaults to "task", name from the function
  async def plan(goal: str) -> str:
      ...
  ```

  ```typescript TypeScript theme={null}
  import { observe } from "@atlanai/sdk";

  const searchDocs = observe(
    (query: string) => index.search(query),
    { asType: "tool", name: "search_docs" },
  );
  ```
</CodeGroup>

Input and output are captured by default. Turn either off per function with
`capture_input=False` / `capture_output=False` when the payload is large or
sensitive. For a blanket rule across every span, use
[`trace_content`](/sdk/privacy) instead.

## Carry identity across a run

`propagate_attributes` stamps association attributes onto every span started
in its scope — including spans created by third-party instrumentation you do
not control.

<CodeGroup>
  ```python Python theme={null}
  with atlan_ai.propagate_attributes(
      session_id="ticket-4821",
      user_id="jordan@example.com",
      visitor_id=visitor.id if visitor else None,
      tags=["support", "escalated"],
      trace_name="zendesk:4821",
  ):
      run_agent(thread)
  ```

  ```typescript TypeScript theme={null}
  await atlan.propagateAttributes(
    {
      sessionId: "ticket-4821",
      userId: "jordan@example.com",
      visitorId: visitor?.id,
      tags: ["support", "escalated"],
      traceName: "zendesk:4821",
    },
    async () => {
      await runAgent(thread);
    },
  );
  ```
</CodeGroup>

`session_id` groups traces into one session in Agent Registry. `trace_name`
overrides the display name of the trace, which is useful when the root span
name is generic but the run has a meaningful external label.

## Deterministic trace IDs

`create_trace_id(seed)` derives a trace ID from a stable external key. The
same seed always produces the same trace ID, in both SDKs, byte-identical. Use
it when a run is triggered by something that already has an id — a webhook, a
ticket, a queue message — so retries and multi-service handling converge on one
trace instead of fragmenting.

<CodeGroup>
  ```python Python theme={null}
  trace_id = atlan_ai.create_trace_id(seed="ISSUE-123")

  with client.start_as_current_span(
      "linear-webhook:ISSUE-123",
      trace_context={"trace_id": trace_id},
  ) as root:
      ...
  ```

  ```typescript TypeScript theme={null}
  const traceId = atlan.createTraceId("ISSUE-123");

  await client.startAsCurrentSpan(
    "linear-webhook:ISSUE-123",
    { traceContext: { traceId } },
    async (root) => {
      // ...
    },
  );
  ```
</CodeGroup>

Called with no seed, it returns a random ID. The derivation is
Langfuse-compatible, so a run already keyed by seed in Langfuse keeps the same
trace ID here.

## Record failures

`record_failure` / `recordFailure` marks the span as errored and records the
exception without re-raising it, so tracing never changes your control flow.

<CodeGroup>
  ```python Python theme={null}
  try:
      result = call_provider()
  except ProviderError as error:
      span.record_failure(error)
      raise
  ```

  ```typescript TypeScript theme={null}
  try {
    const result = await callProvider();
  } catch (error) {
    span.recordFailure(error);
    throw error;
  }
  ```
</CodeGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Add scores" icon="star" href="/sdk/scores">
    Attach evaluation results to a trace.
  </Card>

  <Card title="Privacy controls" icon="shield" href="/sdk/privacy">
    Mask payloads and suppress content.
  </Card>

  <Card title="Inspect a trace" icon="magnifying-glass" href="/objects/traces">
    What a trace looks like in Agent Registry.
  </Card>

  <Card title="Configuration" icon="gear" href="/sdk/configuration">
    Options, environment variables, and export modes.
  </Card>
</CardGroup>
