Wytness Collector

The Collector is a container you run yourself. It polls the Azure logging stores you already have — Application Insights / Log Analytics rows from Microsoft Foundry server-side tracing and Copilot Studio telemetry — turns each row into a Wytness event, signs it with your Ed25519 key and PII-tokenizes it inside your own boundary, then POSTs signed envelopes to the ingest endpoint. It's how you capture agents that never call an SDK directly. The same container can also run Radar discovery sources — identity, proxy and vendor usage logs read for the fingerprints of AI services.

How it works

  1. Polls each configured source on a cadence and reads the new rows since its last cursor.
  2. Converts each row into a Wytness event, deterministically keyed so re-reads never duplicate.
  3. Signs and PII-tokenizes the event inside your container, reusing the same wytness-ai crypto the SDK uses — your keys and secrets never leave your boundary.
  4. POSTs the signed envelope to your ingest endpoint and advances the cursor.

The collected tier

Collector events carry the collected assurance tier: proof the log content is untouched since pickup. This is deliberately below — and never conflated with — the SDK's sdk-signed tier, which attests an action at the moment it happened. The dashboard labels each tier distinctly so an auditor always knows which guarantee applies.

Setting it up

Seven steps: five things to gather, then the container, then a check. Everything except the container is a one-off, and every credential stays on your side — Wytness never receives your Azure keys, your signing private key or your PII secret.

  1. Let the collector read your logs — export switch + a read-only service principal
  2. Create a dedicated API key — WYTNESS_API_KEY
  3. Generate a dedicated signing keypair — WYTNESS_SIGNING_KEY
  4. Copy your PII tokenization keys — WYTNESS_PII_PUBKEY + WYTNESS_PII_SECRET
  5. Describe your sources — COLLECTOR_SOURCES
  6. Run the container — Docker, Compose, Container Apps, ACI or Kubernetes
  7. Check it's working — heartbeat on the Collectors page, events on Events

Before step 1, clear the network path with your platform team — everything the collector does is outbound.

Network requirements

The collector needs four outbound HTTPS destinations and zero inbound rules(two more only if you add vendor usage discovery sources). Hand this table to your network team before anything else — it is the only firewall change the whole setup needs.

DirectionDestinationPortPurpose
Outboundlogin.microsoftonline.com443Entra token for the service principal (step 1)
Outboundapi.loganalytics.io443Reading the Log Analytics workspace
Outboundapi.wytness.ai (or your WYTNESS_ENDPOINT)443Shipping signed events and discovery observations; syncing the signature library
Outboundapi.openai.com / api.anthropic.com443Only with an openai_usage / anthropic_usage discovery source — the vendor's usage API
Outboundghcr.io + pkg-containers.githubusercontent.com443Image pull — or mirror the image into your own registry
Inboundnone—/healthz serves only your own probes
Note
Reading logs is outbound, not inbound: the collector polls — it opens the connection to Log Analytics and the query results come back on that same connection. Nothing ever connects to the collector, which is why no inbound rule, ingress or public IP is needed anywhere below.

1. Let the collector read your logs

Turn on the log export for each product, then create one identity that can read the workspace. Telemetry is OFF by default at the source for both products — if a source later reads silent, this switch is the first thing to re-check.

  • Microsoft Foundry. Connect an Application Insights resource to the project — portal: project → Tracing → Connect, or scriptable via an ARM connection PUT. Captures the full model context: system prompt, messages, tool I/O, tokens.
  • Copilot Studio. Power Platform admin center → the environment → Data export → Application Insights. One environment-level switch covers both authoring experiences and autonomous runs. Prerequisites: Managed Environment, Dataverse capacity and Copilot Studio licensing. Captures conversation and tool I/O at the agent boundary — no system prompt or model internals.
  • Discovery sources (optional). Entra sign-ins: a tenant diagnostic setting that exports the sign-in log categories to the same workspace. Proxy traffic: the custom table your Zscaler, Netskope or other egress export already lands in that workspace. Vendor usage: an org-admin key from the OpenAI or Anthropic console — no Azure-side change at all.
  • The identity. Create a service principal and grant it Log Analytics Reader on the workspace's resource group. The same principal covers both sources when they share a workspace.
Important
From 30 September 2026 the AppGenAIContent table becomes Protected. The service principal will additionally need Privileged Monitoring Data Reader, or Foundry prompt and response content stops arriving.

You now have the four values step 5 embeds: the Log Analytics workspace GUID, and the principal's tenant id, client id and client secret.

2. Create a dedicated API key

In the dashboard, go to Crypto keys and create an API key used only by the collector. A dedicated key means its traffic is attributable, and revoking it never interrupts your SDK-instrumented agents.

You now have WYTNESS_API_KEY.

3. Generate a dedicated signing keypair

Also on Crypto keys, generate a new Ed25519 signing keypair, label it for the collector, and register the public key. The private half is shown once and never leaves your browser — paste it straight into your secret store. It is what makes each collected event non-repudiable.

