# Telemetry Privacy Controls

Control whether Droid telemetry carries per-user identity or message content, and see exactly what reaches Factory versus your own collector.

Two org-managed controls decide what Droid telemetry contains: `telemetry.granularity` for per-individual identity, and `telemetry.logMessageContent` for message content. This page covers both, and where each kind of data can go.

## What reaches Factory versus your collector

| Data | Factory's collector | Your collector |
| :--- | :------------------ | :------------- |
| Metrics | Yes (never in an [airgapped deployment](/enterprise/telemetry#airgapped-deployments)) | Yes, once an endpoint is configured. |
| Spans, including all message content | Never, in any configuration. | Only with a collector of your own, and content only with the opt-in below. |
| [`OTEL_RESOURCE_ATTRIBUTES`](/enterprise/telemetry/data-reference#resource-attributes) pairs | Never. | Yes. |

`telemetry.granularity` applies at the source, before export, so it binds Factory's collector and yours equally. `telemetry.logMessageContent` only controls whether message content is written to your own span export (Factory never receives spans).

## Data granularity

Some organizations operate under a works council agreement or a jurisdictional rule that forbids per-individual analytics. `telemetry.granularity` is the control for that, applied at the source so identifying data is never written rather than filtered downstream.

| Mode | Behavior |
| :--- | :------- |
| `user` (default) | Datapoints carry per-user identity. |
| `aggregate` | `user.id`, `user.email`, `commit.hash`, `pr.number`, and `pr.url` are removed from every datapoint on both collectors, and message content is never written regardless of any other setting. |

`organization.id` survives aggregate mode, because it is the dimension you aggregate over. So do `session.id`, `parent_session.id`, `model.id`, and every workload attribute such as `tool.name`, `repo.*`, and `skill.name`. Data in aggregate mode carries the resource attribute `telemetry.granularity: aggregate`, so a downstream consumer can tell which mode produced it.

Resolution is **most-restrictive-wins**, not a precedence order: either `telemetry.granularity` or `OTEL_TELEMETRY_GRANULARITY` asking for `aggregate` yields `aggregate`, and an organization set to `user` cannot override a machine set to `aggregate`. A value that cannot be read resolves to `aggregate`.

<Warning>
Aggregate mode overrides every content opt-in, including `telemetry.logMessageContent: true`. It also changes what spans you get:

- **`legacy` plus `aggregate` produces no spans at all.** The `droid.message.*` and `droid.tool.*` event spans are per-user by construction, so there is nothing left to strip them down to. Metrics still flow.
- **`genai` plus `aggregate` keeps the full trace**, with the four content attributes stripped. Use this combination if you need trace structure under a compliance mandate.
</Warning>

## Message content logging

Message content export is off by default. Metrics fan out to both Factory and your collector; message content does not.

The organization's controls are authoritative:

| Control | Effect |
| :------ | :----- |
| `telemetry.granularity: aggregate` in org-managed settings | Content is never exported, overriding every setting below. See [Data granularity](#data-granularity). |
| `telemetry.logMessageContent: true` in org-managed settings | Content is exported on every member's machine, whether or not they set `OTEL_LOG_MESSAGE_CONTENT`. |
| `telemetry.logMessageContent: false` in org-managed settings | Content is never exported, even on a machine that sets `OTEL_LOG_MESSAGE_CONTENT`. |
| Property omitted from org-managed settings | Each machine decides for itself with `OTEL_LOG_MESSAGE_CONTENT`. |

To enable message content export for the current process (for example, for session auditing) where the organization has left the decision open, set:

```bash
export OTEL_LOG_MESSAGE_CONTENT=true   # or 1
```

When enabled, Droid emits message content as OTEL **trace spans**, on the `/v1/traces` path of your endpoint. Where it lands depends on your [export format](/enterprise/telemetry/data-reference#export-formats).

Under `legacy`, content arrives as four span types:

- User messages (`droid.message.user`)
- Assistant responses (`droid.message.assistant`)
- Tool calls and their inputs (`droid.tool.call`)
- Tool results (`droid.tool.result`)

Under `genai`, the same content arrives as four attributes on the [GenAI spans](/enterprise/telemetry/data-reference#spans): `gen_ai.input.messages`, `gen_ai.output.messages`, `gen_ai.tool.call.arguments`, and `gen_ai.tool.call.result`.

<Warning>
  **Message content is exported raw, with no redaction.** There is no PII scrubbing and no secret detection; the only transformation is truncation of any single attribute to 32,000 characters. These spans contain verbatim prompts, responses, tool inputs (including file contents and command arguments), and tool results. Make your data-classification decision accordingly before enabling.
</Warning>

<Note>
**Spans are sent only to the collector you configure.** Droid does not fan out any span to Factory's collector.

- A customer endpoint (`telemetry.endpoint` or `OTEL_TELEMETRY_ENDPOINT`) is **required**. With none set, Droid builds no trace pipeline, so content is never written rather than being written and dropped.
- If your endpoint resolves to the same URL as Factory's collector, it does not count as a collector of your own: no span is recorded and content logging is disabled. Adding credentials to the URL does not make it distinct.
- Metrics export is unaffected and continues to fan out to both Factory and your collector.
</Note>

<RelatedLinks>
  <RelatedLink href="/enterprise/telemetry" title="Telemetry & Analytics">
    Configure the OTEL export these controls govern.
  </RelatedLink>
  <RelatedLink href="/enterprise/privacy-and-data-flows" title="Privacy & Data Flows">
    The full picture of what leaves a developer's machine and why.
  </RelatedLink>
</RelatedLinks>
