TL;DR
- A registry is a "known-good agents" list that your partners can trust at a glance.
- Don't let anybody list anything—require lightweight owner verification and simple permissions/limits.
- Make trust queriable with a tiny
/verifyendpoint that returns status and core fields in <100ms. - Design the kill switch to stop harm fast without breaking the whole experience.
- This combo is the base layer for an agentic internet that actually works in production.
The moment we stop emailing spreadsheets
In Part 1, I argued that if a bot can act, it needs a passport. A small card that says who it belongs to, what it's allowed to do, where it can operate, and who to call if something goes sideways. This article is about the next step: how others can trust your agent in milliseconds—without a meeting, a spreadsheet, or three weeks of back-and-forth. That's what a registry does.
What is an Agent Registry?
An Agent Registry is a public list of agents you vouch for. It's not fancy. It's just portable truth:
- Owner and contact: "Acme Support Team" + "security@acme.com"
- Role: "Tier-2 Support Bot"
- Permissions and limits: "Refund ≤ $100, Export ≤ 10K rows, No PII"
- Where it can operate: "US-NY, US-CA, EU-DE"
- Status: "Active / Suspended / Revoked"
- Receipts: "Every refund has a signed record."
"Why a registry? Can't anyone just list agents?"
Short answer: No. Identity without accountability is a costume party. Imagine just accepting an ID card like a Driver's license from anyone and not needing ID to be from trusted issuers like States, Countries, etc. With Agent Registries, keep it simple and borrow what already works in finance:
- Know Your Bot (KYB): who owns it and why it exists
- Know Your Creator (KYC): link the agent to a real person or org
- Permissions & limits: make budgets and data access explicit
- Signed receipts: log refunds/exports/deletions with proofs
- Kill switch: suspend in one click—status updates everywhere
The Flow: How Agents Get Verified, Added, and Kept Fresh
Here's the smallest useful workflow. Anyone can ship this in weeks, not months or years.
1) Issue the Passport
Create a human-readable card + a machine-checkable record with: owner, role, permissions, limits, regions, contact, and status. Example:
{
"agent_id": "ap_abc123",
"name": "Acme Support Bot",
"owner": {
"display_name": "Acme Support Team",
"contact": "security@acme.com"
},
"role": "Tier-2 Support",
"capabilities": [
{"id": "payments.refund", "params": {"max_amount": 100}},
{"id": "data.export", "params": {"max_rows": 1000}}
],
"limits": {
"refund_usd_max_per_tx": 100,
"refund_usd_daily_cap": 1000,
"max_export_rows": 1000,
"allow_pii": false
},
"regions": ["US-NY", "US-CA"],
"assurance_level": "L2",
"status": "active"
}
2) Submit to the Registry (Owner-Verified)
The owner (or delegated admin) lists the agent. A lightweight owner check happens once (reuse existing vendor records if you have them). The agent gets a registry ID. API:
POST /api/passports/create
Authorization: Bearer <ADMIN_TOKEN>
{
"name": "Acme Support Bot",
"owner": {
"display_name": "Acme Support Team",
"contact": "security@acme.com"
},
"capabilities": [...],
"limits": {...}
}
Response:
{
"agent_id": "ap_abc123",
"status": "active",
"created_at": "2025-01-16T12:34:56Z"
}
3) Make It Verifiable
Expose a tiny /verify endpoint (or use the shared one) that returns core fields and status in a compact response.
API:
GET /api/verify?agent_id=ap_abc123
Response:
{
"status": "active",
"owner_display": "Acme Support",
"role": "Tier-2",
"capabilities": [
{"id": "payments.refund"},
{"id": "data.export", "params": {"max_rows": 1000}}
],
"limits": {
"refund_usd_max_per_tx": 10000,
"refund_usd_daily_cap": 100000,
"max_export_rows": 1000,
"allow_pii": false
},
"regions": ["US-NY", "US-CA"],
"assurance_level": "L2",
"updated_at": "2025-01-16T12:34:56Z"
}
Agent Verification Endpoint with target latency of <100ms P95 Real numbers from APort:
- P95 latency: 40-50ms (target: <100ms) ✅
- Cache hit rate: 87% (87% of requests are <5ms)
- Throughput: 10,000+ RPS (tested at scale)
- Global propagation: <15 seconds (US, EU, CA regions)
4) Keep It Fresh
Every change to permissions/limits bumps updated_at and emits a webhook so integrators stay in sync. No surprises.
Webhook payload:
{
"event": "passport.updated",
"agent_id": "ap_abc123",
"changes": {
"limits": {
"refund_usd_max_per_tx": {"old": 100, "new": 500}
}
},
"timestamp": "2025-01-16T12:34:56Z"
}
5) Rotate or Revoke
Keys rotate without breaking the agent's identity. If something smells off, flip the status to "suspended" and let policy do the rest. API:
PUT /api/passports/ap_abc123/status
Authorization: Bearer <ADMIN_TOKEN>
{
"agent_id": "ap_abc123",
"owner_id": "ap_org_456",
"status": "suspended",
"reason": "Suspicious refund pattern detected"
}
Response:
{
"success": true,
"data": {
"previous_status": "active",
"new_status": "suspended",
"changed_at": "2025-01-16T12:34:56Z"
}
}
Suspension Without Drama (Designing the Kill Switch)
Suspension should be a soft stop, not a hard crash.
Design Goals
- Immediate stop to risky actions (refunds/exports/payments)
- Graceful degrade: read-only or narrowed permissions while you assess
- Clear messaging: "This agent is suspended. Contact: security@acme.com."
- Fast rollback when fixed
Playbook
- Flip the registry status to suspended via
PUT /api/passports/{agent_id}/status - Downgrade scopes automatically (policy engine does this)
- Notify owners & integrators (webhook + email/Slack)
- Show it wherever the agent appears (About-this-bot page, UI badge)
- Audit the reason with a signed event
PUT /api/passports/ap_abc123/status
Authorization: Bearer <ADMIN_TOKEN>
{
"agent_id": "ap_abc123",
"owner_id": "ap_org_456",
"status": "suspended",
"reason": "Emergency kill switch activated"
}
What happens:
- Status update: <10ms (KV write)
- Region propagation: <15 seconds (Cloudflare edge network)
- Webhook delivery: <5 seconds (async, non-blocking)
- Cache invalidation: Immediate (ETag change triggers refresh)
{
"error": "agent_suspended",
"message": "Agent is suspended and cannot perform operations",
"status": "suspended",
"suspended_at": "2025-01-16T12:34:56Z",
"contact": "security@acme.com"
}
Real numbers from APort:
- Suspension latency: <10ms (KV write)
- Global propagation: <15 seconds (tested across US, EU, CA)
- Webhook delivery: <5 seconds (99% of webhooks)
- Zero false positives: Status checks are atomic
A Quick Story (What This Prevents)
A retailer's support agent issued three $500 refunds in an hour. Finance couldn't tell which agent did it or who owned it. Nobody knew the limit. Everyone guessed. With a registry and passports in place, the refund limit (≤ $100), owner contact, and signed receipts are obvious, and the team suspends the agent in one click while they investigate. No spreadsheets. No finger-pointing. Just control. Numbers from a prospective implementation:
- Verification time: P95 < 100ms globally
- Cache hit rate: 85%+ (L1 edge cache)
- Suspension propagation: <15 seconds global
- API availability: 99.99% uptime
Agent Registries as the Base Layer for an Agentic Internet
Protocols will multiply. Frameworks will argue. Some teams will go decentralized; others will stay web2. That's fine. A neutral registry with portable passports and a dead-simple verify path works across all of it:
- Interoperable: human-readable, machine-checkable
- Portable: agents move between platforms without redoing trust
- Composable: agents can call agents after they verify each other
- Neutral: chain-agnostic today; ready for ZK/FHE integrations tomorrow
┌─────────────────┐
│ Agent Owner │
│ (Acme Corp) │
└────────┬────────┘
│
│ 1. Create Passport
▼
┌─────────────────┐
│ Agent Registry │
│ (APort) │
└────────┬────────┘
│
│ 2. Verify (GET /verify)
▼
┌─────────────────┐
│ Partner API │
│ (Stripe, etc) │
└─────────────────┘
Flow:
- Owner creates passport → Registry stores it
- Partner verifies agent → Registry returns status + limits
- Partner enforces limits → Policy engine blocks unauthorized actions
- Owner suspends agent → Registry propagates status globally
Implementation: The Smallest Useful Registry
Here's what you need to ship:
1. Passport Schema
interface Passport {
agent_id: string;
name: string;
owner: {
display_name: string;
contact: string;
};
role: string;
capabilities: Capability[];
limits: Limits;
regions: string[];
assurance_level: "L0" | "L1" | "L2" | "L3";
status: "draft" | "active" | "suspended" | "revoked";
created_at: string;
updated_at: string;
}
2. Verify Endpoint
// GET /api/verify?agent_id=ap_abc123
export async function verifyAgent(
agentId: string
): Promise<VerificationResponse> {
const passport = await getPassport(agentId);
if (!passport) {
return { error: "agent_not_found" };
}
if (passport.status === "suspended" || passport.status === "revoked") {
return {
error: "agent_suspended",
status: passport.status,
contact: passport.owner.contact,
};
}
return {
status: "active",
owner_display: passport.owner.display_name,
role: passport.role,
capabilities: passport.capabilities,
limits: passport.limits,
regions: passport.regions,
assurance_level: passport.assurance_level,
updated_at: passport.updated_at,
};
}
3. Kill Switch
// PUT /api/passports/{agent_id}/status
export async function suspendAgent(
agentId: string,
ownerId: string,
reason: string
): Promise<StatusUpdateResponse> {
const passport = await getPassport(agentId);
if (!passport) {
return { error: "agent_not_found" };
}
// Update status
await updatePassport(agentId, {
status: "suspended",
suspended_at: new Date().toISOString(),
suspension_reason: reason,
});
// Invalidate cache
await invalidateCache(agentId);
// Emit webhook
await emitWebhook({
event: "agent.suspended",
agent_id: agentId,
reason,
timestamp: new Date().toISOString(),
});
return {
success: true,
data: {
previous_status: "active",
new_status: "suspended",
changed_at: new Date().toISOString(),
},
};
}
4. Caching Strategy
// Tiered caching for <100ms P95
async function getPassport(agentId: string): Promise<Passport | null> {
// L1: In-memory cache (<1ms)
const cached = memoryCache.get(agentId);
if (cached) return cached;
// L2: KV cache (<5ms)
const kvCached = await kv.get(`passport:${agentId}`);
if (kvCached) {
memoryCache.set(agentId, kvCached);
return kvCached;
}
// L3: Database (<50ms)
const passport = await db.getPassport(agentId);
if (passport) {
await kv.put(`passport:${agentId}`, passport, { expirationTtl: 300 });
memoryCache.set(agentId, passport);
return passport;
}
return null;
}
Real-World Examples
Example 1: E-Commerce Refund Agent
Passport:
{
"agent_id": "ap_refund_bot_001",
"name": "Acme Refund Bot",
"owner": {
"display_name": "Acme Support Team",
"contact": "security@acme.com"
},
"role": "Tier-2 Support",
"capabilities": [
{"id": "payments.refund"}
],
"limits": {
"refund_usd_max_per_tx": 100,
"refund_usd_daily_cap": 1000
},
"regions": ["US-NY", "US-CA"],
"assurance_level": "L2",
"status": "active"
}
Verification:
GET /api/verify?agent_id=ap_refund_bot_001
→ 200 OK, <50ms
{
"status": "active",
"limits": {
"refund_usd_max_per_tx": 100,
"refund_usd_daily_cap": 1000
}
}
Kill switch:
PUT /api/passports/{agent_id}/status
{
"agent_id": "ap_refund_bot_001",
"status": "suspended",
"reason": "Suspicious refund pattern detected"
}
→ 200 OK, <10ms
{
"ok": true,
"status": "suspended",
"propagated_at": "2025-01-16T12:34:56Z"
}
Result: Agent suspended globally in <15 seconds. All refund attempts blocked.
Example 2: Data Export Agent
Passport:
{
"agent_id": "ap_export_bot_001",
"name": "Acme Data Export Bot",
"owner": {
"display_name": "Acme Data Team",
"contact": "data@acme.com"
},
"role": "Data Export",
"capabilities": [
{"id": "data.export", "params": {"max_rows": 1000, "allow_pii": false}}
],
"limits": {
"max_export_rows": 1000,
"allow_pii": false
},
"regions": ["US-NY"],
"assurance_level": "L1",
"status": "active"
}
Verification:
GET /api/verify?agent_id=ap_export_bot_001
→ 200 OK, <50ms
{
"status": "active",
"limits": {
"max_export_rows": 1000,
"allow_pii": false
}
}
Kill switch:
PUT /api/passports/{agent_id}/status
{
"agent_id": "ap_export_bot_001",
"status": "suspended",
"reason": "GDPR compliance review"
}
→ 200 OK, <10ms
Result: Agent suspended globally in <15 seconds. All export attempts blocked.
What's Next
- Part 3: Signed Receipts - how to sign refunds & data-export actions, how long to keep them, and how to stream them into your finance/security tools
- Part 4: The Verify API - one page, TS/Python snippets, and an edge-friendly reference
- Part 5: Policy Packs - copy-paste limits for Support Tier-2 and Billing Assistants
Last updated: September 2025 | Version: OAP v1.0