Important
Generate the key before the collector starts. Ingest rejects envelopes signed by an unregistered key rather than trusting them on first sight, so an unregistered collector is refused, not silently accepted.

A key covers events from its creation onwards — that is what stops anyone signing back-dated evidence. The collector honours it: its first run normally back-fills 24 hours, but if the key is younger than that it starts at the key instead and says so in the log. Nothing is lost that could have been accepted, and if you want that first day captured, create the key before the period you intend to record.

You now have WYTNESS_SIGNING_KEY.

4. Copy your PII tokenization keys

From Privacy, copy your organisation's PII public key and HMAC secret. The collector uses them to replace personal data with tokens before anything leaves your network, which is why Wytness can never read it back. See PII Protection for what is detected automatically and what you should declare.

You now have WYTNESS_PII_PUBKEY and WYTNESS_PII_SECRET.

5. Describe your sources

Write one entry per source into COLLECTOR_SOURCES — either inline JSON or a path to a mounted file. Use the four values from step 1.

sources.json
[
{
"kind": "foundry_traces",
"workspace_id": "<Log Analytics workspace GUID>",
"display_name": "Foundry — prod",
"resource_id": "<App Insights ARM id — required when sources share a workspace>",
"tenant_id": "...",
"client_id": "...",
"client_secret": "..."
}
]

Capture kinds are foundry_traces and copilot_studio_env — they emit collected-tier events. Discovery kinds — entra_signin, proxy_law, openai_usage and anthropic_usage — emit Radar observations, never events; their shapes are under Discovery sources. Source credentials live only in your container or vault — Wytness never stores them.

Checkpoint: what you should now have

Five environment variables, all from your side of the boundary. If any row is missing, the step that produces it is on the left.

FromEnv varValue
Step 2WYTNESS_API_KEYThe collector's dedicated API key
Step 3WYTNESS_SIGNING_KEYThe collector's Ed25519 private key
Step 4WYTNESS_PII_PUBKEY + WYTNESS_PII_SECRETYour org's PII tokenization keys
Step 5COLLECTOR_SOURCESSource list (embeds the step 1 workspace + principal values)
OptionalWYTNESS_ENDPOINTDefaults to https://api.wytness.ai

6. Run the container

The collector is platform-agnostic — anywhere that runs a Linux container works. Whatever you run it on, the contract is the same:

  • One replica. The poll cursor has a single writer — scale sources by adding entries to COLLECTOR_SOURCES, not replicas.
  • The env vars from the checkpoint above. Keep them in your platform's secret store, not plain config.
  • A persistent volume at /var/lib/wytness-collector. It holds the poll cursor (no re-ship or skip across restarts) and the dead-letter file (no event loss across delivery outages). On ephemeral storage a restart re-reads dedup-safe, but pending dead-letter is lost.
  • Outbound 443 only — the four destinations under Network requirements. No ingress, no public IP.
  • 512 MB / 1 CPU is comfortable; sources with very large model contexts may need more memory. Optional HTTP health probe on /healthz (port COLLECTOR_HEALTH_PORT, default 8080).
terminal
docker run -d --name wytness-collector \
--restart unless-stopped \
--memory=512m --cpus=1 \
-v wytness-collector-state:/var/lib/wytness-collector \
-e WYTNESS_API_KEY=... -e WYTNESS_ENDPOINT=... -e WYTNESS_SIGNING_KEY=... \
-e WYTNESS_PII_PUBKEY=... -e WYTNESS_PII_SECRET=... \
-e COLLECTOR_SOURCES='[...]' \
ghcr.io/wytness-ai/wytness-collector:0.2.0
  • COLLECTOR_SOURCES='[...]' is the JSON you built in step 5, inline — or mount the file and pass its path (the Compose tab shows that form).
  • The named volume + --restart unless-stopped satisfy the persistence and one-replica halves of the contract.
Tip
Pin by digest in production — ghcr.io/wytness-ai/wytness-collector@sha256:…. docker pull prints the digest, and every publish records it. Tags are mutable; digests are not.

7. Check it's working

Three things should be true within a few minutes. If any is not, the collector tells you which.

  • It has checked in. The Collectors page in the dashboard lists your instance with a recent heartbeat and one row per configured source — this works the same on every platform.
  • Events are arriving. On Events, filter to the collected tier. Each event names the source it came from.
  • The container is healthy by your platform's own probe. The image ships a Docker HEALTHCHECK against /healthz (so docker ps shows healthy); on Container Apps or Kubernetes, point a liveness probe at the same path.
Tip
A source that is configured but has produced nothing is flagged as silent after COLLECTOR_SILENCE_HOURS (default 6). That is the signal to check step 1 — the export switch or the principal's role assignment is the usual cause.

Configuration

Wytness credentials are shared with the SDK. Use a dedicated API key and a dedicated Ed25519 signing keypair for the collector so its activity is attributable and revocable on its own.

