The verdict
glassprint does not hand you a bare id and a number. It returns a typed class— one of four — alongside the id, the confidence, and the additive signals. The class is purpose-built for the AI-agent era: it distinguishes a real person from a declared agent, from undeclared automation, from "we don't know."
The four classes
| class | What it means | How it is reached |
|---|---|---|
human | A real person, above the confidence floor. | Output of the automation classifier when no human-implausible picture is present. |
declared_agent | An agent that authenticated itself. | Set iff Web Bot Auth (RFC 9421 Ed25519 HTTP Message Signature) verifies at the HTTP layer. Authenticated ≠ trusted — the agent is still scored. |
undeclared_automation | Automation hiding as a human. | Output of the automation classifier when a human-implausible picture clears the floor and topology allows it. |
unknown | We refuse to label. | Below the confidence floor, or a single tell, or capped behind a CDN. The honest default. |
The confidence floor — it refuses to guess
There is a hard floor (confidenceFloor = 0.6). Below it, the class degrades to unknown rather than a coin-flip label. A lone navigator.webdriver or a lone software GPU resolves to unknown at confidence ~0.4 — which is also how a Tor or resist-fingerprinting user with one tell is protected from a false bot call.
The two-tells rule
A bot verdict is the only thing automation evidence can justify, and it requires a human-implausible picture: ≥2 strong tells, or 1 strong tell plus a second independent tell. The strong tells are navigator.webdriver and a headless user-agent. A software GPU (SwiftShader/llvmpipe/Mesa) is weak on its own — millions of real humans run it — so a lone software-GPU never reaches a verdict.
min(0.97, 0.55 + 0.18 × tellCount). We publish population rates with Wilson confidence intervals, not a single "accuracy" number — we tell you where our bullets stop rather than quote a headline you can't reproduce.Farble suppression — never false-positive a privacy user
Brave and Tor "farble" their canvas and audio — each read returns a slightly different value. The client flags this divergence (two-read detection). When the server sees a farble flag it resolves to a privacy defense, caps the suspect score at 0.2, and blocks the bot verdict — it suppresses suspicion rather than flagging the user as tampered. The one exception is anti-evasion: if the farble claim co-occurs with the webdriver-plus-headless combo, the defense is dropped and farble-claim-with-automation is flagged.
The topology cap
A high-confidence undeclared_automation verdict additionally requires topology === 'direct-edge'. Behind a CDN or an untrusted peer, confidence is capped (cdnConfidenceCap = 0.45) and degrades to unknown. We will not assert automation at high confidence when the network tier could be spoofed.
Web Bot Auth — the declared path
A well-behaved agent can declare itself by signing its request (RFC 9421 Ed25519 HTTP Message Signature, covering @authority + signature-agent with a fresh created). When that verifies, the class is declared_agent. Crucially, authenticated does not mean trusted: the agent is still scored, the class just records the declaration so you can allow good bots without lying about what they are.
Human-review override
A reviewer who confirms a real person (for example by overturning an Art-22 contest) writes a durable-keyed neverAutomation override that downgrades a future undeclared_automation to human. It is keyed on the epoch-stable durable id, so it survives visitorId rotation.
Reading it
const r = await gp.identify({ tag: 'login' });
switch (r.class) {
case 'human': break; // proceed
case 'declared_agent': allowKnownAgent(r); // still scored — check r.suspectScore
case 'undeclared_automation': raiseRisk(r); // additive risk signal, never an auto-block
case 'unknown': break; // we refused to guess — fall back to your own rules
}