Skip to content

ADR-006: Verified service agents

  • Status: Proposed, awaiting owner sign-off. No Phase 11 code is written until the owner answers the questions at the end.
  • Date: 2026-10-31

Phase 11 lets a business such as a dental clinic run an agent that strangers can find and book with. Owner decision D3 sets two tiers. Verified website is automatic: the business publishes its agent’s key on its own domain, and every node checks that itself. Verified profession is a manual check of licence and registration by the Stelloz team, issued as a signed attestation that nodes verify. Decision D4 puts the directory and the verifier next to the relay. There are no payments.

ADR-005 gives every agent a signed profile card and an opt-in public directory at /v1/listings, where cards are labelled “self-described”. This ADR adds what can be checked on top of a card. The safety code stays the identity check, both owners still consent to every connection, and a badge never changes policy by itself.

A business card is an ADR-005 card with v: 2, because v1 rejects unknown fields by design. Nodes from P11.1 accept both. A v1-only node treats a v2 card as unreadable, as ADR-005 does for any card it rejects.

New field Rule
business.org Trading name, 1 to 120 characters.
business.profession One code from a fixed profession list in packages/schemas, such as dentist. Each code maps to one ADR-005 category. Optional.
business.location { city, region, country }: city up to 60 characters, ISO 3166-2 region, ISO 3166-1 country. An optional street of up to 120 characters.
business.services Up to 10 phrases of 60 characters.
website A hostname the agent claims to represent, as a lowercase A-label (xn-- form for IDNs), no port, path or IP literal. Optional.
templates Up to 5 service task types the agent accepts (section 5). Optional.

Signing, verification, the 4 KB limit and distribution are unchanged, and a card still never carries the owner’s personal details.

Location and format. The business serves https://<host>/.well-known/agentlink.json:

{
"v": 1,
"agents": [
{ "address": "al1...", "label": "Front desk" }
]
}

agents holds 1 to 20 entries. label (optional, 60 characters) is shown only to the business. Unknown fields are rejected. The file is unsigned: control of the host is the proof.

What it binds. The address is the agent’s identity key (ADR-003), so listing the address binds the host to the key. The badge needs both directions: the agent’s signed card claims website, and that host’s file lists the address.

How the node fetches. Through the SSRF-safe outbound fetcher that cross-node handle lookup adds to apps/api (ADR-008): HTTPS on port 443 only, public IPs only, checked after DNS and pinned for the connection, no redirects, a 3-second timeout and a 16 KB cap. A 200 with application/json and a valid body is the only success.

When the node checks.

  • On receiving a card whose website is new or changed, whether in a connection request, a profile.card message or a directory result about to be shown.
  • Every 24 hours for contacts, and from a “check now” button, at most once per 10 minutes.
  • The whole file is cached per host: a success for 24 hours, a definite failure for 6 hours.
  • Discover checks only the 20 cards on screen, at most 4 fetches at once.

Failure, expiry and revocation.

Result Badge
File lists the address Shown, with “checked 3 hours ago”.
404, 410, a valid file without the address, or a card without website Removed at once.
Timeout, DNS failure, 5xx, bad TLS, redirect, oversized or invalid body Kept for 72 hours from the last success, marked “could not re-check”, then removed.

The business revokes an agent by removing its address from the file. Nodes drop the badge within 24 hours, or at once on “check now”.

Subdomains. The exact host only: book.mapledental.ca needs its own file. A host that is itself a public suffix (co.uk, github.io) is refused, using the Public Suffix List bundled with each release.

IDN and homographs. Cards and caches hold A-labels only. The dashboard shows the Unicode form only when every label passes the UTS #39 “highly restrictive” profile (one script per label), and the A-label otherwise. The registrable part of the host is shown in bold so mapledental.ca.example.net cannot pass for mapledental.ca.

What the badge shows. “Website verified: mapledental.ca”, and on hover or tap: “mapledental.ca lists this agent. Stelloz did not check who runs the website.” It proves control of a domain, not who the business is.

Who can apply. The owner, from the dashboard, for an agent that already has a verified website. The request is signed by the agent key under its own tag, ALVR1.

What the team checks.

  1. The licence or registration is active on the regulator’s public register, in the jurisdiction claimed.
  2. The name on the register matches business.org, or the register lists the licensee at that business.
  3. The applicant speaks for the business: the team calls or emails the contact the register or the verified website publishes, never one the applicant supplies, and confirms the agent’s safety code.

Attestation format. A signed JSON object, sent beside the card (in the connection request, the acceptance, profile.card, and the directory listing), never inside it.

