Interactive Course

How AI Agents Find Each Other on the Internet

An inside look at DNS-AID — the open-source system that lets AI agents discover, trust, and talk to each other using the internet's oldest naming system: DNS.

7 modules · ~30 min · No coding experience needed

↓ Scroll to start
01

What DNS-AID Actually Does

Before we look under the hood, let's understand the problem it solves.

The Problem: AI Agents Can't Find Each Other

Imagine you've built an AI agent that's great at writing code. Your colleague has one that's great at reviewing code. Wouldn't it be great if they could work together?

Right now, connecting them means hardcoding URLs, sharing API keys over Slack, and hoping nothing changes. It's like having to memorize everyone's phone number instead of looking them up in a directory.

💡
The "Phone Book" Insight

The internet already has a universal directory: DNS. When you visit google.com, DNS translates that name into an address your computer can reach. DNS-AID adds AI agents to this same directory.

The Solution: A Contact Card in DNS

DNS-AID publishes a rich "contact card" for each AI agent into DNS. Here's what it looks like:

_chat._mcp._agents.example.com The agent's name in the directory — "chat agent, speaks MCP, at example.com"
target: mcp.example.com Where to find the agent (its address)
alpn="mcp" Which "language" the agent speaks
port=443 Which "door" to knock on
key65400=https://…/cap.json Link to a detailed profile of what the agent can do

This uses a DNS record type called SVCB — a modern record type designed to carry rich connection information in a single lookup.

Three Things You Can Do

📣

Publish

Register your agent in DNS so others can find it — like listing your business in the Yellow Pages.

🔍

Discover

Find agents at any domain — one DNS query returns everything you need to connect.

Verify

Check that an agent is who it claims to be — inspecting DNSSEC, TLS, and DANE security proofs.

See It in Code

Here's the entire workflow in Python — six lines of code:

CODE
import dns_aid

# Publish your agent to DNS
await dns_aid.publish(
    name="my-agent",
    domain="example.com",
    protocol="mcp",
    endpoint="agent.example.com"
)

# Discover agents at a domain
agents = await dns_aid.discover("example.com")

# Verify trust
result = await dns_aid.verify(agents[0].fqdn)
PLAIN ENGLISH

Load the DNS-AID library so we can use it.

 

Register an agent named "my-agent" in DNS. It lives at agent.example.com and speaks the MCP protocol. Anyone querying example.com will now find it.

 

 

 

 

 

 

Ask DNS: "Who are the agents at example.com?" Returns a list of every agent registered there.

 

For the first agent found, run a full security check: Is the DNS signed? Is the TLS valid? Does everything check out? Returns a score from 0 to 100.

Check Your Understanding

You're building a new AI agent and want other agents to find it. What's the first thing you do with DNS-AID?

Why does DNS-AID use DNS instead of a centralized agent registry?

02

Meet the Cast of Characters

DNS-AID is like a film crew — each module has a specific role. Let's meet them.

The Main Characters

Think of DNS-AID as a talent agency for AI agents. Different departments handle different jobs:

📣
The Publisher

The talent agent who creates your listing — turns your agent's details into DNS records

🔍
The Discoverer

The scout who finds agents by querying DNS and reading their contact cards

🔐
The Validator

The security guard who checks IDs — verifies DNSSEC, TLS, and DANE before trusting anyone

📩
The AgentClient (SDK)

The messenger who actually delivers requests — handles auth, policy checks, and protocol details

⚙️
DNS Backends

The postal workers who write records to specific DNS providers (AWS, Cloudflare, BIND9, etc.)

How the Code Is Organized

src/dns_aid/ Main package — everything lives here
__init__.py The "front desk" — exports publish, discover, verify, invoke
core/ Tier 0: DNS operations (no HTTP calls to agents)
publisher.py Creates SVCB + TXT records
discoverer.py Queries DNS for agents
validator.py DNSSEC, DANE, TLS checks → security score
models.py Data structures (AgentRecord, Protocol, VerifyResult)
indexer.py Maintains the _index._agents.{domain} TXT record
backends/ DNS provider adapters (one file per cloud)
base.py Abstract interface all backends must follow
route53.py AWS Route 53
cloudflare.py Cloudflare DNS
ddns.py RFC 2136 Dynamic DNS (BIND9, PowerDNS)
sdk/ Tier 1: Agent invocation, auth, policy, telemetry
client.py AgentClient — the main "messenger"
auth/ Auth handlers (bearer, API key, OAuth2, SigV4)
policy/ Policy enforcement engine (16 rule types + CEL)
protocols/ MCP, A2A, HTTPS protocol handlers
mcp/ MCP Server — lets AI agents use DNS-AID directly
cli/ Command-line interface (dns-aid publish, discover, verify)

