> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://respan.ai/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://respan.ai/docs/_mcp/server.

# Apache Burr (tracing)

> Trace Apache Burr applications, state-machine actions, custom spans, streams, and failures with Respan.

[Apache Burr](https://github.com/apache/burr) is a Python framework for building state-machine applications. Respan attaches a Burr lifecycle adapter when an `ApplicationBuilder` builds an application, preserving Burr's application, action, state, custom-span, and stream concepts.

#### Set up Respan

Create an account at [platform.respan.ai](https://platform.respan.ai) and grab an [API key](https://platform.respan.ai/platform/gateway/api-keys).

Run `npx @respan/cli setup` to set up with your coding agent.

#### Example projects

* [Complete Python examples](https://github.com/respanai/respan-example-projects/tree/main/python/tracing/burr)
* [State-machine execution](https://github.com/respanai/respan-example-projects/blob/main/python/tracing/burr/01_state_machine.py)
* [Custom spans, logged attributes, and expected failure](https://github.com/respanai/respan-example-projects/blob/main/python/tracing/burr/02_custom_span_and_failure.py)

## Setup

#### Install packages

```bash
pip install respan-ai respan-instrumentation-burr apache-burr
```

#### Set your API key

```bash
export RESPAN_API_KEY="YOUR_RESPAN_API_KEY"
```

#### Initialize and run

Initialize Respan before calling `ApplicationBuilder.build()`.

**`Python`**

```python Python
from burr.core import ApplicationBuilder, Result, State, action, default, expr
from respan import Respan
from respan_instrumentation_burr import BurrInstrumentor

respan = Respan(
    instrumentations=[BurrInstrumentor()],
)


@action(reads=["count"], writes=["count"])
def increment(state: State) -> State:
    return state.update(count=state["count"] + 1)


result = Result("count").with_name("result")

app = (
    ApplicationBuilder()
    .with_identifiers(app_id="counter-app", partition_key="user-42")
    .with_actions(increment, result)
    .with_transitions(("increment", "increment", expr("count < 2")))
    .with_transitions(("increment", "result", default))
    .with_entrypoint("increment")
    .with_state(count=0)
    .build()
)

try:
    _, _, state = app.run(halt_after=["result"])
    print(state["count"])
finally:
    respan.shutdown()
```

#### View your trace

Open the [Traces page](https://platform.respan.ai/platform/traces) to inspect the application workflow and its action spans.

## What gets traced

| Burr lifecycle                   | Respan representation | Burr details                                                                  |
| -------------------------------- | --------------------- | ----------------------------------------------------------------------------- |
| Application execution            | `workflow` span       | Execution method, initial/final state, application ID, partition key          |
| Action step                      | `task` span           | Action name, reads, writes, tags, declared inputs, sequence ID, result, state |
| `ActionSpan`                     | Nested `task` span    | Custom span name, action, sequence ID, dependencies                           |
| Stream lifecycle                 | Span events           | Start, each item index, and end                                               |
| `TracerFactory.log_attributes()` | Span metadata         | Logged values and tags                                                        |

Burr application IDs are mapped to Respan trace groups. Partition keys are mapped to thread identifiers, allowing executions for the same logical partition to be correlated.

## Custom spans and attributes

Burr actions that accept `__tracer: TracerFactory` keep their native custom spans and dependencies:

**`Python`**

```python Python
from burr.core import State, action
from burr.visibility import TracerFactory


@action(reads=["payload"], writes=["result"])
def process_payload(state: State, __tracer: TracerFactory) -> State:
    with __tracer("parse_payload") as span:
        words = state["payload"].split()
        span.log_attributes(word_count=len(words), parser="split")

    with __tracer("build_result", span_dependencies=["parse_payload"]):
        result = {"word_count": len(words)}

    return state.update(result=result)
```

## Content controls

Set `capture_content=False` to omit state, action inputs and results, stream items, and logged attribute values:

```python
respan = Respan(
    instrumentations=[BurrInstrumentor(capture_content=False)]
)
```

Application/action identity, state-machine structure, custom span names and dependencies, stream item indexes, identifiers, and status remain available.

## Lifecycle

The instrumentor patches `ApplicationBuilder.build()` and appends one Respan lifecycle adapter. Applications built before activation are not retrofitted. Duplicate adapters are avoided when other Burr lifecycle adapters are already present.

Call `respan.shutdown()` to flush telemetry and deactivate instrumentation. Applications built while the instrumentor was active retain their adapter object, but it is disabled after the last active instrumentor is deactivated.