Telemetry
What telemetry Agent Swarm sends, which identifiers it carries (org, org email domain, pseudonymous user reference), and how to opt out
Agent Swarm sends usage telemetry to the maintainers to help them understand usage patterns and improve the product. Telemetry is on by default and you can turn it off at any time (see How to opt out). Setup does not ask you to opt in, and it has no telemetry toggle.
This page lists every field that leaves your install.
What is collected
Telemetry tracks lifecycle and operational events. It does not include task descriptions, prompts, outputs, agent names, email addresses, user names, or error messages. The only free-text values it carries are ones you set yourself, such as SWARM_ORG_NAME, and the email domain of your admin (see Identity context).
IDs below (taskId, agentId, workflowId) are random UUIDs generated by your install.
The table below is generated from the event catalog that the telemetry endpoint enforces (src/telemetry-contract). An event or property that is not listed here is rejected by the endpoint, and a build that emits one fails to compile. Events with the ui emitter are sent by the dashboard through the API, as described under Dashboard events. Of those, page.viewed, feature.used and ui.session_started are reserved and not sent by this release, and so are skill.invoked and session.steered.
Every event the ingest accepts with schema_version: 2. A v2 event that is not listed here, or that sends a property not listed for it, is rejected with a typed 400. Emitter is the envelope source.
trigger_surface
Surface that started a chain of work: agent-swarm's AgentTaskSourceSchema values plus other.
Values: slack, ui, api, mcp, github, gitlab, linear, jira, agentmail, schedule, workflow, system, other.
agent-swarm
Common properties, allowed on every event of this product:
| Property | Type | Description |
|---|---|---|
has_email_channel | (boolean) | An email channel is configured. |
has_embedding_key | (boolean) | An embedding API key is configured. |
has_notification_channel | (boolean) | A notification channel is configured. |
has_slack_channel | (boolean) | A Slack channel is configured. |
install_method | (string) | Legacy key kept during the v1 window; v2 carries context.install_method. |
is_cloud | (boolean) | Legacy key kept during the v1 window; v2 carries context.deployment. |
is_e2b | (boolean) | Legacy key kept during the v1 window; v2 carries context.deployment. |
swarmVersion | (string) | agent-swarm version. Legacy key kept during the v1 window; v2 carries context.swarm_version. |
| Event | Emitter | Since | Description | Properties |
|---|---|---|---|---|
agent.registered | api-server | 2026-09-30 | An agent registered or reconnected. | capabilities (array, required)isReconnect (boolean, required)role (string) |
compaction.triggered | worker | 2026-09-30 | The harness compacted a session context. | compactTrigger (string, required)contextTotalTokens (integer, required)preCompactTokens (integer, required) |
feature.used | ui | 2026-09-30 | A dashboard feature was used. | feature (string, required) |
fleet_benchmark.unlocked | ui | 2026-10-18 | An admin supplied the company profile fields for the full benchmark. | fields (array of website | team_size | use_case, required) |
fleet_benchmark.viewed | ui | 2026-10-12 | The fleet benchmark card was shown. | has_org_name (boolean, required)level (basic | full) |
identity.lookup | ui | 2026-10-18 | A dashboard identity lookup matched an existing user or found no match. | matched (boolean, required) |
identity.user_created | ui | 2026-10-18 | A user created a dashboard identity. Email and personal names stay out of telemetry. | email_kind (work | personal, required)has_company (boolean, required)has_function (boolean, required)surface (identity_modal | setup, required) |
integration.connected | api-server | 2026-09-30 | An integration was connected. | first_of_type (boolean, required)provider (string)type (string, required) |
lead.captured | api-server | 2026-09-30 | A consented lead (Teams waitlist or private-beta sign-up). The email stays in feedback_submissions. | channel (string, required)consent (boolean, required)submission_id (string, required) |
notification.dismissed | ui | 2026-09-30 | A notification-centre item was dismissed. | key (string, required)moment (task_failed | first_workflow | default) |
notification.submitted | ui | 2026-09-30 | A notification-centre item was submitted. | email_domain (string)key (string, required)moment (task_failed | first_workflow | default)user_function (engineer | eng_lead | founder | product | ops | other) |
notification.viewed | ui | 2026-09-30 | A notification-centre item was shown. | key (string, required)moment (task_failed | first_workflow | default) |
onboarding.completed | api-server | 2026-09-30 | Onboarding was completed. | seconds_since_install (integer)seconds_since_start (integer, required) |
onboarding.dismissed | api-server | 2026-09-30 | Onboarding was dismissed. | seconds_since_install (integer)seconds_since_start (integer, required) |
onboarding.first_task_completed | api-server | 2026-09-30 | The first task after onboarding completed. | method (string)seconds_since_install (integer)seconds_since_start (integer, required) |
onboarding.started | api-server | 2026-09-30 | Onboarding started. | existing_install (boolean, required)seconds_since_install (integer)seconds_since_start (integer, required) |
onboarding.step_completed | api-server | 2026-09-30 | An onboarding step was completed. | derived (boolean, required)method (string)seconds_since_install (integer)seconds_since_start (integer, required)step (string, required) |
onboarding.step_failed | api-server | 2026-09-30 | An onboarding step failed. | error_class (string, required)seconds_since_install (integer)seconds_since_start (integer, required)step (string, required) |
onboarding.step_skipped | api-server | 2026-09-30 | An onboarding step was skipped. | seconds_since_install (integer)seconds_since_start (integer, required)step (string, required) |
onboarding.step_viewed | api-server | 2026-09-30 | An onboarding step was shown. | seconds_since_install (integer)seconds_since_start (integer, required)step (string, required) |
org.profile_updated | ui | 2026-10-12 | An admin changed the org profile. Lists which fields changed, never their values. | fields (array of name | website | team_size | use_case | acquisition_source, required)surface (enum, required) |
org.snapshot | api-server | 2026-09-30 | Daily per-install user and agent counts. Counts only: no names, emails or IDs. | agents_total (integer ≥0, required)users_active_7d (integer ≥0, required)users_admin (integer ≥0, required)users_linked_github (integer ≥0, required)users_linked_gitlab (integer ≥0, required)users_linked_jira (integer ≥0, required)users_linked_linear (integer ≥0, required)users_linked_other (integer ≥0, required)users_linked_slack (integer ≥0, required)users_suspended (integer ≥0, required)users_total (integer ≥0, required)users_with_login (integer ≥0, required) |
page.viewed | ui | 2026-09-30 | A dashboard page was viewed. | route_template (string, required) |
popup.dismissed | ui | 2026-10-12 | A dashboard pop-up was closed without submitting. | method (later | close | escape, required)popup (feedback | org_name | used_by, required) |
popup.shown | ui | 2026-10-12 | A dashboard pop-up was shown. Telemetry-gated: never sent while telemetry is off. | popup (feedback | org_name | used_by, required)trigger (task_failed | tasks_completed | workflow_completed | quarterly | missing_org_name, required) |
popup.submitted | ui | 2026-10-12 | A dashboard pop-up was submitted. Content stays out of telemetry: only flags for what was filled. | answered (integer ≥0)has_contact (boolean, required)has_message (boolean, required)perks (array of used_by | slack_connect)popup (feedback | org_name | used_by, required)score (integer ≥0) |
ranking.opted_in | api-server | 2026-10-18 | An admin opted the install into the public ranking, or the API rebuilt its listing after an org identity change. | listing (string, required) |
ranking.opted_out | api-server | 2026-10-18 | The install left the public ranking: the admin turned it off, or the org name or website was cleared or changed. | domain (string, required) |
ranking.usage | worker | 2026-10-18 | Token usage of one session on an install opted into the public ranking. Sent next to session.cost. | cacheReadTokens (integer ≥0)cacheWriteTokens (integer ≥0)inputTokens (integer ≥0, required)model (string)outputTokens (integer ≥0, required)provider (string)totalTokens (integer ≥0, required) |
schedule.error | api-server | 2026-09-30 | A schedule failed to fire. | consecutiveErrors (integer, required)scheduleType (string, required)triggeredWorkflows (boolean, required)wasRecovered (boolean, required) |
schedule.executed | api-server | 2026-09-30 | A schedule fired. | scheduleType (string, required)triggeredWorkflows (boolean, required)wasRecovered (boolean, required) |
server.shutdown | api-server | 2026-09-30 | The API server shut down. | sessionsProcessed (integer, required)signal (string, required)uptimeMs (integer, required) |
server.started | api-server | 2026-09-30 | The API server started. | port (integer, required) |
session.cancelled | worker | 2026-09-30 | A session was cancelled. | agentId (string, required)durationMs (integer, required)errorCategory (string, required)model (string, required)provider (string, required)taskId (string, nullable, required)trigger_surface (enum, nullable, required)wasRateLimited (boolean, required) |
session.cost | worker | 2026-09-30 | Token usage and cost of one session. | agentId (string, required)cacheReadTokens (integer)cacheWriteTokens (integer)durationMs (integer, required)inputTokens (integer, required)isError (boolean, required)model (string, required)numTurns (integer, nullable, required)outputTokens (integer, required)provider (string, required)reasoningOutputTokens (integer)taskId (string, nullable, required)thinkingTokens (integer)totalCostUsd (number, required)trigger_surface (enum, nullable, required) |
session.ended | worker | 2026-09-30 | A worker or lead session ended. | agentId (string, required)durationMs (integer)taskId (string, nullable, required)tasksProcessed (integer, required)trigger_surface (enum, nullable, required) |
session.failure | worker | 2026-09-30 | A session failed. | agentId (string, required)durationMs (integer, required)errorCategory (string, required)model (string, required)provider (string, required)taskId (string, nullable, required)trigger_surface (enum, nullable, required)wasRateLimited (boolean, required) |
session.started | worker | 2026-09-30 | A worker or lead session started. | agentId (string, required)harnessProvider (string, required)model (string)role (string, required)taskId (string, nullable, required)trigger_surface (enum, nullable, required) |
session.steered | api-server | 2026-09-30 | A running session received a steering message. | by_kind (string, required)trigger_surface (enum, nullable, required) |
setup.worker_command_copied | ui | 2026-10-18 | An admin copied a worker command from the setup health popover. | harness (enum, required)target (compose | docker | kubernetes, required) |
skill.invoked | worker | 2026-09-30 | A skill was invoked in a session. | skill_name (string, required)skill_source (string, required)trigger_surface (enum, nullable, required) |
task.cancelled | api-server | 2026-09-30 | A task was cancelled. | agentId (string)durationMs (integer)previousStatus (string, required)taskId (string, required)task_source (enum, required)trigger_surface (enum, required) |
task.claimed | api-server | 2026-09-30 | An agent claimed a task. | agentId (string, required)taskId (string, required)task_source (enum, required)trigger_surface (enum, required) |
task.completed | api-server | 2026-09-30 | A task completed. | agentId (string)durationMs (integer)harnessVariant (string)harnessVersion (string)provider (string)taskId (string, required)task_source (enum, required)trigger_surface (enum, required) |
task.created | api-server | 2026-09-30 | A task was created. | harnessVariant (string)harnessVersion (string)hasParent (boolean, required)has_repo (boolean, required)priority (integer, required)provider (string)taskId (string, required)task_source (enum, required)trigger_surface (enum, required) |
task.failed | api-server | 2026-09-30 | A task failed. | agentId (string)durationMs (integer)harnessVariant (string)harnessVersion (string)provider (string)taskId (string, required)task_source (enum, required)trigger_surface (enum, required) |
task.session_completed | worker | 2026-09-30 | A worker session for a task finished. | agentId (string, required)durationMs (integer, required)exitCode (integer, required)harnessVariant (string)harnessVersion (string)isError (boolean, required)model (string, required)provider (string, required)taskId (string, nullable, required)trigger_surface (enum, nullable, required) |
task.started | api-server | 2026-09-30 | A task started running. | agentId (string, required)taskId (string, required)task_source (enum, required)trigger_surface (enum, required) |
task.superseded | api-server | 2026-09-30 | A task was superseded by another task. | agentId (string)durationMs (integer)harnessVariant (string)harnessVersion (string)provider (string)reason (string, required)taskId (string, required)task_source (enum, required)trigger_surface (enum, required) |
telemetry.enabled | api-server | 2026-10-12 | Telemetry was turned on. Sent once, on the first boot or re-init after opting in. | via (banner | settings | env | benchmark, required) |
ui.session_started | ui | 2026-09-30 | A dashboard session started. | none |
used_by.consent_changed | ui | 2026-10-12 | An admin granted or revoked the "used by" logo consent. The request itself goes to /v1/used-by. | granted (boolean, required)has_function (boolean)has_quote (boolean) |
workflow.completed | api-server | 2026-09-30 | A workflow run completed. | durationMs (integer)stepsCompleted (integer, required)stepsFailed (integer, required)workflowId (string, required) |
workflow.created | api-server | 2026-09-30 | A workflow was created. | nodeCount (integer, required)via (api | mcp)workflowId (string, required) |
workflow.deleted | api-server | 2026-09-30 | A workflow was deleted. | via (api | mcp)workflowId (string, required) |
workflow.failed | api-server | 2026-09-30 | A workflow run failed. | durationMs (integer)stepsCompleted (integer, required)stepsFailed (integer, required)workflowId (string, required) |
workflow.started | api-server | 2026-09-30 | A workflow run started. | nodeCount (integer, required)triggerType (string, required)workflowId (string, required) |
Fields on every event
Each event is a JSON envelope with these top-level fields:
schema_version— always2product— alwaysagent-swarmevent— the event name from the table aboveoccurred_at— timestampsource— the emitter (api-serverorworker)actor_mode— alwaysanonymousactor_anonymous_id— the installation ID (see Identifiers)context— the identity block described under Identity context
Every event also adds these to properties. They are the same facts context carries, kept under their older names while existing dashboards move over:
swarmVersion— the Agent Swarm versionis_cloud— boolean, whether the event came from a cloud-hosted swarm or a self-hosted install. True whenMCP_BASE_URLpoints at a host we operate orSWARM_CLOUDis set. The hostname itself is never sent.is_e2b— boolean, whether the process runs inside an E2B sandboxhas_embedding_key— boolean, whether an embedding-capable key (EMBEDDING_API_KEYorOPENAI_API_KEY) is configured, i.e. whether semantic memory search is likely enabledhas_slack_channel/has_email_channel/has_notification_channel— booleans indicating whether an outbound notification channel (Slack, AgentMail email, or either) is configured. Presence only — never a token, channel ID, or address.install_method— enum:onboard_interactive,onboard_noninteractive,e2b, ormanual. Which entry point produced this install.
And these to metadata:
transport(alwayshttps) andenvironment(developmentfor git checkouts,testforNODE_ENV=test, otherwiseproduction; override withDESPLEGA_TELEMETRY_ENV)is_cloud— boolean, fromSWARM_CLOUDorganization_id(optional) — yourSWARM_ORG_ID, when setorganization_name(optional) — yourSWARM_ORG_NAME, when setinstall_preset(optional) — the onboard wizard preset used (dev,content,research,solo, orcustom), when the install went through the wizard. Allow-listed against the wizard's known preset IDs — any other value (including free text accidentally placed inINSTALL_PRESET) is omitted rather than forwarded.install_created_at(optional) — ISO timestamp of when this install's identity was first minted, persisted once alongside the installation ID. Only set when this process genuinely mints a new installation ID; an existing installation ID with no stored anchor emits this field as absent, not back-filled with the current time — absence means "pre-existing install, anchor unknown". Usemin(occurred_at)perinstallation_idin ClickHouse as the real anchor for that population.
Identity context
context says which organization and which person an event belongs to, without carrying anything that identifies them directly.
| Field | What it holds | How it is derived |
|---|---|---|
org_id | An organization ID | SWARM_ORG_ID when it is set to a valid ID (cloud deployments). Otherwise a random org_ + 16 hex characters, minted once and stored as telemetry_org_id. |
org_name | The name you gave the organization | SWARM_ORG_NAME, when set. null otherwise. |
org_domain | The email domain of your admin, such as example.com | The domain of the earliest-created active user who has an admin grant, falling back to the earliest active user with an email. Lowercased, stored as telemetry_org_domain, and recomputed when a user changes and hourly. The address itself is never sent. |
org_website | The company website an admin declared, such as example.com | SWARM_ORG_WEBSITE, a bare hostname. Left out when unset. |
team_size | The organization's team size: 1, 2-10, 11-50 or 51+ | Not sent by this version. Reserved in the contract for the company profile. |
use_case | The organization's main use case: features, bugs, review, ops, docs or data | Not sent by this version. Reserved in the contract for the company profile. |
user_ref | A pseudonymous reference to the person who caused the event | u_ plus the first 32 hex characters of sha256(installation ID + ":" + user ID). Stable inside your install, different on every other install, and not reversible to the user. null for events nobody caused (server start, schedules, heartbeat). |
user_role | admin or member | From the user's role grants. null when user_ref is null. |
plan | self-host-free or cloud | cloud for a cloud deployment, else self-host-free. |
deployment | self-host, cloud or e2b | Where the process runs. |
swarm_version | The Agent Swarm version | package.json. |
install_method | How the install was produced | Same as the property of the same name. |
install_preset | The onboard wizard preset | Same as the metadata field. null when there is none. |
acquisition_source | How the organization discovered Agent Swarm | SWARM_ORG_ACQUISITION_SOURCE: search, github, social, colleague, newsletter, event, ai_assistant, hacker_news, or other. null when unset or invalid. |
team_size | The organization's team size | SWARM_ORG_TEAM_SIZE: 1, 2-10, 11-50, or 51+. Omitted when unset or invalid. |
use_case | The organization's main use case | SWARM_ORG_USE_CASE: features, bugs, review, ops, docs, or data. Omitted when unset or invalid. |
user_function | The actor's job function, separate from RBAC role | users.metadata.jobFunction: engineer, eng_lead, founder, product, ops, or other. Cached with the RBAC role and invalidated after user updates. Omitted without an actor or a valid function. |
Workers receive company profile settings through the resolved configuration at boot and during reload. They read telemetry_org_domain through the API. Workers do not query users for job functions. The API resolves those functions for events with user actors.
What is never sent: email addresses (only the domain of one address), user names, raw user IDs, task content, prompts or outputs.
Which surface started the work
Task and session events carry trigger_surface: the surface that started the whole chain of work (slack, ui, api, mcp, github, gitlab, linear, jira, agentmail, schedule, workflow, system, or other). It is the source of the root task. A Slack request that the lead splits into five worker tasks counts as six Slack tasks, and every task.* and session.* event of the chain reports slack. Tasks the swarm creates for itself, such as the heartbeat checklist and boot triage, are system. task_source keeps the raw source of each task on its own, for debugging delegation.
Daily organization snapshot
Once a day each install sends one org.snapshot event from the API server. It carries counts only: active and suspended users, admins, users who can log in, users with a linked Slack, GitHub, GitLab, Linear, Jira or other identity, users who requested a task in the last seven days, and registered agents. It carries no names, email addresses or IDs. The send time is stored as telemetry_last_snapshot_at, so a restart does not send a second one the same day.
Dashboard events
The dashboard never sends telemetry to Desplega itself. It posts each event to its own API (POST /api/telemetry/ui-event), and the API sends it like any server event, with the same context. The API accepts only the catalogued dashboard events, checks their properties against the catalog, and drops them when telemetry is off. The dashboard also holds them back until GET /status has loaded and reports telemetry on.
| Event | When | Properties |
|---|---|---|
notification.viewed | Someone opens the notification bell with an item in it | key: the notification |
notification.dismissed | Someone dismisses a notification card | key |
notification.submitted | Someone submits a notification card | key, and email_domain when they typed an email: the domain only, never the address |
popup.shown | The organization-name prompt or the feedback pop-up opens | popup (org_name, feedback) and trigger: why it opened |
popup.dismissed | It closes without being submitted | popup and method: later, close or escape |
org.profile_updated | An admin saves the organization name or website | fields (name, website: which ones changed, never their values) and surface: prompt, setup or settings |
used_by.consent_changed | An admin grants or revokes the "used by" logo consent | granted |
fleet_benchmark.viewed | The "Your swarm vs. similar swarms" card shows on Home, at most once per page load | has_org_name |
popup.submitted | It is submitted | popup, has_message and has_contact: whether a message or a name or email was filled in, never the text. The feedback card adds the 0-10 score |
telemetry.enabled (via: banner, settings, benchmark or env) is sent by the API server once per opt-in: on the first boot of an install with telemetry on, or when someone turns telemetry back on from the dashboard banner, the Home benchmark card, or the Configuration page. Turning it on that way applies without a restart.
While telemetry is off, admins see a dashboard banner that explains what turning it on sends. It cannot report that it was shown, since telemetry is off. After "Dismiss" it never comes back.
Fleet benchmark
With telemetry on and an organization name set, Home compares this swarm with other swarms over the last 30 days. It compares tasks per week and task success rate. The API reads fleet percentiles from GET https://proxy.desplega.sh/v1/fleet/benchmarks at most once a day.
When the swarm stores a website, team size, and use case, the API sends team_size and use_case to the proxy. The proxy compares against that peer group only when it meets the same minimum of 20 installs as the whole fleet. Otherwise, it uses the whole fleet. Within the selected fleet, comparisons use the same agent-count bucket when at least 20 installs qualify, otherwise all qualifying installs.
The request carries no installation ID or organization name. The response contains aggregates over at least 20 installs with 10 or more finished tasks. With telemetry off, the API never makes the request. The API computes the comparison locally, so this swarm's own numbers never leave it for the benchmark.
GET /api/fleet-benchmark adds level and peerGroup to its existing response. Level is full when website, team size, and use case are set. Otherwise, level is basic. Full requests include team_size and use_case query parameters. The fleet cache key includes these peer filters.
peerGroup contains {teamSize, useCase, orgs} when the proxy returns a matching group. It is null when the proxy returns the whole fleet or omits peer_group. With telemetry off, local contains DB counts for the last 30 days, without a proxy request or ingest requirement. Existing comparisons still require a known proxy ingest date.
Public ranking
agent-swarm.dev/ranking ranks installs by tokens used, like OpenRouter's app rankings. It is off by default (PUBLIC_RANKING_ENABLED=false). Opting in is all it takes: no PR and no approval step. An install is listed when all of these hold:
- Telemetry is on.
- The organization name (
SWARM_ORG_NAME) and company website (SWARM_ORG_WEBSITE) are set. The placeholdersSwarmandYour Swarmdo not count as a name. - An admin clicked Get ranked in Settings → Configuration → Branding.
- The install sent telemetry in the last 7 days and used tokens in the period shown.
The install shows at the proxy's next hourly refresh. If two installs list the same website, the one that opted in first holds the entry. Desplega can hide a domain that abuses the ranking.
Exactly these fields leave the install for the ranking:
| Event | Sent by | Fields |
|---|---|---|
ranking.opted_in | API, on Get ranked and when the organization name changes | listing: the organization name and website, the category and subcategory you picked, the optional description and source code URL, and the time it was built. The same JSON is stored as the telemetry_ranking config key. |
ranking.opted_out | API, when the setting turns off | domain |
ranking.usage | Each worker, after every session with cost data | totalTokens, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, and the session's model and provider. No task, agent or cost. |
Token totals are public and exact. The page shows your total tokens per day, week and month, and tokens per model, the same as OpenRouter.
To leave, turn the setting off, turn telemetry off, or clear the organization name or website. Changing the website also opts out; click Get ranked again to list the new domain. The entry disappears at the proxy's next hourly refresh.
A notification card that asks for your email, and the feedback dialog, are different. Those send what you type to the feedback endpoint (https://proxy.desplega.sh/v1/feedback, overridable with the feedback_endpoint config key), and only when you submit them. They are not telemetry, so the opt-out does not block them.
The feedback card asks self-hosted admins one 0-10 question. It shows once after a completed workflow run, after 10 completed tasks on an install at least 3 days old, or in the session after the one that first saw a failed task, and then at most once a quarter. A tap on a score sends it; a follow-up question and contact details are optional. With telemetry off, the card still works, but it sends no installation ID, and sends the organization name only when you tick "Include my organization name".
Identifiers
- Installation ID. Each installation generates a random, opaque identifier (e.g.
install_a1b2c3d4e5f6g7h8), stored in theswarm_configtable. It is not derived from your hostname, domain, or account, and it stays the same across restarts so events from one install link together. - Organization identity. The installation ID is random, but events are not anonymous to your organization. Every event carries an organization ID (
org_id), the organization name when you setSWARM_ORG_NAME, the company website when you setSWARM_ORG_WEBSITE(org_website), and the email domain of your admin (org_domain), so they are attributable to your organization. Cloud deployments setSWARM_ORG_ID. The setup wizard's "name your swarm" step and the dashboard's organization-name prompt setSWARM_ORG_NAME. LeaveSWARM_ORG_NAMEunset, or opt out, if you do not want to share an organization name. - People. Events caused by a person carry a pseudonymous
user_ref, never an email address or a name. See Identity context. - No IPs logged. The telemetry endpoint (
proxy.desplega.sh) does not store client IP addresses. - No auth required. Events are sent in anonymous actor mode with no authentication headers.
Installation ID mechanism
On first startup, the telemetry module checks swarm_config for a telemetry_installation_id key:
- If found, it reuses the existing ID for consistent tracking across restarts.
- If not found, it generates a new random ID (
install_+ 16 hex characters) and persists it. - If config access fails (e.g. worker without DB access), an ephemeral ID (
ephemeral_prefix) is generated for that session only.
Workers access the installation ID via HTTP (respecting the DB boundary invariant), while the API server reads it directly from the database.
The API server also stores the organization ID as telemetry_org_id (and the admin's email domain as telemetry_org_domain) next to the installation ID. Workers read both through the same HTTP config call, so events from workers and from the server carry the same organization.
A telemetry_installed_at timestamp is minted alongside the installation ID, but only when this process genuinely mints a new installation ID — it anchors "when this install came into being" independent of server restarts. An existing installation ID with no stored anchor (an install that predates this field) leaves it unset, and the field is omitted from events rather than back-filled with the current time; use min(occurred_at) per installation_id in ClickHouse as the real anchor for that population.
How to opt out
Set the ANONYMIZED_TELEMETRY environment variable to false:
ANONYMIZED_TELEMETRY=falseFor Docker workers, also add it to .env.docker:
ANONYMIZED_TELEMETRY=falseYou can also set it in the dashboard under Settings → Configuration → Telemetry & observability. A value stored there takes precedence over the deployment environment.
Two things reach Desplega with telemetry off, because each is an explicit action: feedback you submit, and the "Show our logo on agent-swarm.dev" consent, which sends your organization name and website when you change it. A company website also points the dashboard logo at https://proxy.desplega.sh/v1/logo, so browsers that show the dashboard load the logo from Desplega's proxy; set your own SWARM_ORG_LOGO_URL to avoid that.
When disabled:
- No events are sent
- No installation ID is generated or stored
- The telemetry module initializes as a no-op
- The dashboard stops sending notification events.
GET /statusreports the effective state astelemetry.enabled, which the dashboard reads. A server that predates that field is treated as enabled.
Technical details
- Endpoint:
POST https://proxy.desplega.sh/v1/events - Timeout: 5 seconds per request
- Failure mode: Fire-and-forget — telemetry never throws exceptions or blocks operations
- Module:
src/telemetry.ts(importable from both API server and workers)
Related
- Environment Variables — Full reference for
ANONYMIZED_TELEMETRYand all configuration options - Deployment Guide — How to set environment variables in production Docker deployments
- Architecture Overview — How the API server and workers fit together in the overall system