The AgentRecord: The Star of the Show

Every agent in the system is represented by an AgentRecord — the data structure that holds all the information from its DNS contact card.

CODE
class AgentRecord(BaseModel):
    name: str
    domain: str
    protocol: Protocol
    target_host: str
    port: int = 443
    capabilities: list[str]
    cap_uri: str | None
    policy_uri: str | None
PLAIN ENGLISH

This is the "blueprint" for an agent's identity card.

name — the agent's unique name, like "chat" or "code-reviewer"

domain — the organization it belongs to, like "example.com"

protocol — which "language" it speaks (MCP, A2A, or HTTPS)

target_host — the actual server address where it lives

port — which "door number" to knock on (usually 443, the standard HTTPS door)

capabilities — what it can do, like ["chat", "code-review"]

cap_uri — link to a detailed capability profile document

policy_uri — link to the rules about who's allowed to call this agent

The DNS Name Formula

Every agent gets a structured DNS name built from three pieces:

_

_{name}
The agent's name

.
_

_{protocol}
How it communicates

.
_

_agents.{domain}
The "agents" namespace

_chat._mcp._agents.example.com

💡
Namespacing

The _agents label acts as a namespace — it keeps agent records separate from regular website records. No collisions, no confusion.

Check Your Understanding

You want to tell your AI coding assistant: "Put the authentication logic in the right place." Based on what you've learned, where does auth logic live in DNS-AID?

An agent's DNS name is _billing._a2a._agents.acme.com. What can you tell about this agent?

03

How the Pieces Talk to Each Other

Trace what actually happens when you publish an agent and when another agent discovers it.

Publishing: From Python to DNS Records

When you call dns_aid.publish(), here's the journey your data takes — like dropping a letter into a mailbox and watching it get sorted, stamped, and delivered.

1
You call publish() with your agent's details

Name, domain, protocol, endpoint — the raw ingredients

2
Publisher creates an AgentRecord

Your details get organized into a structured object

3
Backend factory picks the right DNS provider

Reads DNS_AID_BACKEND env var → loads Route53, Cloudflare, DDNS, etc.

4
Agent details split into standard + custom params

Standard SVCB params (alpn, port) go to SVCB record. Custom DNS-AID params get "demoted" to TXT records because most providers reject them

5
Backend writes records to your DNS zone

SVCB record + TXT record created at _name._protocol._agents.domain

The Backend Factory: A Clever Pattern

DNS-AID supports 7 different DNS providers. Instead of a giant if/else chain, it uses a factory pattern:

CODE
_BACKEND_CLASSES = {
    "route53":    ("dns_aid.backends.route53",    "Route53Backend"),
    "cloudflare": ("dns_aid.backends.cloudflare", "CloudflareBackend"),
    "ddns":       ("dns_aid.backends.ddns",       "DDNSBackend"),
    "mock":       ("dns_aid.backends.mock",       "MockBackend"),
}

def create_backend(name):
    module_path, class_name = _BACKEND_CLASSES[name]
    module = importlib.import_module(module_path)
    return getattr(module, class_name)()
PLAIN ENGLISH

A lookup table mapping backend names to their code locations. Think of it as a directory: "route53" → go to this file, use this class.

 

 

 

 

 

Given a name like "route53", look up where its code lives, load it, and create a ready-to-use backend object. The lazy import means boto3 is only loaded if you actually use Route53.

Discovery: From DNS Query to Agent List

Now the reverse — how an agent finds other agents. Like a traveler arriving in a new city and looking up the local directory.

💬 Discovery Flow — what happens inside dns_aid.discover("example.com")
0 / 7 messages

Invocation: One Agent Calls Another

After discovery, the AgentClient handles the actual call. Here's the pipeline:

🔐
Auth
📜
Policy
📩
Protocol
📈
Telemetry
Click "Next Step" to trace the invocation pipeline
Step 0 / 5

Check Your Understanding

Your agent discovers another agent but the call fails with "PolicyViolationError." Where in the pipeline did it stop?

04

DNS Backends: The Postal Workers

DNS-AID speaks to 7 different DNS providers through one uniform interface — like an international adapter for power outlets.

