> For an index of all Botscent documentation, see https://botscent.nibnalin.me/llms.txt.

# The verdict

What the three keys of a verdict mean, and how Botscent chooses the reasons and the agent name.

A *verdict* is the object that both halves of Botscent return. It says whether an agent is visiting, and names the agent when the evidence allows.

```ts title="Verdict"
type Verdict = {
  type: 'agent' | 'human'
  agent_name?: string
  reasons: Reason[]
}
```

## What `type` means

`type` is `agent` when `reasons` has at least one reason. `type` is `human` when `reasons` is empty.

`human` means that Botscent observed no agent evidence. It is never evidence of a person. A probe that the browser does not support, a probe that failed and a negative observation all count as no evidence. Nothing makes a verdict more human.

A person's verdict is always exactly this object:

```ts title="Verdict"
{ type: 'human', reasons: [] }
```

## What `reasons` lists

Each reason is the id of one piece of evidence that held, such as `signer.web-bot-auth.verified` or `claude.marker.active`. The list has no duplicates. It follows the order of the catalogue, strongest first. [Reasons](/docs/reasons) lists every reason.

A newer server half can send reasons that an older page half does not know. Those reasons follow the known ones, in the order received.

Some evidence counts only together with other evidence:

* `instinct.credentials.wrappers` and `instinct.geetest.accessor-pair` count only together. Then both are listed.
* The 3 `codex.*` reasons count only when at least 2 of them hold. Then every one that holds is listed.

## How `agent_name` is chosen

`agent_name` is a lower-case id, such as `chatgpt` or `claude-chrome`. [Agents](/docs/agents) lists every name. When there is no name, the key is absent. It is never `null` or an empty string, and a `human` verdict never has it.

Only 2 kinds of evidence can give a name:

* **Declarations:** a Web Bot Auth signer host, verified or not. A user-agent token. What the page's own `navigator` declares. In the page, the name in the server's `Server-Timing` entry.
* **Product shapes:** the Muse accessor family, the Instinct wrappers with the GeeTest pair, the Codex shell, the ChatGPT badge and the Claude marker.

Botscent chooses the name in this order:

1. If any declaration gives a name, the verdict gets that name only when every declaration gives the same name.
2. If no declaration gives a name, the verdict gets a product shape's name only when every product shape gives the same name.
3. In every other case, the verdict has no name.

So a request that declares 2 different products in its user agent gets no name. Its `type` is still `agent`.

Some evidence never gives a name: the webdriver flag, a `HeadlessChrome` user agent, input to a hidden document, and the platform's verified-bot field. Verification of a signature never changes the name. Verification changes only the reason, from `signer.web-bot-auth.declared` to `signer.web-bot-auth.verified`.

> **Warning:** Do not use `agent_name` to allow access. Anyone can send the headers that produce a name. Use `isVerified(verdict, name)` on the request's own verdict.

## Three verdicts with one shape

Botscent gives a verdict in 3 places. Each one describes a different thing:

| Verdict                   | Where you get it                                                              | What it describes                                                           |
| ------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| The request's own verdict | [`inspect`](/docs/server-api#inspect), or the adapter, such as `req.botscent` | What one request declared.                                                  |
| The page verdict          | [`verdict`](/docs/page-api#verdict), `useBotscent`, `subscribe`               | Whether the page half observed agent evidence at any time in this document. |
| A page report             | `readReport` on your server                                                   | The page verdict, as your code sent it. Each reason has the `page.` prefix. |

`combine` joins the request's own verdict and a page report. The joined verdict is an agent when either verdict is an agent. See [From the page to the server](/docs/page-to-server).

## How the page verdict changes

The page verdict starts as `{ type: 'human', reasons: [] }`. When the page half observes agent evidence, `type` becomes `agent` and stays `agent` for the rest of the document's life.

The name and the reasons depend only on the set of observations. The order in which evidence arrives does not change them, and neither do duplicates. The name can change when stronger evidence arrives, such as a declaration after a product shape.

During server rendering, framework values show `{ type: 'human', reasons: [] }`. After hydration, they follow the page verdict. So the first render in the browser always matches the server's HTML.

## Plain data

A verdict has these 3 keys only. The TypeScript and Python halves give the same JSON for the same evidence. In TypeScript, a verdict is a frozen object. In Python, a verdict is a `dict`.

## What the verdict does not tell you

* It does not prove that a person is present.
* It does not say who controls the browser now, or who performed a particular action.
* It has no confidence value.
* It reports a person as an agent when the person works inside an agent's surface. Examples are the Codex and Cursor in-app browsers, Grok Bot's cloud computer, and a Muse or ChatGPT agent session that the person took over.
* It does not prove which agent is visiting. Only `isVerified` with a name proves that. See [The trust model](/docs/trust-model).

## Related

* [The trust model](/docs/trust-model): which verdict you can use to decide access.
* [Reasons](/docs/reasons): every reason, which half gives it, and whether it names an agent.
* [Agents](/docs/agents): every agent name and the evidence for each.
* [Server API (TypeScript)](/docs/server-api#inspect): the options of `inspect`.
