Skip to main content

Overview

Swarms ships with OpenTelemetry tracing, enabled by default. 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. Spans are exported over OTLP/HTTP to the Swarms telemetry collector (https://swarms-telemetry-capturer-production.up.railway.app/v1/traces). The endpoint is fixed in the source: the standard OTEL_EXPORTER_OTLP_* endpoint variables do not redirect it. To also send spans to your own backend, see Sending spans to your own backend.

Turning it on and off

Telemetry is controlled by a single environment variable, SWARMS_TELEMETRY_ON. Telemetry is on by default. It must be switched off explicitly — leaving the variable unset leaves it enabled.
Recognized off values, case-insensitive and whitespace-tolerant: false, 0, no, off, disable, disabled. An empty or whitespace-only value also counts as off, so SWARMS_TELEMETRY_ON= in a .env file disables telemetry rather than silently enabling it. Anything else — true, 1, yes, or an unrecognized string — leaves it on.
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.
A .env file is loaded automatically on import. Swarms searches upward from the current working directory for the first .env it finds. Values already set in your shell take precedence over the file. If telemetry appears to be on when you expected it off, check both.

Verifying the current state

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: 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

Every span carries identity attributes: And run spans add:

Trace structure

Spans nest, including across the thread pools that concurrent swarms use:
One trace, one root, every agent attributable to the run that spawned it.
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.

Instrumented components

Each of these classes emits an init span from its constructor and a span for every call to the entry point listed:

Agents

Agent (run)

Workflows

SequentialWorkflow, ConcurrentWorkflow, AgentRearrange, GraphWorkflow, BatchedGridWorkflow, RoundRobinSwarm (run); SpreadSheetSwarm (run and run_from_config)

Orchestrators

SwarmRouter, HierarchicalSwarm, MultiAgentRouter, PlannerWorkerSwarm, MixtureOfAgents, ModelRouter, SocialAlgorithms (run)

Deliberation

MajorityVoting, CouncilAsAJudge, DebateWithJudge, GroupChat, LLMCouncil, HeavySwarm, SelfMoASeq (run)

Builders

AutoSwarmBuilder (run); AutoAgentBuilder (build_configs, which run calls)

Decision models

DecisionModel (run, capturing the state input)
AutoSwarmBuilder.run is the parent of the SwarmRouter.run it delegates to, so the build phase and the swarm it builds share one trace. ModelRouter dispatches through ContextThreadPoolExecutor, so its agents nest under the router’s span. Classes not in this list, such as ReasoningDuo or TreeOfThoughts, emit no spans of their own. Their inner Agent.run calls are still traced.

Configuration reference

The OTLP endpoint is not configurable through environment variables. Spans go to the Swarms collector whenever telemetry is on.

Sending spans to your own backend

The tracer provider is an ordinary OpenTelemetry TracerProvider, so you can attach another span processor to it. Your exporter receives every span in addition to the Swarms collector:
_provider is a private attribute, and adding a processor does not remove the built-in exporter. If you need spans to reach only your own backend, this is not supported: set SWARMS_TELEMETRY_ON=false and instrument your application with your own tracer instead.

Instrumenting your own components

The same primitives are available for custom structures.

The decorator

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:

Errors you swallow

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

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:
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:
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. With autosave=True, it also captures Agent and AgentRearrange state snapshots. All of it is sent to the Swarms telemetry collector described in the overview. Because it is on by default, opt out explicitly if any of that is sensitive in your deployment:
There is no partial mode: it is entirely on or entirely off.