One Interface, Many Providers

Every backend must implement the same set of operations. This is the abstract base class:

📝

create_svcb_record()

Write the agent's SVCB "contact card" into the DNS zone

📄

create_txt_record()

Write supplementary text records (capabilities, custom params)

🗑️

delete_record()

Remove an agent's records when it's unpublished

zone_exists()

Check that the DNS zone exists before trying to write to it

The Custom Parameter Problem

DNS-AID adds custom fields to SVCB records (like key65400 for capability URIs). But here's the catch:

⚠️
Most DNS providers reject custom SVCB keys

Route53, Cloudflare, and Cloud DNS only accept standard SvcParamKeys. DNS-AID uses private-use keys (65400–65408) that these providers will reject with an error.

The solution? Parameter demotion. The base class automatically splits params:

1

Standard params (alpn, port) → SVCB record

2

Custom params (cap, policy, realm) → TXT record

The Discoverer knows to check both SVCB and TXT when reading an agent's details. Everything round-trips cleanly.

The DDNS Backend: Talk to BIND9 Directly

For local development, DNS-AID can talk to a BIND9 server using RFC 2136 dynamic updates:

CODE
class DDNSBackend(DNSBackend):
    def __init__(self, server, key_name,
                 key_secret, key_algorithm):
        self._keyring = dns.tsigkeyring.from_text(
            {self.key_name: self.key_secret}
        )
PLAIN ENGLISH

This backend talks directly to a DNS server (like BIND9).

When created, it needs the server address plus a secret key — like a signed letter that proves you have authority to make changes.

The TSIG keyring holds the shared secret for authenticating update messages.

Check Your Understanding

You're deploying DNS-AID and your ops team uses Cloudflare. You try to publish an agent with a policy_uri and get an error about invalid SVCB parameters. What's happening?

05

Security, Auth & Policy

How DNS-AID ensures agents are who they say they are — and enforces rules about who can call whom.

Three Layers of Trust

DNS-AID uses a layered security model — like airport security with multiple checkpoints, each catching different threats:

1
DNS Trust (Validator)

DNSSEC validates the DNS response wasn't tampered with. DANE pins the TLS certificate. Security score: 0–100.

2
Auth (Registry)

Pluggable auth handlers: Bearer tokens, API keys, OAuth2 client credentials, AWS SigV4, HTTP Message Signatures. Each handler knows how to add the right credentials to a request.

3
Policy (Evaluator)

16 built-in rule types + custom CEL expressions. Checked at three enforcement points: caller-side, target-side, and DNS-level.

The Auth Registry: Plug-and-Play Security

Auth handlers are registered in a dictionary — like a toolkit where each tool handles a different type of lock:

CODE
_REGISTRY = {
    "none":         _build_noop,
    "api_key":      _build_api_key,
    "bearer":       _build_bearer,
    "oauth2":       _build_oauth2,
    "http_msg_sig": _build_http_msg_sig,
    "sigv4":        _build_sigv4,
}

def resolve_auth_handler(auth_type,
                        auth_config,
                        credentials):
    factory = _REGISTRY[auth_type]
    return factory(auth_config, credentials)
PLAIN ENGLISH

A registry of auth handler factories, keyed by type name.

"none" — no auth needed (public agents)

"api_key" — adds an X-API-Key header

"bearer" — adds Authorization: Bearer {token}

"oauth2" — full OAuth2 client_credentials flow

"http_msg_sig" — RFC 9421 HTTP Message Signatures

"sigv4" — AWS Signature V4 (for VPC Lattice)

 

Given an auth type string, look up the right factory function and call it with the config + credentials to get a ready-to-use auth handler.

The Security Score

The Validator computes a 0–100 security score by checking five things:

CODE
@property
def security_score(self) -> int:
    score = 0
    if self.record_exists:     score += 20
    if self.svcb_valid:        score += 20
    if self.dnssec_valid:      score += 30
    if self.dane_valid:        score += 15
    if self.endpoint_reachable: score += 15
    return score
PLAIN ENGLISH

This calculates the security score like a report card:

 

Start at zero.

+20 points if the DNS record exists at all (basic presence)

+20 points if the SVCB record is properly formatted

+30 points if DNSSEC proves the record hasn't been tampered with (biggest weight — this is the hardest to fake)

+15 points if DANE pins the TLS certificate correctly

+15 points if the agent's server actually responds

Return the total. A score of 100 means bulletproof.

