# How Heedeo works

Heedeo identifies every agent on your site and lets you decide who gets in and what they can do. For each request it answers three questions: who built the agent, who sent it, and what your rules say it may do on this route.

## Request lifecycle

Everything below runs in your middleware, before the request reaches your route.

1. **Normalize and match the route.** The path is normalized (see https://heedeo.com/docs/rules.md), then matched: exact routes first, then `/x/*` prefixes (longest first), then the catch-all `"*"`.
2. **Classify.** Is this a person or an agent?
3. **Verify.** If the request carries a Web Bot Auth signature, check it cryptographically, then check that the operator is on `trustedOperators`. Both pass: **verified**. Otherwise the agent is **detected** (recognised by user agent but not signed, or signed by an untrusted operator) or **unknown**, and rules treat both as `unknown`.
4. **Identify the principal.** Work out the person or company the agent acts for (see below).
5. **Apply the client rule.** `humans`, or `agents.verified`, `agents.noPrincipal` or `agents.unknown`.
6. **Apply the per-principal cap.** If the route has `perPrincipal` and the request has a principal, count it. The cap covers the whole route, people and agents alike. A client rule that already blocked stays blocked.
7. **Record.** Send decision metadata (route, verdict, agent, reason, rule line) to your dashboard, unless you turned that off. Never the request body.

## Agent tiers

| Tier | Meaning | How rules treat it |
|---|---|---|
| verified | A valid Web Bot Auth signature from an operator on `trustedOperators`. | `verified` |
| detected | Recognised by user agent but not signed (or signed by an untrusted operator). | Treated as `unknown`. |
| unknown | No valid signature from a trusted operator, and not recognised either. | `unknown` |

The landing page legend uses "signs its requests" for verified, "detected, not signed" for detected, and "unknown" for unknown.

## Verification: who built the agent

Heedeo uses the open Web Bot Auth standard:

- The agent signs each request with **HTTP Message Signatures** (RFC 9421, https://www.rfc-editor.org/rfc/rfc9421) using an **Ed25519** key. The signature travels in the `Signature` and `Signature-Input` headers.
- The `Signature-Agent` header names the operator, for example `"https://chatgpt.com"`.
- The operator publishes its public keys at `/.well-known/http-message-signatures-directory` on that origin. The signature's `keyid` is the key's JWK SHA-256 thumbprint (RFC 7638). Heedeo finds that key and checks the signature against it.
- A valid signature is not enough on its own: anyone can publish a key directory on their own domain. The operator must also be on your `trustedOperators` list. A valid signature from anyone else counts as unknown.

Agents known to sign today include ChatGPT agent, Goose, Browserbase and Anchor Browser, per Cloudflare's signed agents list: https://blog.cloudflare.com/signed-agents/. The demo policy trusts `chatgpt.com`, `browserbase.com` and `anchorbrowser.io`. Which operators you trust is your call.

A user agent string alone is never proof. Anyone can type "ChatGPT" into a header; that makes an agent detected at most, and detected is treated as unknown.

## The principal: who sent it

The principal is the person or company the agent acts for. Heedeo takes it from the strongest source available:

1. **A signed principal token**, when the agent carries one. Examples: Skyfire KYAPay, Visa Trusted Agent Protocol.
2. **The passkey handoff.** When a rule says `challenge: "passkey"`, a real person finishes the step with a passkey, and that person becomes the principal.
3. **Your own user ID**, after signup.

An email typed into your form is never treated as the principal.

## Counting: per principal, not per IP

Limits such as `perPrincipal: "5/day"` count per principal. An IP costs a fraction of a cent, so a farm can rotate through hundreds. A new IP, a new browser or a new agent does not reset the count when the principal is the same.

Where the per-principal counters live in your deployment, and whether a counter outage fails open or closed, are agreed during onboarding.

## What a passkey does and does not prove

A passkey proves a real person with a real device finished the step. It does not prove a unique human: one person can hold several passkeys, and so can an email address. So a determined person can still look like several principals. Linking one person's several accounts relies on signed principal tokens when the agent carries one, plus your own signals (payment card, verified email domain, device), set up with you at onboarding.

## Verdicts

| Verdict | HTTP status | What happens |
|---|---|---|
| allow | 200 | The request reaches your route. |
| challenge | 401 | The request waits on a person: a passkey step. Whoever finishes it becomes the principal. |
| throttle | 429 | Rate-limited, not refused. The response carries `Retry-After` (`retryAfter` in seconds). |
| block | 403 | The request does not reach your route. Logged with its reason. |

These are the statuses `/api/decide` returns for the demo policy.

## What people see

People never see a puzzle. The only thing a person sees is one passkey confirmation, when an unknown agent signs up on their behalf.

## Worked examples on the demo policy

All real output of `/api/decide`, trimmed to the main fields:

| Request | verdict | status | rule.line |
|---|---|---|---|
| person on /signup | allow | 200 | 6 |
| trusted agent, principal maya, 2nd today | allow | 200 | 8 |
| trusted agent, no principal | challenge | 401 | 9 |
| unsigned agent | challenge | 401 | 10 |
| valid signature, untrusted operator | challenge | 401 | 10 |
| principal maya, 7th today | throttle, retryAfter 3600 | 429 | 13 |
| principal maya, 11th today | block | 403 | 13 |
| person, principal dave, 12th today | block | 403 | 13 |

```bash
curl "https://heedeo.com/api/decide?route=/signup&client=agent&signed=true&operator=chatgpt.com"
curl "https://heedeo.com/api/decide?route=/signup&client=human&principal=dave&count=12"
```

---

Docs index: https://heedeo.com/docs.md. How to verify our claims: https://heedeo.com/trust.md. Book a call: https://cal.com/heedeo/30min

Updated: 2026-10-02