Field Rule
v 1.
id 16 random bytes, base64url, for revocation.
address The agent’s al1 address.
subject The name as it appears on the register, up to 120 characters.
profession A code from the same fixed list as cards.
jurisdiction ISO 3166-2, such as CA-ON.
regulator The register’s name, up to 120 characters.
registration The public licence number.
website The host verified at check time.
checkedAt Date of the last manual check, YYYY-MM-DD.
issuedAt, expiresAt UTC, to the second, at most 7 days apart.
issuer The issuing key’s certificate, with its kid (below).
sig Ed25519 over AL1-ATTEST, a zero byte, and the canonical JSON of every field except sig.

Canonical JSON is the existing canonicalJson in packages/crypto, with NFC strings and integer numbers, as for cards. A node shows the profession badge only if the signature verifies, issuer chains to a pinned root, neither key nor id is revoked, it has not expired, address matches, and the website badge is shown for the same host. The badge reads “Licensed dentist, Ontario”, with the regulator, licence number and check date on tap, so anyone can confirm it on the register.

Verifier keys and custody.

  • Root key. Ed25519, generated offline and never on a networked machine. The owner holds it on two hardware tokens kept apart. Its public key is pinned in each node release. AGENTLINK_VERIFIER_ROOT_KEYS overrides it for test networks.
  • Issuing key. The root signs an issuing key certificate { v, kid, publicKey, notBefore, notAfter } under AL1-VKEY, valid 90 days. The issuing key lives in the verifier service beside the relay, sealed under that service’s master key as node keys are (ADR-002). It signs only attestations and the revocation list.
  • Rotation. A new issuing key starts 7 days before the old one ends. Each release pins the current and the next root, so a root rotation needs no flag day.
  • Issuing. The team records checks, denials and revocations with the verifier CLI on the verifier host. The service re-signs each active record’s 7-day attestation daily, and the agent’s node fetches its own from GET /v1/attestations/:address daily.

Revocation. Short-lived attestations plus a list. The service publishes GET /v1/verifier/status, signed by the issuing key, listing revoked attestation IDs until they would have expired. A separate root-signed document with a growing seq, GET /v1/verifier/keys, lists revoked issuing keys. Nodes fetch both hourly. If a node cannot fetch them, badges stay until their attestation expires, with “revocation list last updated” shown after 24 hours.

The team revokes when a re-check finds the licence lapsed or suspended, when the business asks, when the owner reports the agent key lost, or on fraud. Every record is re-checked against the register at least once every 12 months.

Compromise plan.

Lost Response Exposure
Issuing key or verifier host The root revokes its kid; a new issuing key re-signs every active record within an hour. Forged badges until nodes next poll, at most an hour.
Root key A release drops it and pins the next root; a notice goes on the docs site. Until nodes upgrade, which is why the root stays offline.

Appeals. A denial or revocation states a reason code. The applicant can appeal within 30 days to a published address. A different team member reviews it, or the owner while the team is one person, and answers within 14 days. A revocation that follows the register stays until the register changes.

Data the team keeps. Per record: the attestation, the register URL, each check’s date, result and checker, the contact used to confirm, and an email for renewals and appeals. The check uses public registers, so the team asks for no ID documents or licence scans, and deletes any sent anyway. Denied applications are deleted after 90 days.

Look-alike rule. The node computes a skeleton for each name and host: NFKC, case folding, the UTS #39 confusables mapping, without spaces or punctuation. Two skeletons match if equal, or within one edit when both have at least 8 characters. The node compares against verified agents it knows and asks the directory, GET /v1/listings/verified?skeleton=, re-verifying every card returned.

Case Warning
Unverified agent whose name or business.org matches a verified one “Not verified. Its name looks like Maple Dental, which is verified at mapledental.ca.”
Card claims website, but the host does not list it “mapledental.ca does not list this agent.”
Verified host that looks like another verified host “rnapledental.ca is verified, but looks like mapledental.ca. Check the safety code.”
New address matching a contact’s name or host “This is a different agent from the Maple Dental in your contacts.”

The address is the key, so a key change is a new address. The last case interrupts before the first message or task to it. Warnings appear in Needs you, Discover and the contact page. The agent API returns them as codes such as lookalike_name, and an agent cannot dismiss one.

Copy guidance. Say what was checked and what was not. Name the real verified business when there is one. End with one action, usually “compare the safety code with the business by phone”. Never write “safe”, “trusted”, “fake” or “scam”. Never rely on colour alone. Warn only on a specific match, so warnings stay rare and read.

5. Service task templates (P11.5), protocol effects only

Section titled “5. Service task templates (P11.5), protocol effects only”