Policy Rules: 16 Built-In Types

Policy documents are JSON files served at the agent's policy_uri. They can enforce rules like:

🔒

required_auth_types

"Only accept calls from agents using OAuth2 or bearer tokens"

🌎

allowed_caller_domains

"Only agents from *.trusted.com can call me"

🕒

availability

"I'm only available 9am–5pm UTC on weekdays"

🚦

rate_limits

"Max 100 requests per minute per caller"

Check Your Understanding

You're building an agent that handles sensitive financial data. You want to ensure only verified callers from your organization can reach it. Which combination of security features would you configure?

06

When Things Break

Build your debugging intuition — common failure modes and where to look when something goes wrong.

The Debugging Map

When an agent call fails, the error usually lives in one of these layers. Think of it like a medical triage — the symptoms tell you which system to examine:

🚨
"Agent not found" / Empty discovery results

The agent isn't published or the index is stale. Run dns-aid index sync to rebuild the index from ground truth, then try discovery again.

⚠️
"DNS_AID_BACKEND must be set"

No backend configured. DNS-AID has no silent fallback to mock — this is intentional to prevent accidentally testing against fake data in production. Set the env var.

🔐
PolicyViolationError

The target agent's policy rejected your call. Check: are you calling from an allowed domain? Using the required auth type? Within the availability window?

🔒
401/403 from the target

Auth failed. The auth_type from discovery might not match your credentials. Check the agent's .well-known/agent-card.json for the expected auth type.

🔄
Low security score

Run dns-aid verify {fqdn} to see which checks failed. Most common: DNSSEC not enabled (costs you 30 points) or no DANE/TLSA record (15 points).

The Doctor Is In

DNS-AID includes a built-in diagnostic tool. Like a doctor's checkup, it examines your environment and reports what's healthy and what needs attention:

dns-aid doctor Checks env vars, backend connectivity, Python version, dependencies
dns-aid doctor --domain example.com Also tests DNS resolution and agent discovery for that domain
dns-aid verify _chat._mcp._agents.example.com Deep security audit: DNSSEC chain, TLS version, DANE cert pinning
💡
Fail-Open vs. Fail-Closed

DNS-AID's policy engine fails open on fetch errors — if it can't reach the policy document, it allows the call. This is a deliberate design choice: a broken policy server shouldn't take down all agent communication. But in strict mode, it fails closed.

Check Your Understanding

Your team deployed a new agent yesterday but other agents can't discover it. dns-aid discover returns nothing. The SVCB record exists when you check manually. What's the most likely issue?

07

The Big Picture

Zoom out and see how all the pieces fit together — and where DNS-AID fits in the AI ecosystem.

The Two-Tier Architecture

DNS-AID is split into two distinct tiers — like a library with a front desk and a delivery service:

Tier 0 — DNS Layer

📣 Publisher
🔍 Discoverer
🔐 Validator
📚 Indexer

Tier 1 — Execution SDK

📩 AgentClient
🔐 Auth Registry
📜 Policy Engine
📈 Signal Collector
Click any component to see what it does

Agent Ranking: Who's the Best?

When you discover multiple agents that can do the same thing, the Signal Collector helps you pick the best one. The ranking formula:

40%
Reliability
Did it succeed?

30%
Latency
How fast?

15%
Cost
How expensive?

15%
Freshness
How recent?

Three Interfaces, One System

DNS-AID can be used three ways — same engine, different steering wheels:

💻
CLI

dns-aid publish / discover / verify / message — for humans in a terminal

🤖
MCP Server

Exposes all operations as MCP tools that AI agents can call directly

🐍
Python Library

import dns_aid — the async API for embedding in your own code

The Internet Standard

DNS-AID isn't just a library — it's the reference implementation for IETF draft-mozleywilliams-dnsop-dnsaid-01, a proposed internet standard. The naming convention:

🌐
Why This Matters

If this becomes an IETF standard, every DNS provider will natively support these records. Any AI agent — regardless of vendor, framework, or protocol — will be discoverable through the same universal directory: DNS.

Final Check

You're planning to build a multi-agent system where agents from three different companies need to discover and call each other. Why would DNS-AID be a good fit versus a centralized agent registry?

You want to add a new DNS backend for a provider called "MegaDNS." What's the minimum you need to implement?

You Did It!

You now understand how DNS-AID works — from the DNS naming convention to the backend factory pattern to the three-layer security model.

Built from the dns-aid-core codebase.