{"slug": "open-source-reference-implementation-of-the-agent-name-service-ans", "title": "Open-source reference implementation of the Agent Name Service (ANS)", "summary": "The Agent Name Service project published `ans`, an open-source reference implementation of a registry and transparency log that assigns AI agents versioned DNS-style names such as `ans://v1.0.0.my-agent.example.com` and backs each registration with an append-only Merkle log, SCITT COSE_Sign1 receipts, and private-CA identity certificates for mTLS between agents. The release ships four binaries — `ans-ra` (Registration Authority, port 18080), `ans-tl` (Transparency Log, port 18081), `ans-verify` (offline CLI verifier) and `ans-dns` (dev-only authoritative DNS server, port 15353) — and requires Go 1.26+, openssl, curl and jq to build via `make build`. The project states the wire shape (event envelope, SCITT COSE receipts, sumdb-note checkpoints and `/root-keys` format) is a public contract that offline verifiers and TL clients depend on byte-for-byte.", "body_md": "`ans` is an open-source implementation of the Agent Name Service\n(ANS), a registry + transparency log for discovering and verifying\nAI agents by name. Every registered agent gets:\n\n- A versioned DNS-style name: `ans://v1.0.0.my-agent.example.com`\n- A publicly-auditable event history (append-only Merkle log)\n- SCITT COSE_Sign1 receipts proving the agent's state at a point in time\n- Identity certificates signed by a private CA for mTLS between agents\n- Optional BYOC server certificates + pinned TLSA records\n\nThe wire shape — event envelope, SCITT COSE receipts, sumdb-note\ncheckpoints, `/root-keys` format — is the public contract. Offline\nverifiers and TL clients depend on it byte-for-byte.\n\n| Binary | Port | Role | \n|---|---|---|\n| `ans-ra` | 18080 | Registration Authority — accepts registrations, issues identity certs, tracks lifecycle | \n| `ans-tl` | 18081 | Transparency Log — durable Merkle-tree append log, serves badges + receipts | \n| `ans-verify` | — | Offline CLI verifier for SCITT COSE receipts and status tokens | \n| `ans-dns` | 15353 | Dev-only authoritative DNS server with `install` /`clear` subcommands for local end-to-end verify-dns testing | \n\nBoth daemons (`ans-ra`, `ans-tl`) serve Swagger UI at `/docs` —\nbrowse [http://localhost:18080/docs](http://localhost:18080/docs) (RA) and\n[http://localhost:18081/docs](http://localhost:18081/docs) (TL) after `make build && scripts/demo/start.sh`. The raw OpenAPI bytes live at\n`/docs/openapi.yaml` on each host.\n\nTo call protected endpoints from Swagger UI's \"Try it out\" button:\nclick **Authorize** in the top-right and paste the static API key.\nThe quickstart defaults are:\n\n- RA: `ans-dev-key-change-me` (from`config/ra-local.yaml` )\n- TL: `tl-internal-key` (from`config/tl-local.yaml` )\n\nThe RA writes signed events; the TL verifies, ingests, and publishes them. An operator can run just the TL (for read-only verification) or both together (full registry).\n\nFor a native Ubuntu host with systemd, Caddy, OIDC, and Let’s Encrypt, see\nthe [RA/TL deployment guide](https://github.com/agentnameservice/ans/blob/main/deploy/ubuntu/README.md).\n\n```\n# Prereqs: Go 1.26+, openssl, curl, jq\ngit clone https://github.com/agentnameservice/ans\ncd ans\nmake build                        # builds bin/ans-ra, bin/ans-tl, bin/ans-verify, bin/ans-dns\nscripts/demo/start.sh             # starts both daemons against ./data/demo\nscripts/demo/run-lifecycle.sh     # registers an agent, verifies the receipt end-to-end\n```\n\nExpected output (last few lines):\n\n```\n━━━ 13. Offline verify (bin/ans-verify) ━━━\n  ✓ Fetched 2 verification key(s) from /root-keys\n  ✓ 1602 bytes (Content-Type: application/scitt-receipt+cose)\n  ✓ VERIFIED (kid <8-char-hex> matched key directly)\n```\n\nTear down with `scripts/demo/stop.sh` or `scripts/demo/stop.sh --clean`\nto wipe the data directory.\n\nThe default DNS verifier in `config/ra-local.yaml` is `noop`, which\naccepts any DNS state. To exercise the real `lookup` verifier\nend-to-end without touching public DNS, run the bundled\nauthoritative server:\n\n```\n# In one terminal: start the dev DNS server (serves ./data/demo/dns)\nbin/ans-dns serve --addr 127.0.0.1:15353\n\n# In another: install the records the RA expects after verify-acme\nbin/ans-dns install --api-key <api-key> http://localhost:18080 <agentId>\n\n# Switch ra-local.yaml to dns.type=lookup pointed at 127.0.0.1:15353\n# and re-run verify-dns; ans-tl observes the AGENT_REGISTERED event.\n\n# Tear down:\nbin/ans-dns clear <agentId>\n```\n\n`ans-dns` is dev-only. Production deployments use the `lookup`\nverifier against the operator's existing authoritative DNS.\n\n```\n# Build images for the long-running daemons\nmake docker-build\n\n# Or bring the stack up with compose (RA + TL, with healthchecks)\ndocker compose up --build\n```\n\nCompose persists `ra-data`, `ra-keys`, and `tl-data` volumes so\nrestarts are cheap; `docker compose down -v` wipes them. `ans-verify`\nand `ans-dns` are local-development binaries and ship from `make build` only — they are not in the compose stack.\n\nHexagonal / ports-and-adapters. The domain model (`internal/domain`)\nhas zero framework or storage dependencies. Services depend on port\ninterfaces (`internal/port`) which adapters in `internal/adapter/`\nimplement against real drivers.\n\n```\ncmd/ans-ra/main.go ───────┐\n                          ├─► internal/ra/service ─► port.AgentStore     ─► internal/adapter/store/sqlite\ncmd/ans-tl/main.go ───────┤                          port.KeyManager     ─► internal/adapter/keymanager\n                          │                          port.DNSVerifier    ─► internal/adapter/dns\n                          └─► internal/tl/service ─► port.RenewalStore   ─► …\n\ncmd/ans-verify/main.go ─► internal/tl/receipt   (Verify, ExtractKID, ComputeLeafHash)\ncmd/ans-dns/main.go    ─► miekg/dns authoritative server + RA-driven record install/clear\n```\n\nSee `spec/api-spec-v2.yaml` + `spec/api-spec-tl-v2.yaml` for the\nauthoritative OpenAPI contracts.\n\n**Event envelope (signed on the RA side):**\n\n```\n{\n  \"payload\": {\n    \"logId\": \"<uuid>\",\n    \"producer\": {\n      \"event\": { \"ansId\": \"...\", \"ansName\": \"ans://...\", \"eventType\": \"AGENT_REGISTRATION\", ... },\n      \"keyId\": \"ans-ra-signer\",\n      \"signature\": \"<detached JWS over JCS(event)>\"\n    }\n  },\n  \"schemaVersion\": \"V1\",\n  \"signature\": \"<TL attestation JWS>\",\n  \"status\": \"ACTIVE\"\n}\n```\n\n**Receipts (SCITT COSE_Sign1, RFC 8152 tag 18):**\n\n- Protected header: `{1:-7 (ES256), 4:<kid 4-byte SPKI hash>, 395:1 (RFC 9162), 15:{1:<issuer>, 6:<iat>}}`\n- Unprotected header: `{396:{-1:treeSize, -2:leafIndex, -3:path[][], -4:rootHash}}`\n- Payload: the JCS-canonical event bytes (the *same bytes* the leaf hash is computed over)\n- Signature: ES256 over the RFC 8152 §4.4 Sig_structure\n\n**Leaf hash:** RFC 6962 — `SHA-256(0x00 || payload)`.\n\n**Root-keys endpoint** (`GET /root-keys`) emits the\nsumdb-note verification-key format (one line per key):\n\n```\n<origin>+<keyhash-hex>+<base64(0x02 || SPKI-DER)>\n```\n\nwhere `keyhash-hex` is the first 4 bytes of `SHA-256(SPKI-DER)` as\nzero-padded big-endian hex. Verifiers match this against the `kid`\nin the receipt's protected header for O(1) key lookup.\n\nThe TL signs every outbound artifact (checkpoints, attestations,\nreceipts, status tokens) with a single ECDSA P-256 key. `/root-keys`\nadvertises exactly one line.\n\nAny third party can verify an agent's state cryptographically without trusting the TL operator beyond the advertised verifier keys:\n\n```\n# Fetch receipt + keys + verify\nans-verify -url https://tl.example.com -agent <agentId>\n\n# Or, air-gapped (receipt already on disk, key pinned in CI):\nans-verify -pubkey ./trusted-tl.pub -agent <agentId> \\\n  -url file://./receipt.cbor\n```\n\nThe verifier:\n\n1. Fetches `/root-keys` (or reads a pinned PEM).\n2. Fetches the receipt CBOR.\n3. Parses COSE_Sign1, extracts the inclusion proof from label 396.\n4. Maps the receipt's 4-byte `kid` → verifier key in O(1).\n5. Walks the Merkle path from `SHA-256(0x00 || payload)` to`rootHash` ; rejects any mismatch.\n6. ES256-verifies the signature over Sig_structure.\n7. Cross-checks that the receipt's computed leaf hash matches the\nbadge's `merkleProof.leafHash` (same leaf in the same tree).\n8. For every metadata hash the leaf attests, fetches the descriptor at\nthe URL the attested SVCB row carries (`key65400` ) and compares its\nSHA-256 with the attested value. A mismatch fails verification; an\nunreachable descriptor is reported only.`-check-metadata=false` skips this step.\n\nBoth daemons read YAML via [koanf](https://github.com/knadh/koanf).\nThe shipped quickstart configs are under `config/`:\n\n- `config/ra-local.yaml` — local-filesystem dev config (SQLite, self-CA, noop DNS)\n- `config/tl-local.yaml` — same for the TL\n- `config/ra-docker.yaml` /`config/tl-docker.yaml` — compose-friendly paths + log format\n\nEvery YAML key is overridable via env vars using the `_` → `.` dotted\nform (e.g. `ANS_RA_SERVER__PORT=18090`).\n\nTwo providers ship today:\n\n- **Static API key** (quickstart): accepts both`Authorization: Bearer <key>` (ans-native form) and`Authorization: sso-key <apiKey>:<apiSecret>` (the form generated SDK clients use). The\nstatic key is treated as admin, so it can hit`/internal/v1/producer-keys` .\n- **OIDC** : standard bearer-token validation against an issuer URL.\nConfigure admin groups with`auth.oidc.admin-groups[]` .\n\nEvery GET under `/v1/agents/`, `/v1/log/`, and the tlog-tiles paths\n(`/checkpoint`, `/root-keys`, `/tile/*`) is anonymous when\n`auth.public-read: true` (the default for the TL quickstart config) —\nverifiers never need credentials.\n\nEvery external dependency lives behind a port interface in\n`internal/port/`. Defaults target local development; you can swap any\nof them at the port boundary without touching the service layer.\n\n| Concern | Default adapter | Common production swap | \n|---|---|---|\n| Auth | Static API key + OIDC | OIDC against your IdP | \n| Identity cert issuance | File-backed self-signed CA | Cloud KMS-backed CA | \n| Server cert issuance | Self-signed CA + BYOC PEM | ACME / public CA / BYOC PEM | \n| DNS verification | `noop` and`lookup` (real miekg/dns queries with DNSSEC AD-bit propagation) | `lookup` against your authoritative DNS | \n| Signing keys | File-based ECDSA P-256 | Cloud KMS adapter | \n| Storage | SQLite | Postgres / managed RDBMS | \n\nCloud-KMS, Postgres, and managed-DNS adapters are\ncontribution-shaped — see `CONTRIBUTING.md`.\n\n```\nmake test         # unit tests\nmake test-cover   # with coverage, enforces internal ≥90%, domain 100%, crypto ≥95%\nmake test-race    # race detector\nmake check        # fmt + vet + lint + test-cover (pre-commit gate)\n```\n\nTest coverage targets: 100% on `internal/domain` and `internal/crypto`\n(with annotated SAFETY exceptions for unreachable defensive\nbranches), ≥90% overall across `internal/` (enforced by `make test-cover`). Adapter packages under `internal/adapter/` are the\nprimary remaining gap — contributions welcome, see\n`CONTRIBUTING.md`.\n\nContributing guide: [`CONTRIBUTING.md`](https://github.com/agentnameservice/ans/blob/main/CONTRIBUTING.md).\nProject conventions for code generation tools: [`CLAUDE.md`](https://github.com/agentnameservice/ans/blob/main/CLAUDE.md).\n\nShipped: full V1 + V2 RA surface (register, verify-acme, verify-dns,\nrevoke, list, detail, identity/server cert GET + POST, CSR status,\nrenewal lifecycle with BYOC + CSR paths, renewal expiry sweep) • TL\ningest, badge, audit, SCITT COSE receipts (tag 18, ES256), SCITT\nstatus tokens (1h TTL, terminal-state aware), root-keys (sumdb-note\nformat), JSON + raw checkpoint, paginated checkpoint history, schema\nlookup, Merkle tiles • Producer-key admin CRUD\n(`/internal/v1/producer-keys`) • `ans-verify` CLI that fetches +\nverifies both receipts and status tokens • `ans-dns` dev DNS server\nwith `install` / `clear` for verify-dns end-to-end testing.\n\nFollow-up opportunities (community): cloud-KMS / Postgres / managed-DNS adapters, real ACME DNS-01 verifier wiring, additional schema versions.\n\nMIT License. See [LICENSE](https://github.com/agentnameservice/ans/blob/main/LICENSE).", "url": "https://wpnews.pro/news/open-source-reference-implementation-of-the-agent-name-service-ans", "canonical_source": "https://github.com/agentnameservice/ans", "published_at": "2026-11-04 22:54:54+00:00", "updated_at": "2026-11-04 23:20:37.119309+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-safety", "developer-tools", "artificial-intelligence"], "entities": ["Agent Name Service", "ans", "ans-ra", "ans-tl", "ans-verify", "ans-dns", "SCITT", "Go"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/open-source-reference-implementation-of-the-agent-name-service-ans", "markdown": "https://wpnews.pro/news/open-source-reference-implementation-of-the-agent-name-service-ans.md", "text": "https://wpnews.pro/news/open-source-reference-implementation-of-the-agent-name-service-ans.txt", "jsonld": "https://wpnews.pro/news/open-source-reference-implementation-of-the-agent-name-service-ans.jsonld"}}