---
title: Tracing Your Code
description: Decorators and context managers to trace your AI agent.
---

# Tracing Your Code

Three decorators and a context manager. That's the whole API.

Each one creates a **span** — a unit of work with a name, timing, status, and optional inputs/outputs. Spans nest automatically: a `@tool` called inside an `@agent` shows up as a child in the trace tree.

## `@st.agent`

Your agent's entry-point.

```python
@st.agent(name="support-agent", tags=["tier-1", "billing"])
def handle_request(user_message: str) -> str:
    ...
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `name` | `str` | Function name | Span name shown on dashboard |
| `tags` | `list[str]` | `None` | Tags attached to span metadata |
| `metadata` | `dict` | `None` | Custom metadata merged into span |
| `capture_input` | `bool` | `True` | Capture function arguments |
| `capture_output` | `bool` | `True` | Capture return value |

## `@st.tool`

Any tool or function your agent calls. Automatically captures **arguments and return value** — you'll see them on the dashboard.

```python
@st.tool(name="search_kb")
def search_knowledge_base(query: str, limit: int = 10) -> str:
    return db.search(query, limit=limit)
```

Dashboard shows:
- **Input**: `{"query": "reset password", "limit": 10}`
- **Output**: `"To reset your password, go to Settings > ..."`

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `name` | `str` | Function name | Span name shown on dashboard |
| `tags` | `list[str]` | `None` | Tags attached to span metadata |
| `metadata` | `dict` | `None` | Custom metadata merged into span |
| `capture_input` | `bool` | `True` | Capture function arguments |
| `capture_output` | `bool` | `True` | Capture return value |

## `@st.trace`

For anything that doesn't fit `@agent` or `@tool` — chains, retrievers, preprocessing.

```python
@st.trace(name="classify_intent", kind="chain")
def classify_intent(message: str) -> str:
    ...

@st.trace(name="fetch_context", kind="retriever")
def fetch_context(user_id: str) -> dict:
    ...
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `name` | `str` | Function name | Span name shown on dashboard |
| `kind` | `str` | `"chain"` | Span kind (see below) |
| `tags` | `list[str]` | `None` | Tags attached to span metadata |
| `metadata` | `dict` | `None` | Custom metadata merged into span |
| `capture_input` | `bool` | `True` | Capture function arguments |
| `capture_output` | `bool` | `True` | Capture return value |

Span kinds: `agent`, `tool`, `chain`, `llm`, `retriever`, `custom`

## `st.span()`

Context manager for manual span creation. Use this when you need to set custom fields on the span directly.

```python
with st.span("my-operation", kind="tool") as s:
    s.metadata["custom_key"] = "custom_value"
    result = do_work()
    s.output = {"result": result}
```

The span object gives you direct access to:

```python
with st.span("llm-call", kind="llm") as s:
    s.model = "gpt-4o"
    s.input = {"prompt": "Hello"}
    s.output = {"response": "Hi there"}
    s.input_tokens = 5
    s.output_tokens = 3
    s.total_tokens = 8
    s.metadata["temperature"] = 0.7
```

| Parameter | Type | Default |
|-----------|------|---------|
| `name` | `str` | *required* |
| `kind` | `str` | `"chain"` |

## Annotations

Attach evaluation scores, labels, or flags to traces and spans.

### On a span

Inside an `st.span()` context, call `annotate()` directly on the span:

```python
with st.span("evaluate") as s:
    result = run_evaluation()
    s.annotate("accuracy", value=0.95)
    s.annotate("category", value="billing", data_type="categorical")
    s.annotate("approved", value=True, data_type="boolean", comment="Passed review")
```

### Standalone

Annotate any trace by ID after the fact:

```python
st.annotate(
    trace_id="abc-123",
    name="quality_score",
    value=4.5,
    data_type="numeric",
    comment="Rated by human reviewer",
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `trace_id` | `str` | *required* | The trace to annotate |
| `name` | `str` | *required* | Annotation name |
| `value` | `float \| str \| bool` | *required* | Annotation value |
| `data_type` | `str` | `"numeric"` | `"numeric"`, `"categorical"`, or `"boolean"` |
| `span_id` | `str \| None` | `None` | Scope to a specific span |
| `comment` | `str \| None` | `None` | Optional comment |

The value type must match the data type — `float`/`int` for numeric, `str` for categorical, `bool` for boolean.

## Disabling Capture

All three decorators capture inputs and outputs by default. To disable:

```python
@st.agent(name="my-agent", capture_input=False, capture_output=False)
def handle_request(sensitive_data: str) -> str:
    ...
```

You still get timing, status, and error tracking — just no argument or return value recording.

## Async

All decorators work with async functions. No changes needed:

```python
@st.tool(name="search")
async def search(query: str) -> str:
    return await db.async_search(query)
```

## Nesting

```python
@st.agent(name="my-agent")
def run(query: str) -> str:
    context = search(query)
    intent = classify(query)
    return respond(context, intent)

@st.tool(name="search")
def search(query: str) -> str: ...

@st.trace(name="classify", kind="chain")
def classify(query: str) -> str: ...
```

Dashboard:

```
my-agent (agent)
├── search (tool)
└── classify (chain)
```

## Errors

Decorated functions that raise exceptions: the SDK records the error on the span and **re-raises it**. Your error handling is never affected.

```python
@st.tool(name="api_call")
def call_external_api() -> str:
    raise ConnectionError("timeout")

try:
    call_external_api()  # Span shows error, exception still propagates
except ConnectionError:
    handle_failure()
```
