Skip to content

Model endpoint and telemetry

Which model Winner AI calls, how to point it at a hosted OpenAI-compatible provider or Amazon Bedrock instead of the compose Ollama, how to check that model with fleetview-model-check before Prompt Coaching is switched on, what each feature sends to that model, and whether the stack sends telemetry. It assumes a server installed from the bundle (Install the server).

On an installation from the bundle, read the server's commands as Commands on the other pages says: in <install>, as the user who owns <install>/.env, with plain docker compose and no sudo.

What calls a model

What calls it Chosen by Default
The jobs passes behind Prompt Coaching, Skills and Benchmarks; the control plane uses the same values (Settings, and Skills' Generate) FLEETVIEW_LLM_ENDPOINT, FLEETVIEW_LLM_MODEL, FLEETVIEW_LLM_API_KEY, and for Amazon Bedrock FLEETVIEW_LLM_PROVIDER and FLEETVIEW_LLM_REGION the compose Ollama at http://ollama:11434/v1, qwen3:4b-instruct-2507-q4_K_M, no key, provider openai
Generated insights in the dashboard INSIGHTS_ENDPOINT, INSIGHTS_MODEL, OPENAI_API_KEY the same compose Ollama and model
The dashboard's query embeddings and the bootstrap push nothing; fixed the compose Ollama, nomic-embed-text, 768 dimensions
Each laptop's own embeddings nothing; handed out at enrollment the laptop's http://localhost:11434/v1, nomic-embed-text, 768 dimensions

The first two rows are what this page changes. The last two are under Embeddings.

What the endpoint must support

This section is for the default provider, an OpenAI-compatible endpoint (FLEETVIEW_LLM_PROVIDER=openai). Amazon Bedrock has its own section.

  • OpenAI-compatible chat completions. Each call is POST <endpoint>/chat/completions, so the endpoint is the provider's base URL, the one that ends in /v1.
  • Strict structured output: response_format of type json_schema with strict: true. A provider that does not support it answers every call with an error, and the model check below fails.
  • A bearer key. The key is sent as Authorization: Bearer <key>. A blank key sends no header.
  • https. FLEETVIEW_LLM_ENDPOINT must be https or a compose-internal host. Anything else is refused with FLEETVIEW_LLM_ENDPOINT must be https or a compose-internal host; refusing to use <scheme>://<host>[:port]. For a jobs pass the refusal is the error on its pipeline_runs row; Skills' Generate shows that the host's model endpoint is misconfigured. INSIGHTS_ENDPOINT must be https too: the Dashboard refuses any http endpoint other than the compose Ollama, http://ollama:11434.

The client retries rate limits and transient errors itself, honouring Retry-After, and adapts to providers that reject temperature or want max_completion_tokens, so do not work around those.

Amazon Bedrock is a separate provider, not an endpoint of this kind: its OpenAI-compatible endpoint is not used, and the host model reaches Bedrock through its own API, described in the next section.

Amazon Bedrock

An operator can run the host model on Amazon Bedrock. The control plane calls Bedrock's Converse API and signs each request itself, so there is no gateway, SDK or extra container. It applies to the host model only: a team's own model stays OpenAI-compatible, and embeddings and the dashboard's insights are unchanged. Prompt Coaching and Skills need nothing else; Benchmarks also needs its Routes set to Bedrock ids (below).

  • Three settings. FLEETVIEW_LLM_PROVIDER=bedrock, FLEETVIEW_LLM_REGION (an AWS region such as us-east-1) and FLEETVIEW_LLM_MODEL, which must be a Bedrock model or inference-profile id: the default model is Ollama's. Leave FLEETVIEW_LLM_ENDPOINT blank, and the endpoint is derived as https://bedrock-runtime.<region>.amazonaws.com. Set it only for a VPC endpoint: a Bedrock endpoint must be an https Bedrock runtime host (bedrock-runtime.<region>.amazonaws.com or its VPC interface endpoint), so the control plane and scripts/check-env.sh refuse a hosted OpenAI-compatible endpoint left from before rather than send it the Bedrock API key or a signed request. Settings shows the provider and region read-only.
  • Credentials, in this order. A Bedrock API key in FLEETVIEW_LLM_API_KEY, sent as a bearer token; else the AWS variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN in the process's environment, which the compose file does not pass, so nothing in the deployed stack uses them; else the instance role of the EC2 host, read through IMDSv2 and refreshed before it expires. Nothing is read until the first call.
  • The instance role needs hop limit 2. A container reaches the metadata service one network hop further than the host does, so the instance's metadata hop limit must be 2 for the role to reach it (Network). The trade-off is that every container on the host can then use the role, so the role should allow only bedrock:InvokeModel on the chosen model and inference-profile ARNs, plus the DNS mode's TXT write where that mode is used. A Bedrock API key avoids the role and the hop limit.
  • The first call on a schema is slow. Bedrock compiles each structured output schema on its first use, which AWS documents as taking up to about 90 seconds, and the default 120 s call timeout covers it. AWS caches the result per account for 24 hours from that first use, so the slow call recurs after that.
  • Errors. A failed call records the HTTP status and AWS's error type, for example HTTP 429 ThrottlingException, and never the message. A host with no credentials from any of the three sources fails the pass with a fixed reason, without sending a request.
  • Routes must be Bedrock ids. The release-default Benchmarks Routes' candidates (claude-sonnet-5 under the claude-opus and claude-fable keys, gpt-5.5 under gpt-5.6) are not Bedrock model or inference-profile ids, so every replay that is not local answers 400 until the operator or the team sets Routes that name ids Bedrock serves.
  • Check it first. fleetview-model-check (next sections) builds its client the same way. It runs in the jobs container, which gets no AWS variables, so there it uses the Bedrock API key or the instance role. Its refused: line can also name the region, or an endpoint that is not an https Bedrock runtime host.
  • Tried so far: no Claude model on Bedrock has passed the model check yet. On 2026-10-29, us.anthropic.claude-sonnet-4-6 in us-east-1 answered the calls, with thinking off and temperature both accepted. It did not pass the rule below: Model fit F1 was 0.76 against a baseline of 0.77, and any F1 0.70 against 0.78, mostly because it over-flagged Context. The run took 11 calls and 29,983 + 7,638 tokens, about USD 0.20. The same day Claude Sonnet 5 (us.anthropic.claude-sonnet-5) was refused for that account with AccessDeniedException, so it has not been measured. Run the model check on your own model before you turn Prompt Coaching on.

An example in us-east-1

The host .env for Bedrock in us-east-1, with the instance role and no key:

FLEETVIEW_LLM_PROVIDER=bedrock
FLEETVIEW_LLM_REGION=us-east-1
FLEETVIEW_LLM_MODEL=us.anthropic.claude-sonnet-4-6
FLEETVIEW_LLM_ENDPOINT=
FLEETVIEW_LLM_API_KEY=
  • Claude models on Bedrock are called through an inference profile, so the model is a profile id such as us.anthropic.claude-sonnet-4-6 (routed in the United States) or global.anthropic.claude-sonnet-4-6. Run aws bedrock list-inference-profiles --region us-east-1 to see which profiles the account has.
  • The Sonnet 4.6 live runs, and the policy below, used a us. profile only; the one call made on a global. profile, for Sonnet 5, was refused for the account.
  • For a VPC interface endpoint, set FLEETVIEW_LLM_ENDPOINT to its https runtime host instead of leaving it blank.
  • To use a Bedrock API key instead of the role, put it in FLEETVIEW_LLM_API_KEY.
  • Then recreate the services, as in step 3 of Point the stack at a hosted model.

The instance role's policy

The role needs one statement, bedrock:InvokeModel, on the inference profile and on the foundation model in every region that profile routes to. Nothing broader: at hop limit 2 every container on the host can use this role.

  1. Read the ARNs from AWS rather than typing them:

    aws bedrock get-inference-profile --region us-east-1 \
      --inference-profile-identifier us.anthropic.claude-sonnet-4-6 \
      --query '[inferenceProfileArn, models[].modelArn]'
    
  2. Put them in the policy. This is the policy the live run used, with <ACCOUNT_ID> in place of the account:

    {
      "Version": "2012-10-17",
      "Statement": [
        { "Effect": "Allow", "Action": "bedrock:InvokeModel",
          "Resource": [
            "arn:aws:bedrock:us-east-1:<ACCOUNT_ID>:inference-profile/us.anthropic.claude-sonnet-4-6",
            "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-sonnet-4-6",
            "arn:aws:bedrock:us-east-2::foundation-model/anthropic.claude-sonnet-4-6",
            "arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-sonnet-4-6"
          ] }
      ]
    }
    
  3. Converse is authorised by bedrock:InvokeModel; no other action is used.

  4. In the dns certificate mode, the same role also carries that mode's Route 53 statements, which every container can then use too. To keep them from the containers, use a Bedrock API key and leave the hop limit at 1; the key alone does not stop a container reaching the role.
  5. On EC2 the instance needs IMDSv2 required and hop limit 2, set before the install: aws ec2 modify-instance-metadata-options --instance-id <instance-id> --http-put-response-hop-limit 2 --http-tokens required.
  6. In the live run, a container at hop limit 2 read the role and Bedrock accepted the signed call. At hop limit 1 the pass failed with the fixed reason: set FLEETVIEW_LLM_API_KEY to a Bedrock API key, set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, or give the instance a role with metadata hop limit 2.

What the AWS account needs

Each of these is set in AWS, not in Winner AI, and a missing one shows as an error on the first call:

  • Access to the model. Anthropic models need the account's first-use details for Anthropic and the model's Marketplace agreement. aws bedrock get-foundation-model-availability --model-id <model> shows the agreement and the authorisation.
  • A quota in the region. In Service Quotas, under Amazon Bedrock, the applied tokens-per-minute quota for the model's cross-region (or global) inference must be above 0.
  • On 2026-10-29 a model whose availability read available and authorised, with a quota of 0, was still refused with AccessDeniedException.
  • Only AWS can raise a quota of 0: a quota increase or AWS Sales.
  • A new account's daily token allowance. AWS limits a new account's tokens per day, and a call over it is throttled until the next day. A full model check took about 38,000 tokens on Sonnet 4.6.

What each error means

A failed call records the HTTP status and AWS's error type, never AWS's message. A pass stores it on its pipeline_runs row, which Skills, Coaching and Settings show a Manager with when a call last worked, and the model check prints the model call failed: followed by:

Recorded Means Check
HTTP 403 AccessDeniedException The role's policy does not cover the profile or a foundation-model ARN, or the account may not use the model The policy above. Then the model's availability and its applied quota (What the AWS account needs)
HTTP 403 with another type, such as UnrecognizedClientException or InvalidSignatureException The credentials themselves were refused The key, or the role and its session
HTTP 429 ThrottlingException The model's quota in the region, or the account's daily allowance Service Quotas; a daily allowance resets the next day. The client already retries with backoff, so do not add retries
HTTP 400 ValidationException Bedrock refused the request or the schema, or the model id does not exist in the region The model id and the region. The client already handles the three refusals it knows, once each and without failing: thinking, temperature and too high a maxTokens

AccessDeniedException looks the same whether the policy or the account is at fault, and nothing the stack records or prints tells the two apart. Check the policy first, then the account (What the AWS account needs).

Benchmarks Routes on Bedrock

Every candidate in FLEETVIEW_BENCHMARK_ROUTES is sent to Bedrock as the model, except one that matches FLEETVIEW_BENCHMARK_LOCAL_ROUTES (qwen3 by default), which goes keyless to the compose Ollama. Each of the others must be an inference-profile or model id that Bedrock serves and that the role's policy covers. For example, on one line:

FLEETVIEW_BENCHMARK_ROUTES={"claude-opus": ["us.anthropic.claude-sonnet-4-6"], "claude-fable": ["us.anthropic.claude-sonnet-4-6"]}
  • The keys are the release defaults' Claude families. A Route under a family with no entry, such as the default gpt-5.6, has no candidate on Bedrock.
  • Add each further profile's ARNs to the role's policy.
  • Benchmarks was not run on Bedrock in the live runs, so this example has not been tried.

Point the stack at a hosted model

The recommended path is one model for the whole deployment, set in the host .env.

  1. In <install>/.env, set the three FLEETVIEW_LLM_* values, and the three insights values to the same provider:

    FLEETVIEW_LLM_ENDPOINT=https://<provider-host>/v1
    FLEETVIEW_LLM_MODEL=<model-id>
    FLEETVIEW_LLM_API_KEY=<key>
    INSIGHTS_ENDPOINT=https://<provider-host>/v1
    INSIGHTS_MODEL=<model-id>
    OPENAI_API_KEY=<key>
    

    Keep local-llm in COMPOSE_PROFILES: the compose Ollama still serves embeddings (Embeddings, below). Leave coaching out of FLEETVIEW_FEATURES until the model passes the model check (next section). The default is [], so a stock deployment has no feature on.

  2. Set the Benchmarks Routes. The default candidate list includes qwen3:4b-instruct-2507-q4_K_M, sent keyless to the compose Ollama, and the Ollama container pulls a chat model only while one of the two chat endpoints is http://ollama:11434. Once both are hosted and the container is recreated (step 3), it stops pulling that model; set the Routes so that Benchmarks does not depend on it. Prompt Coaching's Model fit suggests its cheaper models from the same map, so set it even if only Coaching is on. Set FLEETVIEW_BENCHMARK_ROUTES to a JSON map whose candidates are model ids your provider serves, on one line:

    FLEETVIEW_BENCHMARK_ROUTES={"claude-opus": ["<cheaper-model-id>"], "gpt-5.6": ["<cheaper-model-id>"]}
    
    • A key matches the original Route's name, or a dash- or dot-separated prefix of it. Each candidate is sent verbatim as model. The keys above are the release defaults' families; use your own originals.
    • Any candidate whose id starts with qwen3 still goes keyless to the compose Ollama, because FLEETVIEW_BENCHMARK_LOCAL_ROUTES matches by prefix. When a hosted candidate starts with qwen3, also set FLEETVIEW_BENCHMARK_LOCAL_ROUTES=[].
    • A team that saved its own Routes to try on Settings keeps them; this value applies to the teams that have not.
  3. Recreate the services that read these values, ollama among them:

    # in <install>
    docker compose up -d
    

    Expected: compose recreates each service whose values changed, ollama included, and reports it started. The edits stay in place across releases: an upgrade from the bundle carries .env across (Upgrade and roll back).

  4. Check: on a manager's Settings, the group "Model ยท bring your own key" shows the Endpoint and Model rows with the new values.

Per team, instead. A manager can give their organisation its own endpoint, model and key on Settings; that needs FLEETVIEW_SETTINGS_KEYS (The settings key). A team endpoint must be https and a public address: a local name or a private address is refused, the compose ollama aside. The model check below measures only the host model in FLEETVIEW_LLM_*, never a team's. The host's own FLEETVIEW_LLM_API_KEY is never sent to a team's endpoint: a team uses its endpoint, model and key together, or the host's three together.

FLEETVIEW_LLM_FALLBACK decides what a team with no key of its own gets: host, the default, lends it the host model; refuse makes no model call for it at all. Its passes still do the work that needs no model, and the product pages say No model configured. A deployment whose teams bring their own key sets refuse, so the host's key and budget are never spent on them.

The settings key

A team's model key is stored sealed with FLEETVIEW_SETTINGS_KEYS, so a database dump or backup alone does not reveal it. A server without that value cannot read a stored key, and every model pass for that team records the reason and does only the work that needs no model. Keep the value in .env only, never beside the backups. Generate one with the running backend, in <install>:

docker compose exec -T backend \
  uv run --no-dev python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'

The value is a comma-separated list: the first key seals, every key listed opens. Rotate without a gap:

  1. Put the new key first: FLEETVIEW_SETTINGS_KEYS=<new>,<old>.
  2. Recreate the two services that read it: docker compose up -d backend jobs.
  3. Re-seal every stored key under the new one: docker compose exec -T jobs uv run --no-dev fleetview-jobs rekey. It prints how many keys it re-sealed and how many a Manager replaced or removed while it ran, which it leaves as they are; if any stored key opens with neither value it changes nothing and says so.
  4. Drop the old key (FLEETVIEW_SETTINGS_KEYS=<new>) and recreate backend and jobs again.

A lost settings key cannot be recovered: each team's Manager enters its key again on Settings.

Check the model before Prompt Coaching

fleetview-model-check runs the production coaching judge over the labelled set the release ships, against the host's model: the endpoint, model and key in FLEETVIEW_LLM_*. On Bedrock it also reads FLEETVIEW_LLM_PROVIDER=bedrock and FLEETVIEW_LLM_REGION, and the endpoint may be left blank, since the region's own is derived. It scores the answers against the labels and compares them with the baseline recorded for that release. It runs inside the jobs container, from the image the server already runs, so it needs no checkout of the release; recreate jobs first if you have just changed FLEETVIEW_LLM_*.

# in <install>
docker compose exec -T jobs uv run --no-dev fleetview-model-check

An optional smoke run first: add --limit 10 to judge only the first ten rows. It proves the endpoint accepts the calls and shows what a call costs, and it is never a verdict, whatever the number: it ends not a verdict and exits 3.

The last line is the verdict, and the exit code says the same:

Last line Exit Means
verdict: pass 0 A full run, compared with a baseline, neither score more than 0.05 below it
verdict: fail - ... 1 A score more than 0.05 below the baseline, or no baseline at this rubric version for this labelled set
verdict: fail 1 The run did not finish; the line directly above says why, and the cost line above that what it spent. A refused: line has no cost line above it: no call was made
not a verdict: limited to ... 3 A --limit run
not a verdict: the limited run did not finish 1 A --limit run that did not finish; the lines above it read as for verdict: fail

A usage error exits 2.

The model passes when neither its Model fit F1 nor its any F1 is more than 0.05 below the newest full-run baseline recorded at the same rubric version for the exact labelled set this release ships. A score above the baseline, however far, is a pass. Only the host model is measured, never a team's own, and only the coaching judge: Skills and Benchmarks call the same model but nothing here measures them.

A full run prints this shape, with the run's and the baseline's own values in place of the placeholders:

model check: <model-id> (hosted endpoint), rubric v<N>, <rows> of <rows> rows

coaching eval: rubric v<N>, <model-id> (hosted), <date>, <judged> rows (<excluded> excluded), <n> calls, <p>+<c> tokens
...
cost: <n> calls, <p> prompt + <c> completion tokens
vs baseline <baseline-model> <baseline-date> (rubric v<N>, tolerance 0.05)
  Model fit f1  0.xxx vs 0.xxx (+0.xxx)
  any f1        0.xxx vs 0.xxx (+0.xxx)
verdict: pass

Between the header and the cost line it prints the per-category precision, recall and F1 table, the starts_task agreement, how many of the judge's rewrites the faithfulness check dropped, and the rewrite judge's agreement. The cost line is what the run cost in calls and tokens; a full run is about a dozen calls of up to ten prompts each.

What it prints is scores, counts, calls and tokens. It prints no prompt and nothing the model or the endpoint wrote, and writes no file. The one log line it can add, on standard error, is the judge's warning that an item was answered twice even when asked again, naming a labelled row's session id and ordinal and no text. When the run does not finish:

  • refused: FLEETVIEW_LLM_ENDPOINT must be https or a compose-internal host means the endpoint would carry the key in the clear; it is refused before anything is sent, as the stack refuses it.
  • the model call failed: HTTP <status> ... means the endpoint refused the calls: for example a 401, or a 400 from one with no strict json_schema support. What the endpoint said beyond the status is not printed (see below); send it a minimal request with the same model and response_format using curl to read it.
  • the model left items unanswered after a second ask means the model did not answer every item, even when asked again.
  • the run stopped on an unexpected <Type> means the run failed for a reason it does not describe; only the error's type is printed.
  • A call that runs past FLEETVIEW_LLM_TIMEOUT_SECONDS (120 by default) fails the run. A CPU-bound local Ollama answering ten prompts at a time can take longer than that, and a full run keeps it busy for every call; give one run more time by adding -e FLEETVIEW_LLM_TIMEOUT_SECONDS=900 after exec -T.

If the model does not pass, leave Prompt Coaching off and try another model.

Switch Prompt Coaching on

  1. Add coaching to FLEETVIEW_FEATURES, for example FLEETVIEW_FEATURES=["coaching"]. Skills and Benchmarks have their own slugs, skills and benchmarks. A team can switch a feature off on Settings but never on: the operator's list is the ceiling.
  2. Recreate backend and jobs:

    # in <install>
    docker compose up -d backend jobs
    
  3. The recreated jobs service sweeps as it starts, so there is no need to wait for the timer (900 seconds by default). Wait until its log shows the sweep finished, then read the newest coaching rows:

    # in <install>
    docker compose logs jobs | grep -E 'rows written this sweep|jobs sweep failed'
    docker compose exec -T db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "$1"' sh \
      "SELECT feature, model, error, finished_at FROM control.pipeline_runs WHERE feature = 'coaching' ORDER BY started_at DESC LIMIT 5"
    

    The database user and name are read inside the db container, as compose parsed them from .env, so a quoted value in .env reads the same as it does to Postgres.

    Expected: the logs command prints jobs: <n> rows written this sweep, <m> passes had no model to call; if it prints nothing, the sweep is still running, so run it again. jobs sweep failed for <organisation> means that organisation's passes raised and the sweep carried on; jobs sweep failed with nothing after it means the whole sweep crashed. Either way, read the traceback below the line with logs jobs. Then the newest coaching row has an empty error, and its model is the configured model once the pass judged something; model is empty when there was nothing new to judge.

Embeddings

The compose Ollama stays on while local-llm is in COMPOSE_PROFILES. It pulls nomic-embed-text at every start and serves the dashboard's query embeddings and the bootstrap push. That model is fixed in deploy/pgserve/config.toml, not a compose setting, and it must be the laptops' model too, because vectors from different models do not compare. A hosted embedding model is therefore not an option.

Enrollment points each laptop at its own http://localhost:11434/v1 with nomic-embed-text, 768 dimensions. Winner AI installs neither Ollama nor the model on a laptop. A laptop without them still pushes its sessions, but no vectors, and Skills reads only the vectors laptops push, so that laptop's prompts are absent from Skills. To give a laptop vectors, install Ollama on it and pull the model: ollama pull nomic-embed-text.

What each feature sends to the model

This is what a model endpoint receives from Prompt Coaching, Skills and Benchmarks, per call. An automated test per row captures the request, from the feature's own read but for Skills' Generate, whose test starts from what the mining read returns, and holds the kinds of data listed here and the shape of the request: it fails when one of them starts or stops being sent, or when the request gains a field or a message. It cannot rule out a kind of data nobody thought to plant in its fixtures.

A prompt is sent with nothing redacted, so it carries whatever the person typed or pasted into it: a path, a name, a file's content. Prompt Coaching and Skills first cut the blocks an agent adds around a prompt (Claude Code's hook, command, reminder and message wrappers, Codex's context items and image wrapper, and the Codex app's browser context, heartbeat block and writing-block edits, with the request heading it puts before the person's words) and send what is left; a prompt that is nothing but such a block, or opens with a hook, command, reminder or task block, is not sent, and neither is a Codex app scheduled automation run that opens with its Automation: header, a prompt one agent wrote for another (Codex's agent history, a plan handed to a fresh context, Claude Code's background task notice), one with a $ARGUMENTS line, one with a wrapper's closing tag that opens no block, or one with a wrapper block that is never closed. The cut finds a block where its opening tag starts a line: one that begins in the middle of a line, follows another block's closing tag on the same line, or sits inside a code fence stays in what is sent. Benchmarks replays a prompt whole, and a prompt a hook or template wrote can inline an earlier reply or a diff; naming a pattern sends what a person typed of its prompt, what its shown text would show of a known opening, and otherwise the prompt as stored.

Call Sent
Prompt Coaching, judging prompts Each prompt a person typed, cut to what they typed: at least 40 characters of it, from a prompt of at most 8,192, nothing redacted. Beside each one: the agent's name, the model its session mostly ran on, whether it is the session's first prompt, the team's Routes to try for that model, the session's outcome, and the counts stored with the session (user messages, tool failures, edit churn, and whichever prompt-quality counters are not zero). The agent, model, outcome, Routes and frontier families are sent as plain names: words of letters, digits and . _ - : / @, at most 64 characters, so up to that much of what a laptop reported as its agent, model or outcome sits beside the prompt as ordinary words. Two Routes that come to the same plain name are numbered. The instructions name the team's frontier model families. One call carries the prompts attributed to one member only, up to ten; a session nobody is attributed to yet is sent on its own.
Prompt Coaching, the main points to improve Per person, and once for the sessions nobody is attributed to yet, from what the judge stored and nothing read again from a transcript: how many prompts were judged, were eligible and had findings, how many sessions were judged and had a Model fit verdict, and how many findings each category has; the judge's reasons, up to 30 per category; the cheaper Route it named; and up to 8 of its suggested rewrites, each cut to 600 characters. A rewrite restates the person's prompt, so it carries what that prompt said.
Skills, naming a workflow Up to 5 prompts of the workflow, each cut to what a person typed: at least 50 characters of it, from a prompt of at most 8,192.
Skills, Generate and Regenerate Up to 10 distinct typed prompts of the workflow, each cut to what a person typed and then to 1,800 characters; the workflow's stored name and summary, which the model wrote from its prompts when it was named; how many prompts and sessions it has; and its scope, which is the project name at least 60% of its prompts came from, or Global. The name, summary and scope are marked as data, like the prompts.
Benchmarks, naming a pattern One prompt of the pattern, cut to 1,800 characters. When the cut Prompt Coaching makes leaves any of it, only what it leaves is sent. A prompt that opens with a hook, command or other opening Winner AI knows is sent only as far as a pattern's shown text would show it, at most its first 100 characters: the blocks it opens with, up to but never including a slash command's arguments, text after a closing tag, or a block that is never closed or that holds a shell-mode command, a slash command's arguments or its own opening tag; a command run in Claude Code's shell mode as its bare <bash-input> tag, the command left out, and a prompt that opens with a slash command's arguments as its bare <command-args> tag; an opening that is a line of text rather than a block, such as Claude Code's interruption notice, as those 100 characters, whatever follows it on them up to a shell-mode command or a slash command's arguments; and an opening that cannot be placed, nothing. Any other prompt is sent as stored, a block an agent added included: a prompt an agent composed, such as a review request Claude Code writes for Codex, a Codex app scheduled automation run with the name and saved prompt it carries, or a Codex app writing-block edit with the block text it carries; or a prompt a person typed that the cut's stricter rules drop whole, such as one opening "adversarial review". One that holds a shell-mode command or slash command arguments at the start of a line, in a code fence or out of one, is not sent at all. A pattern whose prompts open with a known hook or plugin template is never named, so never sent. Any other pattern is, whichever kind of session its prompt was issued in, an SDK or automated run included.
Benchmarks, replaying a task The task's prompt, whole, and every system message stored for its session, sidechain ones included, to the endpoint that serves the Route being tried: the model endpoint, or the local one for a local Route. A task's prompt is one a hook or template wrote, so it carries whatever that template put in it, which can be an earlier reply or a diff.
Benchmarks, judging a replay The same prompt, every reply of the original session outside its sidechains, as stored, which includes the model's thinking where the agent records it, and the reply the Route gave.
The model check, fleetview-model-check The labelled set the release ships in its image, judged as Prompt Coaching judges a prompt: each labelled prompt, cut as above, beside its row's agent, session model, position, outcome and counters, ten to a call; then each labelled rewrite beside its prompt, for the rewrite judge. The instructions name a fixed list of frontier families and the cheaper Routes for each, not the team's. Nothing is read from the database, so no team's prompt is sent.

Text a member typed, and text a model wrote from it (a workflow's name, a judge's reason), goes to the next call between markers that the instructions declare to be data. Inside the markers, a run of three or more < or > is written out as {lt} or {gt}, bracket by bracket, so the text cannot close a marker or open one. The same goes for the characters drawn as an angle bracket that Winner AI lists (fullwidth signs, angle quotation marks, mathematical and CJK brackets) and for a run broken only by spaces or invisible characters; the list is not every glyph a model might read as a bracket. Nothing else in the text is changed. This keeps such text in its place in the request. It does not prove a model will ignore an instruction written there.

Every call also carries its fixed instructions and the model's name. The replay adds a limit on the answer's length, worked out from the original's token counts; every other call adds the shape its answer must take, and when an answer does not fit that shape the call is made once more with the model's own answer and the validation error added to it. No call adds the session's id, the laptop's name or who typed the prompt, and none reads the sessions' tool-call table, where a tool's result is kept; Benchmarks replays and judges only sessions that used no tool. Only Skills' Generate adds a project name, and only the Benchmarks judge reads a session's replies.

When a call fails, the log line and the error stored on the pipeline_runs or benchmark_runs row say the HTTP status or the kind of failure, the model, the name of the shape asked for and how many tries were made, and why no connection could be made when none was (a refused certificate, an unknown name). They never hold the request, the endpoint's response or the model's answer, because an endpoint that refuses a request can quote it and a rejected answer is a model's writing about a member's prompts. To see why a provider refused a call, send it a minimal request with the same model and response_format using curl. A failed write to the control database does not print the values it was writing.

Sessions flagged as holding a secret

The session collector (agentsview) looks for secrets in every session it stores and records each one it finds; Home counts them as Credential exposures. A session with any such finding is flagged, whatever the finding's confidence and wherever in the session it is. The control plane adds no detection of its own and does not read what was matched.

While the findings table lists a finding for a session, no call in the table above sends anything from it: not its prompts, its replies, its system messages or the judge's stored results about it. The check is made each time a feature reads, so a session flagged after it was first read is left out for as long as it stays flagged. If the lookup itself fails, nothing is sent: the pass fails, or the page shows no stored result. What it means for results made before the flag:

  • Prompt Coaching. Findings already shown stay on the page until a release next changes the rubric version. They are not sent with the main points to improve.
  • Skills. A workflow with an example from a flagged session is not listed while it is a candidate or a draft, and cannot be generated or regenerated. An approved skill stays listed and installable. Examples from a flagged session are not shown as evidence.
  • Benchmarks. A run whose sample session is flagged is not shown and does not count toward the proven saving. A pattern's name stays until a release next changes the pattern version; a name is never sent to a model.
  • The disputes export. A Winner AI developer's tool, run from a checkout of the release and never by the server, writes the prompts a manager disputed on Prompt Coaching into a labelled set in a file it is given, outside the repository, which Winner AI's model evaluation then sends to the model under test; the set a release ships never holds them. It reads as Prompt Coaching does, so a prompt from a flagged session is not written and is listed with the prompts no longer in the organisation's data; a row an earlier export wrote from it is taken out of the file. Until that export is run again, the row stays in the file and the next eval sends it.

What the rule does not do:

  • It does not outlast the finding. The findings table is the collector's, and every laptop of the organisation can change it; the collector may also replace a session's findings when the session is pushed again. A session whose findings are removed is not flagged and is sent like any other. That is not caught today.
  • It does not catch a secret the collector did not flag. The rule is as good as its detector.
  • It recalls nothing. A request made before the flag was already sent, and nothing is deleted from the database.
  • It does not cover Insights, which is the Dashboard's own call; Telemetry, below, says what that sends.

Telemetry

  • Winner AI sends error reports, timings and usage counts only when the operator sets them. The control plane and the jobs service read seven operator settings for them (configuration reference, Telemetry). Error reports leave when FLEETVIEW_SENTRY_DSN is set to a DSN that passes the host rule; "What an error report carries", below, lists every field. Timings go to the same project while error reports are on, for the share of requests and jobs passes that FLEETVIEW_SENTRY_TRACES_SAMPLE_RATE sets; "What a timing carries", below, lists every field. While error reports are on and the telemetry secret is valid (set and at least 32 characters), the jobs sweep sends cron check-ins there, unless FLEETVIEW_JOBS_INTERVAL_SECONDS is under 60, and fleetview-checkin lets a host job (the backup, the certificate renewal) do the same once its script calls it; the backup and renewal scripts this release ships do not call it yet. "What a check-in carries", below, lists every field. Usage counts leave only when FLEETVIEW_POSTHOG_KEY, FLEETVIEW_POSTHOG_HOST and FLEETVIEW_TELEMETRY_SECRET are all set and valid; they go from the control plane and the jobs service, best effort, to that host's /batch/. An unreachable analytics host never fails a request or a pass. Settings shows the state of each in its Telemetry group, and "Usage counts, event by event", below, lists every event. What a laptop sends is under "What is never instrumented, and what a laptop does", below.
  • The browser's usage counts have a path on the deployment's own origin, /lumen/, and each control-plane page posts its page view, and any uncaught error or unhandled rejection in its own scripts, there. The page loads the analytics library, a pinned build of posthog-js, from the control plane and never from the vendor, and only while usage counts are on: with them off a page carries neither the library nor its settings. While usage counts are on, the web server forwards POST /lumen/e/ and nothing else, over verified https, to FLEETVIEW_POSTHOG_HOST; a host that names a path keeps it shut, because the edge posts only at the host's root. The vendor never sees the cookie, authorization, identity, client-address, Referer or Origin headers, and its answer can set no cookie or other header that acts on the whole origin, and is sandboxed. Every other address under /lumen/, page recording's included, answers 404, and so does a request whose Origin is missing or is not this deployment's PUBLIC_BASE_URL. The path is behind sign-in like every page, and a removed person is refused.
  • A hosted model receives what it is sent. "What each feature sends to the model", above, lists it call by call. Insights sends aggregates and a few sample prompts, not transcripts.

What blank settings guarantee

FLEETVIEW_SENTRY_DSN, FLEETVIEW_POSTHOG_KEY and FLEETVIEW_POSTHOG_HOST are blank in a new installation, and blank is off. While they are blank, nothing is sent, no vendor library is loaded, no telemetry script is rendered into a page and no connection is opened to an error-report or analytics host. The telemetry secret alone sends nothing: an installation from the bundle writes one before any vendor value is set. A blank FLEETVIEW_SENTRY_TRACES_SAMPLE_RATE is not off, it is 2 in 100, but no timing is sent while the DSN is blank.

An installation may be handed an .env with our values already filled in. Blanking them and restarting the backend and the jobs service, as below, returns it to sending nothing: Settings then reads off for Error reporting and Product analytics.

Switching each kind on and off

Each kind is switched by the .env settings below; the configuration reference gives each its row. Settings shows the state of each in its Telemetry group, which no one can change from the page.

Kind Setting Sent while What Settings shows
Error reports FLEETVIEW_SENTRY_DSN the DSN is valid Error reporting: on with the host and the project number; off while it is blank; invalid. Error report environment: the name every report and check-in carries. While FLEETVIEW_SENTRY_ENVIRONMENT is blank, Settings shows none and reports and check-ins carry production.
Timings FLEETVIEW_SENTRY_TRACES_SAMPLE_RATE error reports are on and the share is above 0 Timing sample rate: the share as a decimal; off while error reports are not on, or when the value is 0 or not a share between 0 and 1. Blank is 2 in 100.
Cron check-ins none of their own error reports are on, the telemetry secret is valid, and the sweep interval is 60 seconds or more No row of their own: Error reporting reads on and Identity hashing reads set.
Usage counts FLEETVIEW_POSTHOG_KEY, FLEETVIEW_POSTHOG_HOST and FLEETVIEW_TELEMETRY_SECRET all three are set and valid Product analytics: on; off while the key is blank, whatever else is set; incomplete; invalid. Analytics host: the host name, not set or invalid.
Page views and browser errors none of their own: they ride usage counts usage counts are on Product analytics, as above.
Page recording FLEETVIEW_POSTHOG_REPLAY never in this release, whatever it says Page recording: always off.
Identity hashing, for every id Winner AI sets FLEETVIEW_TELEMETRY_SECRET not a kind: the key every id Winner AI sets is hashed with, which never leaves the deployment; a page with no one to hash sends the library's own random id instead, as "Usage counts, event by event" says Identity hashing: set, not set or invalid.
  • Incomplete means the analytics key is set but the host or the secret is not. Nothing is sent.
  • Invalid means a value is set but fails its rule: a DSN or analytics host that is not https or that points at this host's own network, a DSN that names no key or no numeric project, or a secret shorter than 32 characters. Nothing of that kind is sent. Where one value is invalid and another is missing, Settings shows invalid, because that names what to fix; while the analytics key is blank, Product analytics reads off whatever else is set. An invalid secret also stops check-ins and leaves error reports without the hashed deployment id.
  • 0 as the sample rate keeps error reports and drops timings.

The backend and the jobs service read these settings when they start. After editing .env, restart both, then reload Settings:

# in <install>
docker compose up -d backend jobs

What an error report carries

Every report is rebuilt from a fixed list of fields before it leaves, so a field this list does not name never reaches the vendor. The vendor also sees the address the deployment connects from, as the receiver of any https request does.

  • Every report: an event id, the time, the level, the platform, the release version, the environment name (FLEETVIEW_SENTRY_ENVIRONMENT, or production when that is blank), and the SDK's name, version, package and integration list.
  • The failure: each exception in its chain by type and module, never its text, with how it was caught (the mechanism's type and whether it was handled), and each stack frame by file, function, module, line and whether it is our code, never a local variable or a line of source.
  • A route failure, an uncaught error or a 5xx a route returns on purpose: the method and the route template (never the path as requested), and no header. A failure before a route is matched carries neither, and a 4xx refusal sends no report.
  • Tags: component (backend or jobs); deployment, the hashed deployment id, when the telemetry secret is valid; failure_class (model_call or provisioning); error_class, the kind of a model failure (transport, http_status, body_not_json, body_shape or invalid_output); and pass and feature when a jobs pass failed.
  • A laptop's failure report, at most ten an hour per laptop: the message winner login failed or winner update failed, the logger name winner, with command, stage, http_status when there was one, error_type, winner_version, agentsview_version when the laptop could read it, os, arch and device, the hashed Device id, left out when the telemetry secret is unset or too short to hash with, and nothing else.
  • Never: a request body, a query string, a cookie or a header, an identity or a user, a server name, a local variable, an error or log message, a breadcrumb, prompt text, a repository name, a file path other than a stack frame's, a key, or a setting value other than the environment name above. No sessions or client reports are sent.

What a timing carries

A timing says how long a request to one route, or one jobs pass for one team, took, and how long each database statement, outbound call and model call inside it took. Each is rebuilt from a fixed list of fields before it leaves, as an error report is, and goes to the same project. FLEETVIEW_SENTRY_TRACES_SAMPLE_RATE is the share sampled: blank is 2 in 100, and 0, or a value that is not a share between 0 and 1, sends none. None is sent while error reports are off.

  • Every timing: an event id, its type (transaction), its start and end time, the platform, the release version, the environment name, the SDK's name, version, package and integration list, a trace id and span id this deployment made (never one a caller sent), its kind (http.server for a request, pass for a jobs pass), its source (route or task), and the tags component (backend or jobs) and deployment, the hashed deployment id, when the telemetry secret is valid. No trace header travels with it.
  • A request: the route template (never the path as requested), the method when it is one of GET, HEAD, POST, PUT, PATCH, DELETE or OPTIONS, and the status class (1xx to 5xx). A request that matched no route, and the health check, send none.
  • A jobs pass: the pass name, never the team.
  • Inside either, each with its span id, the span id it sits under, the trace id, its kind (db, http.client or model) and its start and end time: a database statement as its kind alone (SELECT, INSERT, UPDATE, DELETE, WITH, BEGIN, COMMIT, ROLLBACK, SET, CREATE, ALTER, DROP, GRANT, REVOKE, DO, COPY, or other); an outbound call as http; a model call as the model's name for this host's model or team for a team's own, with its prompt and completion token counts when it succeeded.
  • Never: statement text or its values, an outbound call's address, path, query or host, a header, a query string, a request body, a prompt, a reply, code, a repository name, a file path, an error message, a local variable, an identity or a raw id. No outbound call carries a trace header, and none sent in is followed.

What a check-in carries

A check-in is rebuilt from a fixed list of keys before it leaves, as a report is, so a key this list does not name never reaches the vendor. It is sent only while error reports are on and the telemetry secret is valid: without the hashed deployment id every deployment would share one monitor.

  • Type: check_in, which marks the item as a check-in.
  • Event id: a random id, which the SDK needs to send it.
  • Check-in id: a random id that pairs a run's end with its start.
  • Monitor slug: the monitor, its component and the deployment, one of jobs-sweep-<id>, backend-backup-<id> and backend-certificate-renewal-<id>, where <id> is the first 16 hex characters of the hashed deployment id.
  • Status: in_progress, ok or error.
  • Duration: seconds, on the end of a run whose start was seen, otherwise none.
  • The monitor's schedule: its interval, check-in margin and longest run time, from which the vendor makes and updates the monitor, and the failure and recovery thresholds and the time zone, UTC.
  • Environment: the environment name, as an error report carries it.
  • Never: an error's text, a path, a host or file name, an org, a tag or any free text. The monitor and status come from closed lists and the duration is a number the run measures.

What a browser error carries

A control-plane page reports an error its own scripts raise, an uncaught error or an unhandled rejection, only while usage counts are on and only when what was thrown is an Error with a stack. The page rebuilds the event from a fixed list before it leaves, so nothing else the library gathered goes with it. A page sends at most five a load, and none for an error with no frame in the page's own scripts or whose message holds the page's address.

  • The error's type name where it is one of JavaScript's own (Error, TypeError and the other standard constructors), else Error.
  • For each frame of the page's own scripts, its line and column, its function where that is a plain name, and the route template as its file.
  • The route template as the page ($current_url, $pathname).
  • The hashed user as the distinct id, as for a page view.
  • component, which is web, and deployment, the hashed deployment id.
  • The page view's browser properties and the library's token.

Never: the message, the address or its query, page text, an attribute value, typed input, a frame of another script (the library's, an extension's, another origin's), or a trail of clicks, inputs or earlier steps.

Usage counts, event by event

Every event the control plane or the jobs service sends carries deployment, this deployment's id hashed under its secret, and $geoip_disable: true; a page view or a page error, which the browser sends, carries no $geoip_disable, and only a page error carries deployment. The lifecycle events below and laptop enrolled also carry component, the part of Winner AI that sent them: backend or jobs. An event whose id is not a person's also carries $process_person_profile: false; a person's key actions are sent under a hashed user, and so is a page view or a page error where the page has one to hash. Every id Winner AI sets is an HMAC-SHA256 under FLEETVIEW_TELEMETRY_SECRET, so the analytics host sees no raw id and cannot reverse one. The one exception is a page view or a page error on a page with no hashed user: the library then sends its own random id, kept for the tab, which is no HMAC and names no one; a tab that signs out or changes person starts a fresh id and never carries the last person's hash.

Never sent: a prompt, a reply, code, a repository name, a file path, a form field, a name, an email, a setting's value, a model key, an endpoint host, an error message or a raw id. The one model name that leaves is $ai_model for the operator's own host model; a team's model is sent as team, because its name may be the customer's. A Bedrock model id given as an ARN is sent as its last segment, because an ARN holds the AWS account id.

Event When Id Properties
member added, device revoked, person removed, person restored, org schema migrated, benchmark run queued, team model saved, team model reset, team model key saved, team model key removed, licence uploaded, skill generated, skill regenerated, skill candidate dismissed, skill edited, skill approved, coaching finding disputed, laptop login decided, repo choice queued A person's change, after its route answers below 400 The person, hashed route (the route template, never the address), status_class (2xx or 3xx), org (hashed), deployment
setting saved, setting reset A person's change, after its route answers below 400 The person, hashed route (the route template, never the address), status_class (2xx or 3xx), org (hashed), setting_key (the setting's key on Settings, never its value), deployment
pass completed Once per pass run The team, hashed feature, pass_version, outcome (succeeded, failed or skipped), rows_in, rows_out, duration_ms, model_source, deployment
$ai_generation Each model call, from a pass or from Skills' Generate and Regenerate, a failed call included The team, hashed, inside a pass or a Skills generation; this deployment, hashed, otherwise $ai_trace_id, $ai_model, $ai_provider (openai-compatible, or amazon-bedrock for a call through Amazon Bedrock), $ai_input_tokens, $ai_output_tokens, $ai_latency, attempts, finish_reason (stop, length, tool_calls, content_filter or function_call; any other value the endpoint gives is sent as other), $ai_is_error, error_class (the class name, never the message), feature, pass_version, model_source, deployment
winner login failed, winner update failed When an enrolled laptop reports that the command failed, at most ten an hour per laptop The laptop, hashed stage, http_status, error_type, winner_version, agentsview_version, os, arch, deployment
laptop enrolled Each successful enrolment, a re-login included: a re-login enrols a new laptop row under a new hashed id, so these count enrolments, not distinct laptops The laptop, hashed org (hashed), os, arch, winner_version (each a release target or a bare version; any other value is sent as other), component, deployment
deployment installed, telemetry switched on The first start with usage counts on; a start with them on after one with them off This deployment, hashed component, deployment
deployment heartbeat Once per UTC day, from the jobs service This deployment, hashed version (this release, or other), seats (people with at least one enrolled laptop), laptops_pushing, laptops_stale, laptops_quiet, laptops_never, winner_version_counts, os_counts, arch_counts (counts of enrolled laptops per value), component, deployment
deployment upgraded When scripts/upgrade-server.sh completes an upgrade, from the new release's control plane This deployment, hashed version (the new release, or other), component, deployment
deployment rolled back Not sent yet This deployment, hashed version, component, deployment
licence state changed From the jobs service, when its sweep finds the licence state moved; the first sweep after the upgrade records the state without sending This deployment, hashed licence_state (licensed, grace or lapsed), component, deployment
$pageview Each load of a control-plane page while usage counts are on, sent by the browser through /lumen/; a form whose answer is a whole page (a refused form, Add row and Remove row, the new member's one-time page) counts as a page view of the form's own route; never the documentation, the laptop-login approval page or the dashboard The person, hashed; where the page has no one to hash (a person with no roster row where Auth0 holds the roles, or the host-run bypass before its roster row exists), the library's own random id for the tab, which names no one $current_url and $pathname (both the route template the server matched, never the address or its query), $lib, $lib_version, $browser, $browser_version, $os, $device_type, $session_id, $window_id, $process_person_profile, and the library's own token (the analytics project key, public by the vendor's design) and distinct_id (the hashed id, or that random id), without which the event is not sent or is filed under no one
$exception An uncaught error or unhandled rejection in a control-plane page's own script while usage counts are on, at most five per page load The person, hashed, as for a page view $current_url and $pathname (both the route template, never the address), $exception_list (the error's type name and, for each frame of the page's own scripts, its line, column, function where that is a plain name, and the route template as its file; never the message), component (web), deployment, $lib, $lib_version, $browser, $browser_version, $os, $device_type, $session_id, $window_id, $process_person_profile, and the library's own token and distinct_id, as for a page view

The deployment heartbeat is not the operator's dead-man's switch (FLEETVIEW_HEARTBEAT_URL), which is unchanged and sends nothing to Winner AI.

A Benchmarks replay call to a Route is counted as team, in $ai_model and model_source alike, because its model is named by the Route.

A refused form sends no key action, and neither do the Benchmarks routes editor's Add row and Remove row, which only redraw the page. Each redrawn page still sends its page view, under the form's route: /settings/{key} for the two buttons.

What is never instrumented, and what a laptop does

  • The Dashboard is never instrumented. Its pages carry no analytics library, no error reporter and no page view. The only script Winner AI adds to them is the brand script, which sends nothing.
  • The Dashboard's telemetry is switched off. The pg serve container runs with AGENTSVIEW_TELEMETRY_ENABLED set to 0 and --no-update-check, and the control plane's schema setup runs with AGENTSVIEW_TELEMETRY_ENABLED set to 0.
  • The collector's telemetry on laptops is switched off too. Every service winner installs sets AGENTSVIEW_TELEMETRY_ENABLED to 0 and AGENTSVIEW_DISABLE_UPDATE_CHECK to 1, and the collector's config, which enrollment writes, sets disable_update_check = true. A daemon you start yourself afterwards, from the agentsview desktop app or a shell, carries your own environment instead, so those two variables do not reach it. What the laptop sends, and where says what a laptop still reaches, which includes the collector's model-price refresh, which has no switch.
  • A laptop contacts no telemetry vendor. winner holds no vendor address, key or telemetry state. It reports a failed winner login or winner update to the control plane it enrolled against and nowhere else. The one exception to this bullet is the daemon you start yourself, above. What else a laptop reaches, none of it telemetry, is in What the laptop sends, and where.
  • The report is not stored. While error reports or usage counts are on, the control plane builds the events listed above from it (the laptop's error report, and winner login failed or winner update failed) and keeps no copy. It holds only the time of each report, in memory, to keep a laptop to ten an hour, whether or not telemetry is on. With both off it builds nothing, and the laptop is answered the same either way.
  • Page recording cannot be switched on in this release. It is built and held off whatever FLEETVIEW_POSTHOG_REPLAY says, and its Settings row reads off.

There is no developer switch. A developer cannot turn the report off on the laptop; the operator's settings above decide whether the control plane passes anything on. winner login prints this notice once the laptop is enrolled, and When winner fails shows it too:

If winner fails after this laptop is enrolled, it sends a short report to your organisation's Winner AI server: the command, the step, the HTTP status, the kind of error, the versions, and your operating system and processor type. The report never contains prompts, code, repository names, file paths, emails or the text of an error. If your organisation's operator has switched telemetry on, the server passes those fields on to Winner AI's error reports and, under a scrambled laptop id, its usage counts. winner sends the report nowhere else.

Where it goes

Each kind goes where its setting points. The values Winner AI hands over point at Winner AI's own projects: error reports, timings and check-ins at Sentry, and usage counts, page views and browser errors at PostHog. Winner AI holds no copy of a deployment's telemetry secret, so it cannot reverse a hashed id.

Some of what happens to an event happens in those projects, where a deployment cannot see it. We keep them set as follows:

  • Sentry: its data scrubber is required, with its default rules; IP addresses are not stored; events are kept for 30 days. Sentry still derives a country and city from the address an event arrives from before it drops the address, so an error report can be kept with a location near the server that sent it.
  • PostHog: the client address is discarded and its GeoIP transformation is off, so no location is kept; events are kept for 30 days; session recording, autocapture, heatmaps, exception autocapture, web vitals and console-log recording are off.
  • Keys: every deployment we hand values to sends usage counts under one shared analytics key, and deployments are told apart there only by the hashed deployment. Each gets an error-report key of its own, which we can revoke alone.

If you later blank FLEETVIEW_SENTRY_DSN or the telemetry secret, or rotate the secret, which changes the hashed deployment id, the check-in monitors we hold for your deployment stop hearing from it and report it missed. That alert stands with us until we remove the monitor, so we may ask whether you meant it. Nothing on your side needs doing.