Two new task types, appointment.booking and document.request, join dinner.coordination, each with a strict proposal schema in packages/schemas. New disclosure capabilities cover the new fields: disclose.identity (name, date of birth), disclose.contact, disclose.health and disclose.financial (insurer, plan). Like every disclose.*, they start on ask. The dashboard sets health and financial grants to allow only one contact at a time, and warns when that contact has no profession badge. A new simulated action, appointment.book, works like reservation.book: owner approval bound to its exact params. document.request carries only text fields. File transfer needs its own ADR.

A listed, verified business may turn on standing consent, so its node accepts connection requests without a manual Accept. This is owner consent given in advance, like an invite link. It is off by default and only available to listed agents. Rate limits, blocking and per-request disclosure rules still apply, and the requesting owner still consents on their side.

  • DNS TXT records instead of a well-known file. Rejected for now: the Node.js resolver does not validate DNSSEC, and many small businesses can upload a file but not edit DNS.
  • The directory checks websites and nodes trust it. Rejected by D3: every node checks itself.
  • One online verifier key pinned directly. Rejected: a host compromise would need a node release to recover.
  • Year-long attestations with only a revocation list. Rejected: a node that misses the list shows a revoked badge for months.
Threat Defence Residual risk
Agent claims a domain it does not control The host’s file must list the address A compromised website
SSRF through a website claim Shared fetcher: HTTPS, public IPs, pinned, no redirects, caps Small fetch load on a victim host, cached 6 hours
Homograph domain A-labels; UTS #39 display rule; bold registrable domain Owners who ignore the warning
Forged attestation Pinned root, issuing key chain, Ed25519 None while the root is safe
Revoked licence still shown 7-day life, hourly revocation list Up to an hour, or 7 days for a node offline from the verifier
Social engineering of the team Contact taken only from the register or verified site; safety code confirmed A compromised register entry
Unverified look-alike Skeleton rule, warnings, interstitial Names that differ by more than one edit
Operator issues false badges Badges show the regulator and licence number, which anyone can check Same operator runs relay, directory and verifier

Each unit ends with an independent security audit and does not close while a high-severity finding is open. Docs, AGENTS.md and the API reference change with the code.

  1. P11.1 Business profiles. Tests: v1 and v2 accepted; bad, oversized and unknown fields rejected.
  2. P11.2 Verified website. Tests: SSRF suite (private, loopback, rebinding, redirects, oversize, slow body); a file without the address shows no badge; the grace window; IDN, subdomain and public-suffix rules.
  3. P11.3 Verified profession. Tests: forged, expired, wrong-address, revoked-ID and revoked-key attestations show no badge; rotation keeps badges continuous.
  4. P11.4 Impersonation warnings. Tests: the four cases, confusable and one-edit names, and no warning for distinct names.
  5. P11.5 Service templates. Tests: health and financial fields wait on ask; changed params fail approval.

Phase gate: the dental booking demo. Two nodes. “Maple Dental” lists its agent and serves the file on a demo domain, and holds a profession attestation from a test root, never the production root. Alice finds it on Discover, sees both badges, and both owners consent. Her agent proposes an appointment.booking for a cleaning. Health notes wait for Alice’s approval. The clinic accepts, Alice approves appointment.book (simulated), and both tasks end confirmed. An unverified “Maple Dentall” shows the look-alike warning.

  • Owners can tell a business’s real agent from a copy without trusting the directory.
  • Website badges are automatic. Profession badges cost team time, which caps how many are issued.
  • Nodes fetch business websites, bounded by one shared fetcher.
  • The verifier root becomes the most sensitive secret in the project.
  1. Does Verified profession require a verified website first? (a) Yes, it gives the team a contact to confirm and ties the badge to a domain. (b) No, a register call-back alone. Recommended: (a).
  2. Attestation lifetime. (a) 7 days, re-signed daily, plus an hourly revocation list. (b) One year with a revocation list only. (c) 7 days with no list. Recommended: (a).
  3. Verifier key custody. (a) An offline root you hold on two hardware tokens, signing 90-day online issuing keys. (b) One online key pinned in releases. Recommended: (a).
  4. Website re-checks. (a) Every 24 hours, badge kept 72 hours through outages, dropped at once on a definite failure. (b) Every 7 days with no grace. Recommended: (a).
  5. Subdomains. (a) Exact host only. (b) A domain’s file also covers its subdomains. Recommended: (a).
  6. Look-alike warnings. (a) Warn and require one extra confirmation, never block. (b) Block connections to unverified look-alikes. Recommended: (a).
  7. Open to new clients. (a) A listed, verified business may turn on standing consent, so connection requests from anyone are accepted automatically. Rate limits, blocking and per-request disclosure rules still apply. (b) Every request needs a manual Accept. Recommended: (a).

Stelloz's source code is not public yet. An open source GitHub link may come later.