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

# Capture with OpenTelemetry

> Govern an estate that is already emitting telemetry. Point a collector at the receiver, add six attributes, and your spans become governed trajectories — no SDK, no proxy.

There are three ways to get an agent's work into Rotascale. Two of them require
a deliberate act: import the SDK, or route through the [MCP
proxy](/sdk/mcp-proxy). This one requires neither.

If your estate already emits OpenTelemetry, point a collector at the receiver and
your existing spans become [trajectories](/concepts/agent-governance) — with
taint propagated and authority context intact.

<Note>
  This is the path for agents you **already run**, rather than ones you are
  adopting us for. It is often the fastest way to see which agents in an
  organisation are ungoverned, because it needs nothing deployed alongside them.
</Note>

## Point a collector at it

The receiver speaks OTLP/JSON at the standard OTLP HTTP path:

```yaml theme={"system"}
exporters:
  otlphttp/rotascale:
    endpoint: https://your-deployment/v1/otel
    encoding: json
    headers:
      x-api-key: ${ROTASCALE_API_KEY}

service:
  pipelines:
    traces:
      exporters: [otlphttp/rotascale]
```

## Name the agent

One attribute is required. Set it once on the resource and every span inherits
it:

```python theme={"system"}
from opentelemetry.sdk.resources import Resource

resource = Resource.create({"rotascale.agent": "refund-bot"})
```

A span that names a different agent overrides the resource, because being
specific on one span is deliberate.

## What becomes a step

Two step kinds are inferred from standard OTel GenAI attributes, and the
asymmetry between them is the design:

| Signal                                                                  | Becomes     | Introduces taint |
| ----------------------------------------------------------------------- | ----------- | ---------------- |
| `gen_ai.operation.name` is `chat`, `text_completion`, `embeddings`…     | `llm_call`  | No               |
| `gen_ai.operation.name` is `execute_tool`, or `gen_ai.tool.name` is set | `tool_call` | **Yes**          |

`llm_call` is inferred freely because it introduces no taint — guessing wrong
costs a row in a trajectory. `tool_call` is inferred only from an unambiguous
signal, because it introduces taint and
[taint gates authority](/concepts/clean-context).

`retrieval` and `delegation` are **never** inferred. They taint, and no standard
OTel attribute identifies them unambiguously — a database span is not a
retrieval into an agent's context, and guessing that it is fabricates authority
context. Say so explicitly:

```python theme={"system"}
from rotascale.otel import govern, RETRIEVAL

with tracer.start_as_current_span("search_policies") as span:
    govern(span, kind=RETRIEVAL, source_ref="policy-corpus")
```

A span we cannot map is **skipped and reported**, never guessed into a step. The
response names what was dropped and why.

## The attributes

| Attribute              | Meaning                                            |
| ---------------------- | -------------------------------------------------- |
| `rotascale.agent`      | Agent slug. Required, on the span or the resource. |
| `rotascale.step.kind`  | Explicit step kind. Always wins over inference.    |
| `rotascale.grant`      | The grant this span exercised.                     |
| `rotascale.source_ref` | What untrusted content came from.                  |
| `rotascale.trusted`    | The customer attests this source is safe.          |
| `rotascale.discharges` | Source kinds a sanitise step discharges.           |
| `rotascale.goal`       | Goal for the trajectory, read from the root span.  |

<Warning>
  `rotascale.*` is a **holding prefix**. The mapping belongs upstream at
  OpenTelemetry rather than in a vendor's specification, and the
  [agent governance specification](https://github.com/rotascale/agent-governance-spec)
  deliberately declines to declare it. If it is accepted upstream, these names
  change.
</Warning>

## What it does not do

The receiver **records**; it does not authorise. Asking permission is a separate,
deliberate call — an OTLP receiver that quietly consumed budget would turn an
exporter misconfiguration into a spend event.

Retries are safe. Exporters resend on 5xx and timeouts, and duplicate spans are
recognised and ignored rather than appended twice.

Ordering is handled for you. OTLP makes no ordering promise and batch processors
flush on span *end*, so a nested trace arrives roughly inside-out; spans are
sorted by start time before anything is recorded. This matters more than it
sounds — appending a tool call after the action it should have tainted would
leave that action's recorded context reading clean.

## Closing a trajectory

The root span — the one with no parent — seals the trajectory when it ends. A
batch that does not contain it leaves the trajectory open on purpose: OTel
exports the root last, so a rootless batch is a trace still being written.