Env varPurpose
WYTNESS_API_KEYOrg API key — use a dedicated key for the collector.
WYTNESS_ENDPOINTIngest endpoint (defaults to https://api.wytness.ai).
WYTNESS_SIGNING_KEYEd25519 key — use a dedicated signing keypair (label collector).
WYTNESS_PII_PUBKEY / WYTNESS_PII_SECRETCustomer-held PII tokenization keys.

Collector-specific settings:

Env varPurpose
COLLECTOR_SOURCESJSON list of source configs, or a path to a mounted JSON file.
COLLECTOR_POLL_SECONDSPoll cadence (default 120, min 30).
COLLECTOR_STATE_PATHCursor persistence path (default /var/lib/wytness-collector/state.json).
COLLECTOR_SILENCE_HOURSSilence threshold carried on every heartbeat (default 6).
COLLECTOR_ID / COLLECTOR_LABELInstance identity shown on the Collectors page.
COLLECTOR_HEALTH_PORT/healthz port (default 8080).
COLLECTOR_FLYWHEELDiscovery pattern contribution (default off). Ships only the pattern classes Radar could not match — model ids, hostnames, user-agent classes — with daily counts, and only when the organisation owner has also switched it on in the dashboard; the server refuses it otherwise.

Sources

The source list is built in step 5. Two notes that matter once you have more than one.

Note
resource_id is required when two sources share a single Log Analytics workspace, so each stream is attributed to the right App Insights resource. Source credentials live only in your container or vault — Wytness never stores them.

Discovery sources (Radar)

Discovery sources read logs for the fingerprints of AI services and emit observations — which actor reached which service, day by day — never events. The reduction runs inside the log store, so raw rows never leave it; only matched hits and day counts ship. Findings land in Registry as unreviewed rows with a Radar badge. A finding proves that something ran, not what it did: only capture produces evidence.

sources.json (discovery)
[
{
"kind": "entra_signin",
"workspace_id": "<Log Analytics workspace GUID with the sign-in export>",
"display_name": "Entra — corp tenant",
"tenant_id": "...", "client_id": "...", "client_secret": "..."
},
{
"kind": "proxy_law",
"workspace_id": "<workspace GUID>",
"table": "ZscalerWeb_CL",
"preset": "zscaler",
"columns": { "user": "login_s" },
"display_name": "Zscaler — egress",
"tenant_id": "...", "client_id": "...", "client_secret": "..."
},
{
"kind": "openai_usage",
"api_key": "<OpenAI org-admin key>",
"account_label": "openai-prod",
"display_name": "OpenAI — production org"
}
]
  • entra_signin — the same workspace shape as a capture source, read with Log Analytics Reader scoped to one workspace, never a tenant-wide directory role. No resource_id.
  • proxy_law — the workspace shape plus the custom table name and a column mapping: preset is zscaler, netskope or custom, and columns overrides any of url, host, user, client_ip, user_agent — your feed's real column names win. IP-only traffic is recorded as usage, never registered as an agent.
  • openai_usage / anthropic_usage — the vendor's org-admin key and a stable account_label that survives key rotation; no workspace fields (the collector refuses them on a vendor source). Anthropic's report carries no request count, so its observations report an honest floor of one call per key, model and day.
Note
Discovery pattern contribution is a separate, off-by-default consent: the organisation owner switches it on in the dashboard (Collectors page) and the container runs with COLLECTOR_FLYWHEEL=on. Then, and only then, the pattern classes Radar could not match are sent for review. Terms: DPA §13A.

Resilience

  • Log Analytics throttling or outage. 429s honour Retry-After (capped); 408/5xx and network errors retry with capped jittered backoff, and a revoked AAD token re-auths once. A source that exhausts its retries records the error on its heartbeat and retries next poll — one broken source never stalls the others.
  • Ingest outage or rejected delivery. Undeliverable batches append to a dead-letter file beside the cursor on the volume and replay through the live transport every cycle. Deterministic event IDs mean duplicates collapse server-side, so replay is safe by construction.
  • Bad or unregistered signing key. Validated at startup with a loud error and the fix path; capture continues — events dead-letter and replay automatically once the key is registered. Ongoing delivery failure also surfaces per-source on the Collectors page.
  • Corrupt state. A corrupt cursor restarts from empty (overlap re-reads are dedup-safe); unparseable dead-letter lines are set aside, never looped and never silently dropped.

Versioning & updates

The Collector has its own semver, independent of the SDKs (it pins a minimum wytness-ai). Images are published through CI only — collector tests, then image build, then an image-layer secret scan, then push to GHCR with the digest recorded in the run. To update, pull the new digest and recreate the container; the volume carries your cursor and any pending dead-letter forward.

  • PII Protection — the tokenization the collector applies in your boundary
  • Security model — how the customer-held keys compose with AGT identity
  • Event schema — the shape of what gets shipped
  • Wytness Radar — where discovery findings land, and what they do and do not prove

Bring your Foundry and Copilot Studio activity into your audit trail.

Start recording

We set no cookies. Sign-in and preferences use essential first-party browser storage only — no tracking, advertising, or third-party analytics. Privacy Policy

Wytness Collector
TABLE OF CONTENTS