Navigation
Getting Started
Guides
Integrations
Guides
Monitor AI Agent Access
Check that your pages and APIs respond to AI crawlers and agents the way they respond to everyone else: agent personas, parity verdicts, sampling, alerting, and the OpenTelemetry signals they emit.
A growing share of the requests reaching your site come from AI crawlers and agents: a model fetching your pricing page for a user, a search index refreshing your docs, a training crawler. These clients are often treated differently from a browser. A WAF rule blocks them, a bot challenge stops them, or a page that renders fine for a person returns an empty shell to a fetch.
None of that shows up in an ordinary HTTP monitor, because the monitor is not asking as an agent. Agent personas fix that. An HTTP monitor with personas switched on repeats its request once per persona, using that crawler's User-Agent, and compares each response with the normal one.
How a persona run works
- The monitor makes its normal request. This is the baseline. Its status, response time, assertions, anomaly baselines and SLOs behave exactly as they do without personas.
- If the baseline succeeded, Yorker repeats the request once for each selected persona, one at a time, replacing the
User-Agentheader with the persona's. Everything else (method, URL, other headers, auth, body) stays the same. - Each persona response is compared with the baseline and given a verdict.
- The verdicts are stored on the run, shown on the run page, and emitted to your OpenTelemetry backend.
A run has a fixed time limit. Each persona request times out after 15 seconds or the monitor's timeout, whichever is shorter, and if a slow target uses up the run's time before every persona has gone, the remaining personas are skipped for that run and listed as not run. They are not billed.
If the baseline fails, the personas are skipped for that run. The run already tells you the URL is down for everyone, and there is nothing healthy to compare against.
Personas
| Persona | Operator | What the real client does |
|---|---|---|
claude-user | Anthropic | Fetches a page when a Claude user asks about it |
claude-searchbot | Anthropic | Indexes pages for search results |
claudebot | Anthropic | Crawls for model training |
chatgpt-user | OpenAI | Fetches a page when a ChatGPT user asks about it |
oai-searchbot | OpenAI | Indexes pages for ChatGPT search |
gptbot | OpenAI | Crawls for model training |
perplexity-user | Perplexity | Fetches a page when a Perplexity user asks about it |
perplexitybot | Perplexity | Indexes pages for Perplexity search |
If you are not sure where to start, pick the three user-triggered fetchers (claude-user, chatgpt-user, perplexity-user). A block on those is felt immediately by someone asking a question about your product. Add the search and training crawlers when you have a policy for them that you want to verify.
Verdicts
| Verdict | Meaning |
|---|---|
| Matches | The persona got an equivalent response. |
| Blocked | The baseline got a 2xx and the persona did not. |
| Bot challenge | The edge answered the persona with a challenge page instead of your content. |
| Assertion failed | An assertion that passed for the baseline failed for the persona. |
| Request failed | The persona request errored or timed out. |
The assertions checked against a persona response are the ones about content and access: status_code, body_contains, body_matches, body_json_path and header_value. Timing, TLS and OpenAPI assertions describe the endpoint, not who is asking, so they stay with the baseline.
This makes body_contains the most useful assertion to pair with personas. A page that returns 200 to an agent but leaves out the content is the failure a status code never shows:
assertions:
- type: status_code
value: 200
- type: body_contains
value: "Plans start at"Response size and content type are recorded for every persona alongside the baseline so you can compare them, but a difference in size alone is not a verdict. Serving agents a smaller Markdown version of a page is a legitimate choice.
Switch personas on (Web UI)
- Open the monitor and click Edit.
- Under the assertions, switch on Agent personas.
- Tick the personas to run as.
- Choose How often: every run, or 1 in N runs.
- Choose what happens when a persona differs: record it on the run, or fail the run.
- Save. The next run includes the personas.
Personas are available on HTTP monitors. New monitors are created without them; add them from the edit page.
Switch personas on (Monitoring as Code)
monitors:
- name: Pricing page
type: http
url: https://www.example.com/pricing
frequency: 5m
locations:
- loc_us_east
- loc_eu_central
assertions:
- type: status_code
value: 200
- type: body_contains
value: "Plans start at"
agentPersonas:
personas:
- claude-user
- chatgpt-user
- perplexity-user
every: 6
failOnDivergence: trueWriting the block switches personas on. To pause them and keep the settings, add enabled: false.
Use an up-to-date CLI everywhere that deploys this config, including CI. A CLI release from before agent personas does not know the block: it ignores it in the file and removes personas from the monitor on its next deploy. The full field list is in the configuration reference.
How often to run them
every (in the UI, How often) runs personas on 1 in that many runs. 1 runs them every time.
Bot rules change when someone edits a WAF policy or a CDN setting, not minute to minute, so sampling is usually enough. A monitor that runs every 5 minutes with every: 6 checks personas every half hour.
Sampling is counted separately at each location. It restarts when you change the persona settings, so the first run after an edit always includes personas and you can confirm the result straight away.
Cost
Each persona request counts as one additional HTTP run, per location. The example above adds three runs to 1 in 6 executions at each location, which averages half an extra run per execution. every is the control: raise it to spend less.
Rate limits
Yorker limits how many requests per second your monitors can send to one hostname. Persona requests count toward that limit: a monitor with three personas counts as four requests per run, whatever every is set to. If adding personas would take a hostname over the limit, the save is rejected with the same error as raising the frequency would be.
Read the results
Open any run that included personas and scroll to Agent Personas. The table shows the baseline on the first row and one row per persona with its verdict, status code, response time, body size and content type.
Runs that were not sampled for personas do not show the section.
Because every location runs its own personas, a block that only happens in one region shows up as a difference between locations. That is common with geo-specific WAF rules.
Alerting
By default a difference is recorded and the run stays successful. That is the right setting while you learn how your site treats each persona.
To be alerted, set When a persona differs to Fail the run (failOnDivergence: true). A run with a differing persona then fails, with an error message naming the persona and the reason, and your existing alert rules apply. For example, consecutive_failures fires after repeated blocks and multi_location_failure fires when the block is seen from several regions. See Set Up Alerts.
Failing the run also counts against any SLO on the monitor. If you want agent access tracked separately from human availability, create a second monitor for the same URL with personas and failOnDivergence on, and leave the original as it is.
Browser monitors: run as an agent
Agent personas on an HTTP monitor compare one request against another. A browser monitor has a different option: Run as. Set it and the whole monitor runs presenting that agent's user agent, on every run, in a real browser. Its steps, assertions and filmstrip then show what a browser identifying as that agent gets.
This is how you see the bot wall itself. A blocked HTTP persona tells you the status code was 403; a browser monitor running as that agent shows the challenge page or the access-denied screen in the filmstrip, and which step of a flow it interrupted.
In the edit form, choose an agent under Run as. In YAML:
monitors:
- name: Checkout as ChatGPT
type: browser
script: ./monitors/checkout.ts
frequency: 15m
agentPersona: chatgpt-userThere is no separate "normal" request to compare with: the monitor simply is that client, and it passes or fails on its own steps and assertions. To compare, keep a second monitor for the same flow on the default browser. When the default one passes and the persona one fails, the difference is how your site treats that agent.
It is billed as an ordinary browser run, and alerts and SLOs work as they do for any browser monitor. Runs carry synthetics.agent.persona, synthetics.agent.operator and synthetics.agent.purpose as resource attributes on their metrics, traces and logs, so you can split browser telemetry by agent.
Two things to keep in mind when reading the result:
- As with HTTP personas, the run carries the agent's user agent and not its verified identity.
- A browser runs JavaScript. The crawlers among the personas (ClaudeBot, GPTBot, the search bots) fetch pages without running it, so a page that only works with JavaScript will look fine in this monitor while being empty to the real crawler. Use an HTTP monitor with a
body_containsassertion and personas to check what a non-JavaScript client receives.
Browser monitors: agent view
Agent view is a second option on browser monitors. It does not change how the monitor runs. At the end of each successful run it measures the page the run ended on in two ways and reports both on the run:
Text present without JavaScript. Most AI fetchers request a page and read the HTML; they do not run scripts. Agent view compares the text in the HTML document as it was served with the text the browser ended up rendering, and reports the share that was already there. A server-rendered page scores near 100%. An application that ships an empty shell and builds the page in the browser scores near zero, which is what a fetcher that does not run JavaScript receives.
Controls an agent can name. Agents that drive a browser work from the accessibility tree, where a control is its role and its name. A button containing only an icon, with no label, is an anonymous button to them. Agent view counts the visible, enabled buttons, links and form fields on the page, and lists up to ten that have no accessible name:
Controls an agent can name 9/12
3 visible controls have no accessible name.
button.icon-btn.close
a
input#search[type=text]
A name can come from aria-label, aria-labelledby, an associated <label>, alt text, visible text, or title. A placeholder does not count: it disappears when the field has a value.
Switch it on under Agent view in the edit form, or in YAML:
monitors:
- name: Checkout flow
type: browser
script: ./monitors/checkout.ts
agentView: trueThings to know:
- It never fails a run. Both numbers describe the page; what to do about them is your call.
- It measures the page the run ended on. For a scripted flow that is the last page the script reached, so a monitor that ends on an order confirmation reports on the confirmation page.
- The without-JavaScript figure compares against the last HTML document the browser loaded. If a flow moved between views by client-side routing, that is the document the app started from, not a separate one per view. The figure is left out on a run where that document's body could not be read back or was not HTML.
- It runs on successful runs only. A run that fails or times out has no agent view.
- It adds no extra requests and no cost: it reads the page the run already loaded, and the time it takes is not counted in the run's duration.
- A page whose HTML is larger than 256 KB, or with more than 20,000 candidate elements, is measured only in part: the without-JavaScript figure is left out for the first, and controls are counted up to the limit for the second.
- The results are emitted as the gauges
synthetics.agent.view.no_js_text_ratio,synthetics.agent.view.controlsandsynthetics.agent.view.unnamed_controls. - It works with or without Run as. Together, they show what a browser presenting an agent's user agent gets and how legible that page is to it.
Private locations need a current runner
Persona requests are made by the runner. A private location running an image from before this feature ignores the setting and reports ordinary results with no Agent Personas section, so Fail the run never triggers there. The same goes for Run as on a browser monitor: an older runner uses the default browser. Update the runner image at each private location before relying on it for alerting.
OpenTelemetry
When your team has an OTLP endpoint configured, every persona request produces a synthetics.agent.persona.checked log event in your backend: INFO when it matched, WARN when it differed. The event carries the persona, its operator, the verdict, the persona's status code and the baseline's.
A query for blocked agents by region looks like this in most backends:
event.name = "synthetics.agent.persona.checked"
AND synthetics.agent.divergence != "none"
GROUP BY synthetics.agent.persona, synthetics.location.name
Monitors on private locations with runner-side OTLP enabled also emit per-persona metrics and a child span per persona request. The full attribute and metric list is in OpenTelemetry > Agent persona signals.
What a persona run does and does not prove
A persona request carries the crawler's user agent. It does not carry the crawler's identity.
The operators sign their real traffic and publish the IP ranges it comes from, and some CDNs use those to tell a genuine crawler from a request that only claims to be one. A persona request comes from a Yorker location and is not signed by the operator.
So a persona run tells you how your stack treats a request presenting that user agent. That covers the common cases: WAF rules, rate limits, bot walls and application code that match on the user agent string. It cannot tell you how a CDN that verifies bot identity treats the genuine crawler, and such a CDN may answer a persona request more strictly than the real thing.
Treat a block as a strong reason to look at your rules, and a match as evidence that nothing keyed on the user agent is in the way.
Related
- Check Agent Readiness to compare these results with what your robots.txt says, and to monitor llms.txt and Markdown versions
- Monitor MCP Servers for agents that call your tools rather than fetch your pages
- Assertions
- Configuration reference