Implement agent discovery in 10 minutes Cheela Labs released version 0.3.1 of the Agent Discovery Specification, an open standard that lets agents advertise their capabilities via a simple JSON manifest served at a well-known path. The spec requires only five fields in the manifest and mandates HTTPS except for local development, and the project provides a minimal Python validation script using jsonschema to check manifests against the normative schema. By the end of this page you will have served a valid ADS manifest, validated it against the normative schema, and made one capability callable from an LLM tool-calling API. Ten minutes, a text editor, and Python — which you almost certainly already have. This is a quickstart for the Agent Discovery Specification https://github.com/Cheela-Labs/agent-discovery-spec , an open, Current spec version: 0.3.0 released as v0.3.1 . If you have seen an ADS example declaring 0.1.0 , it predates two releases. Copy from here instead. Five fields. That is the entire requirement. mkdir -p ads-demo/.well-known && cd ads-demo Create .well-known/agent-discovery.json : { "specVersion": "0.3.0", "id": "com.example.bookshop", "name": "Example Bookshop", "provider": { "name": "Example Inc." }, "capabilities": } An empty capabilities array is valid. "I speak ADS and I currently expose nothing" is a real, useful answer — it is how a client tells the difference between a system that has no capabilities and a system that has never heard of discovery. Serve it: python3 -m http.server 8000 In a second terminal, fetch it the way a client would: curl -i http://localhost:8000/.well-known/agent-discovery.json You should see Content-type: application/json in the response headers. The spec requires it, and Python's dev server gets it right for free. WhyThe spec says a client http:// and not https:// ?MUST NOTfetch a manifest over plaintext HTTP — a manifest names endpoints an agent will subsequently call and the auth it will present, so over a rewritable channel it is a redirection primitive. The single exception is loopback localhost , 127.0.0.0/8 , ::1 during local development, where there is no network attacker. You are inside that exception. Everywhere else, use TLS. You have just performed agent discovery. A GET to a predictable path. That is genuinely the whole mechanism. This is where you find out whether your file is correct instead of hoping. pip install jsonschema requests Save as validate.py : python import json, requests from jsonschema import Draft202012Validator SCHEMA URL = "https://raw.githubusercontent.com/Cheela-Labs/" "agent-discovery-spec/v0.3.1/spec/schema/manifest.schema.json" schema = requests.get SCHEMA URL .json manifest = json.load open ".well-known/agent-discovery.json" errors = sorted Draft202012Validator schema .iter errors manifest , key=lambda e: list e.path if not errors: print "✅ Valid manifest" else: for e in errors: location = " → ".join str p for p in e.path or " root " print f"❌ {location}: {e.message}" python3 validate.py That URL is pinned to a tag on purpose. Pointing a validator at main means the thing you validate against can change under you between two runs; pin the version you are targeting and upgrade deliberately. There is no official ads-validate CLI yet. Thirty lines of jsonschema is the whole tool, which is roughly the point of keeping the schema small. An empty manifest is legal but boring. A capability needs three things: name , version , endpoint . Replace the file: { "specVersion": "0.3.0", "id": "com.example.bookshop", "name": "Example Bookshop", "description": "Search the catalogue and check stock.", "provider": { "name": "Example Inc.", "url": "https://example.com" }, "capabilities": { "name": "com.example.searchBooks", "invocationName": "search books", "version": "1.0.0", "description": "Search the catalogue by title or author.", "inputSchema": { "type": "object", "properties": { "query": { "type": "string" } }, "required": "query" }, "endpoint": { "transport": "http", "address": "https://api.example.com/v1/books/search", "auth": "none" } } , "discovery": { "cacheTtlSeconds": 3600 } } python3 validate.py ✅ Valid manifest . Three fields there are worth understanding, because they are the ones people get wrong. name must contain a dot. It is a reverse-DNS identifier, and the schema ❌ capabilities → 0 → name: 'searchBooks' does not match '^ A-Za-z A-Za-z0-9- {0,63} \. A-Za-z A-Za-z0-9- {0,63} +$' The namespace is what stops your searchBooks and someone else's searchBooks from colliding the moment two manifests are merged into one agent's tool list. invocationName must not contain a dot. This is the newest part of the ^ a-zA-Z0-9 - {1,64}$ . A dot is rejected outright. So a conformant ADS name invocationName is the identifier to use where name cannot be. It is presentation only — name remains the sole identity. If you omit it, a client that needs a constrained identifier must derive one by replacing dots with hyphens , and is now forbidden from truncating to a subset of segments. That endpoint.auth is required , even when it is "none" . Delete the line and ❌ capabilities → 0 → endpoint: 'auth' is a required property Making "no auth" an explicit statement rather than an omission is deliberate — a missing field is indistinguishable from a forgotten one, and an agent should never have to guess whether it needs a credential. Break each of those three on purpose and run the validator. Ninety seconds, and you will remember the rules for good. Then put them back. Here is the payoff, and the reason invocationName exists. This turns a manifest into a tool list an LLM API will actually accept: python import json manifest = json.load open ".well-known/agent-discovery.json" def tool name cap : ADS-2: prefer invocationName; otherwise dots → hyphens. Never truncate. return cap.get "invocationName" or cap "name" .replace ".", "-" tools = { "name": tool name cap , "description": cap.get "description", "" , "input schema": cap.get "inputSchema", {"type": "object"} , } for cap in manifest "capabilities" if cap "endpoint" "transport" == "http" skip what you can't speak and not cap.get "deprecated" print json.dumps tools, indent=2 { "name": "search books", "description": "Search the catalogue by title or author.", "input schema": { "type": "object", "properties": { "query": { "type": "string" } }, "required": "query" } } That array can be passed straight to a tool-calling API. You went from a URL to a usable tool list without knowing anything in advance about the system behind it. Note the transport filter. The rule is skip what you do not understand, never reject the whole manifest . A client that raises on an unrecognised transport You have a valid, current, tool-callable manifest. To put it in production: serve the same document at /.well-known/agent-discovery.json over TLS, send Access-Control-Allow-Origin: if browser clients should see it, and keep specVersion honest when you upgrade. The most useful thing you can do next is disagree with something here. ADS is a 0.x draft. It is small, young, MIT-licensed with no CLA, and governed through a public proposal process modelled on Ethereum's EIPs — which makes it an unusually good first standards contribution. invocationName exists because someone hit the dot problem and wrote it up. Ambiguity in the spec is a spec-bug issue https://github.com/Cheela-Labs/agent-discovery-spec/issues/new/choose . A change to how it works is a proposal https://github.com/Cheela-Labs/agent-discovery-spec/blob/main/proposals/0000-process.md . Both doors are open, and the second one is less intimidating than it sounds. If you serve a manifest anywhere public, open an issue and say so. A spec with one implementer is a design document; the implementations are what make it a standard. Cheela https://cheelalabs.com is the first production implementer of ADS — its runtime registry publishes conformant manifests for every registered runtime. The spec does not depend on it, and nothing in this quickstart used it.