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

# Telemetry

> OpenTelemetry tracing for agents and swarms — what is captured, and how to turn it on or off

## Overview

Swarms ships with optional [OpenTelemetry](https://opentelemetry.io) tracing. When enabled, every agent and swarm run emits a span recording what was asked, what came back, how long it took, and whether it failed — and those spans nest, so a swarm run and every agent run underneath it form a single connected trace.

Telemetry is **fail-safe by design**: if the exporter is unreachable, the configuration is wrong, or the dependency is missing, the instance goes inert and your agents keep running. It never raises into your code.

## Turning it on and off

Telemetry is controlled by a single environment variable, `SWARMS_TELEMETRY_ON`.

<CodeGroup>
  ```bash Off (default) theme={null}
  # Unset, or any value other than the three below
  unset SWARMS_TELEMETRY_ON
  ```

  ```bash On theme={null}
  export SWARMS_TELEMETRY_ON=true
  ```

  ```bash .env file theme={null}
  SWARMS_TELEMETRY_ON="true"
  ```
</CodeGroup>

Accepted "on" values are exactly `"True"`, `"true"`, and `"1"`. Anything else — including `"yes"`, `"TRUE"`, or an empty string — is off.

<Warning>
  The gate is read **once per process**, at first use. Changing `SWARMS_TELEMETRY_ON` at runtime has no effect until you restart. This is deliberate: it keeps the hot path to a single cached lookup.
</Warning>

<Note>
  A `.env` file in your working directory is loaded automatically on import. If telemetry appears to be on when you expected it off, check `.env` before checking your shell — the file populates the variable before the gate is read.
</Note>

### Verifying the current state

```python theme={null}
from swarms.telemetry.otel import swarm_telemetry, telemetry_on

print(telemetry_on())              # what the env gate says
print(swarm_telemetry().ready)     # whether the tracer actually initialized
```

`ready` can be `False` even when `telemetry_on()` is `True` — that means setup failed and telemetry silently disabled itself. Run with loguru at `DEBUG` level to see why.

## Performance cost

Measured overhead per decorated call:

| State                  | Overhead per `run()` | Notes                                                        |
| ---------------------- | -------------------: | ------------------------------------------------------------ |
| **Off**                |        **\~0.14 µs** | One cached lookup and a boolean check, then straight through |
| **On**, small payload  |              \~22 µs | Span creation, attributes, context attach                    |
| **On**, \~48 KB output |             \~100 µs | Cost scales with payload size, not span machinery            |

Every one of those numbers is dwarfed by a single LLM round trip (200 ms – several seconds). Even the worst case is roughly **0.05% of a 200 ms call**. Span export happens on a background thread via `BatchSpanProcessor`, so the network is never on your hot path.

The dominant cost when enabled is stringifying inputs and outputs for the span. Payloads are truncated to 16,000 characters (`SWARMS_OTEL_MAX_CHARS`), and captured constructor configs to 65,536 (`SWARMS_OTEL_MAX_CONFIG_CHARS`).

## What gets captured

### Spans

| Span                | Emitted when                           | Key attributes                                               |
| ------------------- | -------------------------------------- | ------------------------------------------------------------ |
| `<Component>.init`  | A component is constructed             | `swarms.config` — the full constructor configuration as JSON |
| `<Component>.run`   | `run()` is called                      | inputs, output, status, identity                             |
| `<Component>.error` | An error is caught and *not* re-raised | error type, message, context                                 |

Every span carries identity attributes:

| Attribute               | Meaning                                                           |
| ----------------------- | ----------------------------------------------------------------- |
| `swarms.component`      | Class name — `Agent`, `SwarmRouter`, `ConcurrentWorkflow`, …      |
| `swarms.name`           | The agent's `agent_name` or the swarm's `name`                    |
| `swarms.id`             | The component's `id`                                              |
| `swarms.swarm_type`     | Present on multi-agent structures only, never on a single `Agent` |
| `gen_ai.operation.name` | `"agent"` for a single agent, `"swarm"` for everything else       |

And run spans add:

| Attribute                                    | Meaning                                                              |
| -------------------------------------------- | -------------------------------------------------------------------- |
| `swarms.input.task`                          | The task passed in (also `.tasks`, `.img`, `.imgs` where applicable) |
| `swarms.output`                              | The return value, truncated                                          |
| `swarms.status`                              | `completed` or `error`                                               |
| `swarms.error.type` / `swarms.error.message` | Present on failures                                                  |

### Trace structure

Spans nest, including across the thread pools that concurrent swarms use:

```
SwarmRouter.run
└─ ConcurrentWorkflow.run
   ├─ Agent.run          (worker thread)
   └─ Agent.run          (worker thread)
```

One trace, one root, every agent attributable to the run that spawned it.

<Note>
  `SequentialWorkflow` delegates internally to `AgentRearrange`, so a sequential run shows an extra `AgentRearrange.run` span between the workflow and its agents. That is the real call path, not a duplicate.
</Note>

## Instrumented components

Every multi-agent harness and the `Agent` class itself:

<CardGroup cols={2}>
  <Card title="Agents" icon="robot">
    `Agent`
  </Card>

  <Card title="Workflows" icon="diagram-project">
    `SequentialWorkflow`, `ConcurrentWorkflow`, `AgentRearrange`, `GraphWorkflow`, `BatchedGridWorkflow`, `RoundRobinSwarm`
  </Card>

  <Card title="Orchestrators" icon="sitemap">
    `SwarmRouter`, `HierarchicalSwarm`, `MultiAgentRouter`, `PlannerWorkerSwarm`, `MixtureOfAgents`
  </Card>

  <Card title="Deliberation" icon="scale-balanced">
    `MajorityVoting`, `CouncilAsAJudge`, `DebateWithJudge`, `GroupChat`, `LLMCouncil`, `HeavySwarm`
  </Card>
</CardGroup>

## Configuration reference

| Variable                       | Default     | Purpose                                     |
| ------------------------------ | ----------- | ------------------------------------------- |
| `SWARMS_TELEMETRY_ON`          | unset (off) | Master on/off switch                        |
| `OTEL_SERVICE_NAME`            | `swarms`    | Service name attached to every span         |
| `SWARMS_OTEL_TIMEOUT`          | `8`         | Export timeout in seconds                   |
| `SWARMS_OTEL_MAX_CHARS`        | `16000`     | Max characters per input/output attribute   |
| `SWARMS_OTEL_MAX_CONFIG_CHARS` | `65536`     | Max characters for a captured `init` config |

## Instrumenting your own components

The same primitives are available for custom structures.

### The decorator

```python theme={null}
from swarms.telemetry.otel import capture_init, trace_run

class MySwarm:
    def __init__(self, agents, name="MySwarm"):
        self.agents = agents
        self.name = name
        capture_init(self)          # emits MySwarm.init with the full config

    @trace_run("MySwarm.run", input_params=("task", "img"))
    def run(self, task=None, img=None):
        ...                          # output and errors recorded automatically
```

`trace_run` opens the span, captures the named parameters, records the return value on success, and records any exception that propagates before re-raising it unchanged. You never need to touch the span yourself.

### Inline spans

When you need to attach extra attributes mid-run:

```python theme={null}
from swarms.telemetry.otel import capture_run

with capture_run("MySwarm.run", self, task=task) as span:
    result = do_work()
    span.set("myswarm.rounds", 3)
    span.record_output(result)
```

### Errors you swallow

`trace_run` and `capture_run` only see exceptions that propagate. For errors you catch and handle, record them explicitly:

```python theme={null}
from swarms.telemetry.otel import capture_error

try:
    result = agent.run(task)
except Exception as e:
    capture_error(e, self, agent=agent.agent_name)
    result = None       # swallowed to keep the swarm going
```

### Threads

OpenTelemetry's active-span context does not cross thread boundaries. If you dispatch agents to a pool, use the drop-in executor so their spans nest under the run that spawned them:

```python theme={null}
from swarms.telemetry.otel import ContextThreadPoolExecutor

with ContextThreadPoolExecutor(max_workers=8) as executor:
    futures = [executor.submit(agent.run, task) for agent in self.agents]
```

A plain `ThreadPoolExecutor` still works — the agents just show up as disconnected root traces instead of children.

## Capturing spans in tests

Point the tracer at an in-memory exporter instead of the network:

```python theme={null}
import os
os.environ["SWARMS_TELEMETRY_ON"] = "true"

import swarms.telemetry.otel as otel
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter

otel.TELEMETRY_BASE_URL = "http://127.0.0.1:9/dead"   # nothing leaves the machine
otel.swarm_telemetry.cache_clear()                    # rebuild with the new gate

telemetry = otel.swarm_telemetry()
memory = InMemorySpanExporter()
telemetry._provider.add_span_processor(SimpleSpanProcessor(memory))

agent.run("hello")

spans = memory.get_finished_spans()
run_span = next(s for s in spans if s.name == "Agent.run")
assert dict(run_span.attributes)["swarms.status"] == "completed"
```

`swarm_telemetry.cache_clear()` is the important line — without it you get the singleton built under the previous gate value.

## Privacy

Telemetry captures task text, agent outputs, and constructor configuration — including system prompts, which may contain proprietary instructions. If that matters for your deployment, leave `SWARMS_TELEMETRY_ON` unset. There is no partial mode: it is entirely on or entirely off.
