Telemetry (OpenTelemetry)

Optional OpenTelemetry export to your own collector — off by default. Nothing is sent to us.

Nothing is sent to us. Synapse has no analytics and no usage reporting. It can export traces and metrics to an OpenTelemetry collector you run — off by default, and only when you turn it on.

Turn it on

The Docker images already carry the telemetry packages. Anywhere else, install them into the environment Synapse runs from:

# A one-command, npm or pip install
synapse upgrade --with telemetry

# A source checkout
pip install -r backend/requirements-telemetry.txt

synapse upgrade --with telemetry keeps the version you run, and the packages stay through every later synapse upgrade.

Then set one Synapse switch and restart:

SettingEffect
OTLP_ENDPOINT=http://collector:4317On, exporting to that collector
The Scale tab's OTLP EndpointThe same, saved in settings (applies on restart)
SYNAPSE_TELEMETRY=otlpOn, exporting to OTEL_EXPORTER_OTLP_ENDPOINT
SYNAPSE_TELEMETRY=offOff, whatever else is set
OTEL_SDK_DISABLED=trueOff, whatever else is set

The standard OTEL_* variables only tune an export one of those switches started: the protocol (OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf; gRPC by default), headers (OTEL_EXPORTER_OTLP_HEADERS, for a hosted collector's API key), the sampler (OTEL_TRACES_SAMPLER), the service name and resource attributes. A machine that already exports another application's telemetry with OTEL_EXPORTER_OTLP_ENDPOINT set will not start sending Synapse's there. There is no default endpoint, not even localhost.

METRICS_TOKEN serves Prometheus metrics at GET /api/v2/metrics (a Bearer token) — pull only; nothing is sent. It needs the same packages.

What you get

  • Traces: one span per HTTP request (named by its route template, never its path), per run and per step, per chat turn and agent loop, per model call (tokens and cost, retries as events), per tool call (refused or not, and why as a code), per compaction; database and Redis calls, and outbound HTTP calls. In scale mode a queued job continues the trace of the request that queued it.
  • Metrics: runs and chat turns by how they ended, run/step/turn/job durations, model calls, latency and tokens by model, tool calls by outcome, webhook deliveries, dead-lettered jobs, the queue's depth.
  • Logs: SYNAPSE_LOG_FORMAT=json writes JSON lines carrying the trace and span ids and the run, session and request ids. x-request-id is kept (or set to the trace id) and returned on every response.

What is exported — and what never is

A span carries ids, timings, counts, cost, step types and tool names — never a prompt, an answer, a tool's arguments or result, a file name, a URL, a query string, a database statement or an error message. Every span is rebuilt from the list below before it leaves the process; anything else — including whatever an instrumentation library would have recorded — is dropped. An outbound call's host is kept only for a service on your own network; any other host is reported as external. An exception keeps its type, never its message.

AttributeWhat it holds
synapse.org_idthe workspace (tenant) id; Cloud only — a self-hosted install has one
synapse.run_ida workflow run's id
synapse.root_run_idthe id of the run a nested run was started by
synapse.session_ida chat conversation's id
synapse.agent_idthe agent's id
synapse.orchestration_idthe workflow's id
synapse.step.ida step's id within its workflow
synapse.step.typea step's type: agent, llm, tool, parallel, loop, human, …
synapse.statushow a run, step or turn ended: completed, failed, paused, cancelled
synapse.triggerwhat started a run: run, resume, resume_failed
synapse.cost_usdthe model cost of a call or a run, in US dollars
synapse.stepshow many steps a run executed
synapse.turnthe agent loop's turn number
synapse.tool.namethe tool's name, as the agent sees it
synapse.tool.categorywhere the tool runs: builtin, mcp, http, python, delegate, subtask, builder
synapse.tool.refusedtrue when policy, an allowlist or a person refused the call
synapse.tool.refusal_kindwhy, as a code: not_allowed, policy, person, memory, db_allowlist
synapse.job.namethe queued job's function name (scale mode)
synapse.job.idthe queued job's id (scale mode)
synapse.job.trywhich attempt at the job this is (scale mode)
synapse.request_idthe request's x-request-id, or the trace id when none was sent
synapse.schedule_idthe schedule that started the work
synapse.surfacewhere a chat came from: app, api, widget, channel
synapse.model.bucketthe model, or own-key:<provider> for a model name a user typed
synapse.tokens.cache_readprompt tokens served from the provider's cache
synapse.tokens.cache_writeprompt tokens written to the provider's cache
synapse.retry.attempta model call's retry number (on a retry event)
synapse.retry.reasonwhy a model call was retried, as a code (on a retry event)
synapse.compaction.messages_beforemessages in the context before a compaction
synapse.compaction.messages_aftermessages in the context after a compaction
synapse.cancelledtrue when the work was stopped rather than finished
gen_ai.provider.namethe model provider: openai, anthropic, gemini, bedrock, …
gen_ai.systemthe model provider (the older name for gen_ai.provider.name)
gen_ai.operation.namechat
gen_ai.request.modelthe model's name as configured (metrics use the model bucket instead)
gen_ai.usage.input_tokensprompt tokens
gen_ai.usage.output_tokenscompletion tokens
gen_ai.response.finish_reasonswhy the model stopped: stop, tool_calls, length, …
gen_ai.request.max_tokensthe response cap sent to the model
gen_ai.request.temperaturethe sampling temperature sent to the model
gen_ai.request.top_pthe nucleus-sampling value sent to the model
http.request.methodGET, POST, …
http.response.status_codethe HTTP status
http.routethe route's template, e.g. /api/orchestrations/{orch_id} — never the path
url.schemehttp or https
network.protocol.versionthe HTTP version
server.addressan internal service's host name, or 'external' for any other host
server.portan internal service's port
error.typean error's class name or HTTP status — never its message
exception.typean exception's class name (on an exception event) — never its message
exception.escapedwhether the exception left the span
db.systempostgresql, sqlite or redis
db.system.namepostgresql, sqlite or redis (the newer name)
db.operationSELECT, INSERT, … or a Redis command — never the statement
db.operation.nameSELECT, INSERT, … or a Redis command (the newer name)
db.redis.database_indexthe Redis database number
messaging.systemarq
messaging.operation.typesend or process
messaging.destination.namethe queue's name
rpc.systemaws-api
rpc.servicethe AWS service, e.g. BedrockRuntime
rpc.methodthe AWS operation, e.g. Converse
cloud.regionthe AWS region
aws.regionthe AWS region (the older name)
aws.request_idthe AWS request id
retry_attemptshow many times the AWS SDK retried

Metric dimensions are low-cardinality only — a step type, a status, a job name, a tool category, a model bucket (a user-typed model name is reported as own-key:<provider>). Never a run, session or organisation id.

Try it locally

docker run -d --name jaeger -p 16686:16686 -p 4317:4317 jaegertracing/jaeger:2.21.0
OTLP_ENDPOINT=http://localhost:4317 synapse start

Open http://localhost:16686 and search service synapse-api (or synapse-worker), tag synapse.run_id=<a run's id>.

On this page