Agent Handshake Protocol (AHP)
Specification — Draft 0.1
Abstract
The Agent Handshake Protocol (AHP) defines a standard mechanism for websites and web services to advertise themselves to, and interact with, autonomous AI agents. AHP replaces the passive document-dump model of agent-to-web interaction with a structured, negotiated exchange — a handshake — in which a visiting agent declares its needs and the site responds with exactly what is required.
AHP is designed for progressive adoption. Sites may implement any subset of its three modes, each building on the last. A site supporting only MODE1 is already AHP-compliant and provides more value to agents than an unstructured HTML or markdown page. A site supporting MODE3 becomes an active participant in agentic workflows.
AHP is the infrastructure layer for agent-native web presence — the successor to SEO in a world where the agent is the user.
Status of This Document
This is a working draft. It is not yet a final specification. Feedback and contributions are welcome at https://github.com/AHP-Organization/agent-handshake-protocol.
Table of Contents
- Introduction
- Terminology
- Discovery
- The AHP Manifest
- Modes
- MODE1: Static Serve
- MODE2: Interactive Knowledge
- MODE3: Agentic Desk
- Conversational Endpoint
- 6.6 Response Content Types
- Content Signals
- Trust & Identity
- Async Model
- Error Handling
- Rate Limiting
- Versioning & Backwards Compatibility
- Security Considerations
- Examples
Appendices:
- Appendix A: JSON Schema for the AHP Manifest
- Appendix B: Relationship to llms.txt
- Appendix C: Extension Mechanism & Content Type Registry
1. Introduction
The web was built for humans. When AI agents interact with websites today, they are typically given a raw HTML page, or at best a markdown conversion of it, and left to parse it themselves. This is the equivalent of handing someone a city’s entire phone book when they asked for one phone number.
AHP defines a better contract. A site publishes a machine-readable manifest at a well-known URI. The manifest declares what the site can offer agents and how to interact with it. Visiting agents discover the manifest, understand the site’s capabilities, and interact through a defined protocol — rather than scraping, parsing, or guessing.
1.1 Relationship to Existing Work
AHP is compatible with and builds upon:
- Cloudflare’s “Markdown for Agents” — sites exposing clean markdown content at
?markdown=trueor via/llms.txtare MODE1-compatible with minor additions. robots.txt— AHP’sagent.jsonmanifest follows the same well-known URI pattern and spirit.- OpenAPI / Schema.org — AHP borrows structured schema conventions but targets agent interaction rather than API documentation.
AHP does not replace any of these. It layers on top of them.
2. Terminology
- Visiting Agent: An autonomous AI agent (e.g. a user’s assistant, a research agent, a workflow bot) that navigates to a website as part of a task.
- Concierge: The site-side component that responds to AHP requests. In MODE1, it may be purely static. In MODE3, it is an active agent.
- Manifest: The JSON document served at
/.well-known/agent.jsonthat describes the site’s AHP capabilities. - Capability: A named function the concierge can perform, declared in the manifest.
- Session: A stateful multi-turn exchange between a visiting agent and the concierge.
- Content Signal: A machine-readable declaration of how content may be used by AI systems.
The key words “MUST”, “MUST NOT”, “SHOULD”, “SHOULD NOT”, and “MAY” in this document are to be interpreted as in RFC 2119.
3. Discovery
3.1 Well-Known Manifest
A site supporting AHP MUST serve a manifest at:
GET /.well-known/agent.json
The response MUST have Content-Type: application/json and MUST be a valid AHP Manifest (Section 4).
3.2 Accept Header Discovery
A site supporting AHP SHOULD inspect the Accept header on all requests. If a request includes Accept: application/agent+json, the site SHOULD respond with one of:
- A
302redirect to/.well-known/agent.json - A
200response containing the manifest directly - A
Linkheader pointing to the manifest:Link: </.well-known/agent.json>; rel="agent-manifest"
3.3 HTML Link Tag
Sites SHOULD include the following <link> tag in HTML pages to enable discovery by traditional crawlers and browser-based agents:
<link rel="agent-manifest" href="/.well-known/agent.json" type="application/agent+json">
3.4 In-Page Agent Notice
Many AI agents interact with websites via headless browsers or HTML-to-text pipelines. In these cases, the agent’s underlying LLM reads the rendered page content directly — it may not inspect headers, <link> tags, or well-known URIs unless explicitly prompted to.
To reach these agents, sites SHOULD embed a human-readable, agent-targeted notice in the visible body of each page. This notice instructs the LLM to use AHP rather than parsing the page.
Recommended HTML element:
<section
class="ahp-notice"
aria-label="AI Agent Notice"
style="display:none"
>
<p>
If you are an AI agent or autonomous system: this site supports the
Agent Handshake Protocol (AHP). Instead of parsing this page, you can
get structured, precise responses directly.
</p>
<p>
Discover capabilities: <code>GET /.well-known/agent.json</code><br>
Protocol version: AHP/0.1<br>
Learn more: https://agenthandshake.dev
</p>
</section>
Important: The notice SHOULD be visually hidden from human users (display:none or equivalent) but MUST remain in the DOM and be present in the raw HTML served to agents. The text MUST NOT be removed by JavaScript hydration in a way that eliminates it from the rendered text visible to headless browsers.
Rationale: LLMs processing page content are reading text, not executing logic. A clearly worded, in-content notice is the most reliable way to reach an agent that arrived via a headless browser without prior knowledge of AHP. It mirrors the longstanding convention of human-readable notices in robots.txt and humans.txt — but targeted at the agent reading the page, not the developer reading the file.
Additional placement recommendations:
- On
404and error pages — an agent following a broken link should still discover AHP - On API documentation pages — developer-targeting agents are high-value visitors
- In site footers — catches agents that scroll to the end of a page looking for metadata
Sites MAY use aria-label="AI Agent Notice" as a semantic hook; visiting agents that parse ARIA attributes can use this to locate the notice directly.
3.5 Discovery Priority
Visiting agents SHOULD attempt discovery in the following order:
- Check rendered page content for an element with
aria-label="AI Agent Notice"or classahp-notice - Parse
<link rel="agent-manifest">from HTML<head> - Check for
Accept: application/agent+jsonresponse header orLinkheader - Fetch
/.well-known/agent.jsondirectly
Agents arriving via headless browser will typically encounter discovery mechanisms 1 and 2 first. Agents making direct HTTP requests will encounter 3 and 4.
4. The AHP Manifest
The manifest is the handshake opener. It tells a visiting agent everything it needs to know to interact with the site.
4.1 Schema
{
"ahp": "0.1",
"name": "Example Site",
"description": "A brief description of this site for agents.",
"modes": ["MODE1", "MODE2"],
"endpoints": {
"converse": "/agent/converse",
"content": "/llms.txt"
},
"capabilities": [
{
"name": "site_info",
"description": "General information about this site, its owner, and purpose",
"mode": "MODE2",
"response_types": ["text/answer"]
},
{
"name": "content_search",
"description": "Find specific content, posts, or pages by topic",
"mode": "MODE2",
"response_types": ["text/answer", "application/feed"]
},
{
"name": "get_video",
"description": "Retrieve a video by title, topic, or ID",
"mode": "MODE2",
"response_types": ["media/video", "text/answer"],
"accept_fallback": true
},
{
"name": "contact",
"description": "How to reach the site owner; returns structured data",
"mode": "MODE1",
"response_types": ["application/data"]
}
],
"authentication": "none",
"rate_limit": "30/minute",
"content_signals": {
"ai_train": false,
"ai_input": true,
"search": true
},
"async": {
"supported": false
}
}
4.2 Required Fields
| Field | Type | Description |
|---|---|---|
ahp | string | Protocol version. MUST be present. |
modes | array | List of supported modes. At least one MUST be declared. |
content_signals | object | Content usage declarations (see Section 7). MUST be present. |
4.3 Optional Fields
| Field | Type | Description |
|---|---|---|
name | string | Human-readable site name |
description | string | Brief description for visiting agents |
endpoints | object | URLs for AHP endpoints |
capabilities | array | Declared capabilities (required for MODE2/MODE3). Each capability MAY include response_types (array of content type strings) and accept_fallback (boolean — whether the capability can fall back to text/answer if the visiting agent cannot handle the primary type). |
authentication | string | Auth scheme: "none", "bearer", "api_key" |
rate_limit | string | Request limit in "N/period" format |
async | object | Async capability declaration (see Section 9) |
5. Modes
5.1 MODE1 — Static Serve
The agent-readable web. No server required beyond static hosting.
In MODE1, the site provides a static, structured representation of its content accessible to agents. This is the entry point for AHP adoption and is compatible with existing Cloudflare llms.txt implementations.
Requirements:
- The manifest MUST declare
"modes": ["MODE1"] - The site MUST serve agent-readable content. This MAY be:
- A
/llms.txtfile following the llms.txt convention - Markdown pages accessible at predictable URLs
- A static JSON knowledge document
- A
- The manifest SHOULD include an
endpoints.contentpointing to the primary content URL
Visiting agent behavior: A visiting agent discovering a MODE1 site fetches the content document and processes it locally. The interaction is read-only and stateless.
Compatibility: Sites already exposing /llms.txt or ?markdown=true endpoints are MODE1-compatible with only the addition of /.well-known/agent.json.
5.2 MODE2 — Interactive Knowledge
The agent asks questions. The site answers from its content.
MODE2 adds a conversational endpoint. Instead of parsing a document, the visiting agent submits queries and receives precise, sourced answers. The concierge is typically backed by a retrieval system (vector search, structured data, or keyword search) over the site’s content.
Requirements:
- All MODE1 requirements apply (backwards compatibility is mandatory)
- The manifest MUST declare capabilities with
"mode": "MODE2" - The site MUST expose a
POST /agent/converseendpoint (or the path declared inendpoints.converse) - The endpoint MUST support single-turn queries
- The endpoint SHOULD support multi-turn sessions via
session_id
Key characteristic: The concierge answers from its knowledge base. It does not take actions, make external calls, or escalate to humans. It knows what the site contains and surfaces it precisely.
5.3 MODE3 — Agentic Desk
The site’s agent works on your behalf.
MODE3 elevates the concierge from a knowledge retrieval system to an active agent. The site-side concierge may have access to tools, MCP servers, external APIs, and human operators that the visiting agent does not. A visiting agent can delegate tasks to the concierge that require capabilities it lacks.
Requirements:
- All MODE2 requirements apply
- The manifest MUST declare MODE3 capabilities with explicit
input_schemaandoutput_schema - Each MODE3 capability MUST declare its
action_type:"query","action", or"async" - Capabilities of type
"action"or"async"MUST require authentication (authentication MUST NOT be"none") - The site MUST implement the async model (Section 9) for
action_type: "async"capabilities
Example MODE3 capabilities:
- Book an appointment (accesses a calendar)
- Check order status (accesses a CRM)
- Get a custom quote (runs a calculation or contacts a human)
- Perform deep research (has access to MCPs the visitor does not)
- Escalate to a human (routes to a real person and delivers answer via callback)
Key characteristic: MODE3 capabilities have action surface. This requires a trust model (Section 8) and careful capability scoping. Visiting agents MUST declare their intent when invoking MODE3 capabilities.
6. Conversational Endpoint
6.1 Request Format
POST /agent/converse
Content-Type: application/json
{
"ahp": "0.1",
"capability": "content_search",
"query": "What has the site owner written about AI agents?",
"session_id": null,
"context": {
"requesting_agent": "my-research-bot/1.0",
"user_intent": "research",
"max_tokens": 500,
"accept_types": ["text/answer", "application/feed", "media/video"]
}
}
| Field | Required | Description |
|---|---|---|
ahp | SHOULD | Protocol version for compatibility checking |
capability | MUST | The capability being invoked |
query | MUST | The request or question |
session_id | MAY | Session identifier for multi-turn exchanges |
context | MAY | Additional context about the requesting agent |
context.accept_types | MAY | List of content types the visiting agent can handle, in preference order. If absent, the concierge SHOULD default to text/answer. See Section 6.6 and Appendix C. |
6.2 Response — Success
{
"status": "success",
"session_id": null,
"response": {
"answer": "The site owner has written three pieces on AI agents...",
"sources": [
{
"title": "Why Agents Need Better Web Protocols",
"url": "/blog/agent-protocols",
"relevance": "direct"
}
],
"follow_up": {
"suggested_queries": [
"What is the site owner's professional background?",
"Are there open source projects related to this topic?"
]
}
},
"meta": {
"tokens_used": 187,
"capability_used": "content_search",
"mode": "MODE2",
"cached": false,
"content_signals": {
"ai_train": false,
"ai_input": true
}
}
}
6.3 Response — Clarification Needed
When the query is ambiguous, the concierge SHOULD request clarification rather than guess.
{
"status": "clarification_needed",
"session_id": "abc-123",
"clarification": {
"question": "Are you looking for blog posts, or also open source projects?",
"options": ["blog_posts", "projects", "everything"],
"free_form": true
}
}
options MAY be null for entirely open-ended clarification. free_form: true signals that the visiting agent may respond with arbitrary text rather than selecting an option.
To continue, the visiting agent resubmits with the same session_id and a clarification field:
{
"capability": "content_search",
"query": "What has the site owner written about AI agents?",
"session_id": "abc-123",
"clarification": "blog_posts"
}
6.4 Response — Async Accepted (MODE3)
{
"status": "accepted",
"session_id": "xyz-456",
"eta_seconds": 120,
"callback": {
"method": "POST",
"url": "https://[provided by visiting agent]"
},
"poll": "/agent/converse/status/xyz-456"
}
6.5 Session Constraints
Implementations SHOULD enforce:
- Maximum 10 turns per session
- Session expiry after 10 minutes of inactivity
- Request body cap of 8KB
- Rate limiting per IP and optionally per
requesting_agent
6.6 Response Content Types
AHP responses are not limited to text. A concierge MAY return any media type — video streams, audio, images, files, structured feeds, or custom payloads — provided the type is declared in the capability’s response_types and the visiting agent has indicated it can handle it via context.accept_types.
Content type negotiation:
- The manifest declares what types each capability can return (
response_types) - The visiting agent declares what types it can handle (
context.accept_types) in preference order - The concierge selects the most preferred type both parties support
- If no overlap exists and
accept_fallback: trueis set on the capability, the concierge MUST fall back totext/answerwith a plain-language description of the content - If no overlap exists and
accept_fallbackis false or absent, the concierge MUST return a400error with codeunsupported_type, listing the available types
Response structure with content type:
The standard success response is extended with a content_type field and a payload object alongside (or instead of) answer:
{
"status": "success",
"response": {
"content_type": "media/video",
"payload": {
"stream_url": "https://cdn.example.com/video/intro-to-ahp.m3u8",
"format": "hls",
"duration_seconds": 847,
"width": 1920,
"height": 1080,
"thumbnail_url": "https://cdn.example.com/thumb/intro-to-ahp.jpg",
"subtitles": [
{ "lang": "en", "url": "https://cdn.example.com/subs/intro-to-ahp.en.vtt" }
],
"title": "Introduction to AHP",
"description": "A walkthrough of the Agent Handshake Protocol specification."
},
"answer": "Here is the requested video. It covers AHP modes 1 through 3 in 14 minutes.",
"sources": [
{ "title": "Introduction to AHP", "url": "/videos/intro-to-ahp", "relevance": "direct" }
]
},
"meta": {
"content_type": "media/video",
"capability_used": "get_video",
"mode": "MODE2"
}
}
Rules:
- Media content MUST be URL-referenced. Binary content MUST NOT be embedded in the response body.
answerSHOULD always be present as a human-readable (and LLM-readable) summary, even when a rich payload is returned. This ensures visiting agents that cannot render the media can still extract useful information.payloadstructure is defined per content type. See Appendix C for the standard type registry and payload schemas.content_typein themetaobject SHOULD echo the type used, to aid logging and downstream processing.
Fallback example — visiting agent cannot handle media/video, capability has accept_fallback: true:
{
"status": "success",
"response": {
"content_type": "text/answer",
"answer": "The video 'Introduction to AHP' (14 min) is available at https://example.com/videos/intro-to-ahp. It covers MODE1 through MODE3 with live demonstrations.",
"sources": [
{ "title": "Introduction to AHP", "url": "/videos/intro-to-ahp", "relevance": "direct" }
]
},
"meta": {
"content_type": "text/answer",
"fallback_from": "media/video",
"capability_used": "get_video",
"mode": "MODE2"
}
}
The meta.fallback_from field informs the visiting agent that a richer response was available but not served due to type negotiation. The visiting agent MAY retry with the appropriate accept_types if it gains the ability to handle the type.
7. Content Signals
Content signals allow site owners to declare their preferences for AI usage of their content. They MUST appear in the manifest and SHOULD be echoed in responses.
| Signal | Type | Meaning |
|---|---|---|
ai_train | boolean | May this content be used to train AI models? |
ai_input | boolean | May this content be used as input/context for AI inference? |
search | boolean | May this content be indexed for AI-powered search? |
attribution_required | boolean | Must the source be cited when content is used? |
Visiting agents and downstream systems MUST respect ai_train: false by not including the response content in training pipelines. AHP does not enforce this technically — it is a declaration of intent and a legal/ethical signal.
A future AHP revision may define a signed content signals extension for stronger assertions.
8. Trust & Identity
8.1 Visiting Agent Identity
Visiting agents SHOULD include a requesting_agent field in the request context. This is an unverified hint — it is not a security mechanism. Sites MAY use it for logging, routing, or capability gating.
8.2 Authentication
MODE1 and MODE2 query capabilities MAY be unauthenticated. MODE3 action capabilities MUST require authentication.
Supported schemes:
| Scheme | Description |
|---|---|
none | No authentication required |
bearer | HTTP Bearer token in Authorization header |
api_key | API key in X-AHP-Key header |
signed_request | HMAC-signed request body (details TBD in 0.2) |
8.3 Future Work
A future revision will define a verifiable agent identity extension, likely building on existing work in decentralized identity (DIDs) or signed JWTs with well-known public keys. The goal is to allow site-side concierges to make trust decisions based on verified agent identity rather than self-reported claims.
9. Async Model
MODE3 capabilities that involve human escalation, long-running computation, or external API calls MUST use the async model.
9.1 Flow
- Visiting agent POSTs to
/agent/conversewith a MODE3 async capability - Concierge responds with
status: "accepted", asession_id, estimated ETA, and apollURL - Visiting agent either:
- Polls
GET /agent/converse/status/{session_id}until status issuccessorfailed - Waits for callback at a URL it provided in the initial request
- Polls
- On completion, the response follows the standard success format
9.2 Status Response
{
"status": "pending",
"session_id": "xyz-456",
"progress": "Waiting for human operator response",
"eta_seconds": 60
}
Status values: pending, success, failed, expired
10. Error Handling
All errors MUST return appropriate HTTP status codes and a JSON body:
{
"status": "error",
"code": "unknown_capability",
"message": "The capability 'foobar' is not supported.",
"available_capabilities": ["site_info", "content_search", "contact"]
}
| HTTP Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed request body |
| 400 | unknown_capability | Capability not in manifest |
| 400 | missing_field | Required field absent |
| 401 | auth_required | Authentication required for this capability |
| 403 | forbidden | Valid auth but insufficient permissions |
| 413 | request_too_large | Body exceeds size cap |
| 429 | rate_limited | Rate limit hit; includes Retry-After header |
| 500 | concierge_error | Internal error in the concierge |
| 503 | unavailable | Concierge temporarily unavailable |
11. Rate Limiting
Rate limiting in AHP serves two purposes: protecting the site’s infrastructure and managing LLM API costs for MODE2/MODE3 concierges. Sites MUST communicate rate limit status using standard headers and SHOULD publish their limits in the manifest.
11.1 Required Headers
All AHP endpoints MUST include the following headers on every response when rate limiting is active:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
X-RateLimit-Window | Window duration in seconds (e.g. 60) |
On a 429 Too Many Requests response, the site MUST also include:
| Header | Description |
|---|---|
Retry-After | Seconds until the visiting agent may retry |
Example headers:
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 12
X-RateLimit-Reset: 1708559400
X-RateLimit-Window: 60
11.2 Recommended Limits by Mode
These are recommended defaults. Sites MAY apply stricter or more permissive limits based on their infrastructure and use case.
| Mode | Unauthenticated | Authenticated |
|---|---|---|
| MODE1 (static content) | 120/minute | N/A |
| MODE2 (conversational) | 30/minute | 120/minute |
| MODE3 (agentic, query) | 30/minute | 120/minute |
| MODE3 (agentic, action) | N/A | 30/minute |
MODE3 action capabilities SHOULD have conservative limits regardless of authentication status. Actions have side effects; runaway agents can cause real-world consequences.
11.3 Limit Scope
Rate limits SHOULD be applied per IP address as the baseline. Sites MAY additionally apply limits per requesting_agent identifier (from the request context), per API key, or per session.
When requesting_agent scoping is in use and a visiting agent hits its per-agent limit before the per-IP limit, the 429 response SHOULD indicate this:
{
"status": "error",
"code": "rate_limited",
"message": "Rate limit exceeded for this agent identity.",
"scope": "agent",
"retry_after": 47
}
11.4 Cost-Based Throttling (MODE2/MODE3)
Sites operating MODE2 or MODE3 concierges backed by LLM APIs incur per-token costs. Sites MAY implement cost-based throttling in addition to request-count limits:
- Token budget per session: Limit total tokens consumed across a session’s turns (recommended: 10,000 tokens/session)
- Daily token quota per IP: Cap total LLM token spend per IP per day
- Max tokens per response: Honor the
context.max_tokenshint from visiting agents, and enforce a hard ceiling regardless
When a token budget is exhausted, respond with 429 and include:
{
"status": "error",
"code": "rate_limited",
"message": "Token budget for this session has been exhausted.",
"scope": "session_tokens",
"retry_after": null
}
retry_after: null signals that retrying in the same session will not help; the visiting agent should start a new session.
11.5 Manifest Declaration
Sites MUST declare their rate limits in the manifest for transparency:
{
"rate_limits": {
"unauthenticated": {
"requests": "30/minute",
"token_budget": "5000/session"
},
"authenticated": {
"requests": "120/minute",
"token_budget": "20000/session"
}
}
}
11.6 Backoff Guidance for Visiting Agents
Visiting agents MUST respect Retry-After headers and MUST NOT retry before the indicated time. Visiting agents SHOULD implement exponential backoff with jitter when retrying after a 429. Visiting agents SHOULD NOT treat 429 as a fatal error — it is a temporary condition.
12. Versioning & Backwards Compatibility
The ahp field in both the manifest and requests carries the protocol version.
- A site implementing AHP 0.x MUST remain compatible with MODE1 visiting agents regardless of which modes it supports.
- A visiting agent encountering an unknown
ahpversion SHOULD fall back to MODE1 behavior. - Minor version increments (0.1 → 0.2) MUST be backwards compatible.
- Major version increments (0.x → 1.0) MAY introduce breaking changes but MUST provide a migration path.
13. Security Considerations
- Prompt injection: MODE2 and MODE3 concierges that pass visiting agent queries to an LLM MUST sanitize inputs and implement guardrails against prompt injection attacks.
- Data exfiltration: MODE3 action capabilities SHOULD restrict what data can be returned to visiting agents, particularly for authenticated endpoints.
- Rate limiting: All AHP endpoints SHOULD implement rate limiting. Recommended defaults: 30 requests/minute for unauthenticated, 120/minute for authenticated.
- SSRF: Concierges that accept callback URLs from visiting agents (async model) MUST validate URLs to prevent SSRF attacks.
- Content signal enforcement: AHP does not technically enforce content signals. Sites SHOULD include signals in responses; visiting agents SHOULD honor them.
14. Examples
13.1 Minimal MODE1 Implementation
/.well-known/agent.json → AHP manifest with modes: ["MODE1"]
/llms.txt → Site content in plain text/markdown
A visiting agent fetches the manifest, finds the content URL, retrieves /llms.txt, and processes it locally. Zero server-side logic required.
13.2 MODE2 Query Flow
Visiting Agent Site Concierge
│ │
│ GET /.well-known/agent.json │
│──────────────────────────────────────►│
│ ◄── manifest (modes: MODE1, MODE2) ──│
│ │
│ POST /agent/converse │
│ { capability: "content_search", │
│ query: "posts about AI agents" } │
│──────────────────────────────────────►│
│ ◄── { status: "success", │
│ response: { answer, sources }} │
13.3 MODE3 Human Escalation Flow
Visiting Agent Site Concierge + Human
│ │
│ POST /agent/converse │
│ { capability: "get_custom_quote", │
│ query: "...", auth: "Bearer ..." } │
│──────────────────────────────────────►│
│ ◄── { status: "accepted", │
│ session_id, poll: "/..." } │
│ │ [human notified]
│ GET /agent/converse/status/xyz │ [human responds]
│──────────────────────────────────────►│
│ ◄── { status: "success", │
│ response: { answer } } │
Appendix A: JSON Schemas
Machine-readable JSON Schema files for all AHP data structures are published alongside this specification. Implementations SHOULD validate against these schemas.
| Schema | URL | Validates |
|---|---|---|
| Manifest | /schema/0.1/manifest.json | /.well-known/agent.json |
| Request | /schema/0.1/request.json | POST /agent/converse body |
| Response | /schema/0.1/response.json | POST /agent/converse response |
All schemas use JSON Schema draft-07 and are versioned alongside the specification. The schema $id URIs are stable and will not change for a given version.
Validating a manifest:
# Using ajv-cli
npx ajv validate -s https://agenthandshake.dev/schema/0.1/manifest.json \
-d .well-known/agent.json
# Using Python jsonschema
pip install jsonschema requests
python3 -c "
import jsonschema, json, requests
schema = requests.get('https://agenthandshake.dev/schema/0.1/manifest.json').json()
manifest = json.load(open('.well-known/agent.json'))
jsonschema.validate(manifest, schema)
print('Valid')
"
Appendix B: Relationship to llms.txt
The llms.txt convention (proposed by Answer.AI) defines a standard location for a site-level plain text or markdown document intended for LLM consumption. It is a useful first step — better than raw HTML — but it is fundamentally a document dump: a static, unstructured file that an LLM must parse entirely, regardless of what it actually needs.
AHP is the evolution beyond llms.txt. The differences are significant:
| llms.txt | AHP | |
|---|---|---|
| Interaction model | Read-only document | Conversational exchange |
| Agent gets what it needs | Must parse everything | Asks for what it needs |
| Capabilities declared | No | Yes |
| Multi-turn support | No | Yes |
| Human escalation | No | Yes (MODE3) |
| Content signals | No | Yes |
| Rate limiting | No | Yes |
| Agent discovery (in-page) | No | Yes |
| Backwards compatible with llms.txt | — | Yes |
AHP does not deprecate llms.txt. A site with an existing llms.txt can become AHP MODE1 compliant with zero changes to that file:
- Add
/.well-known/agent.jsonwithmodes: ["MODE1"]andendpoints.content: "/llms.txt" - Add the
<link rel="agent-manifest">tag to HTML pages - Add the in-page agent notice (Section 3.4)
The llms.txt file becomes the content source for MODE1. When the site is ready to upgrade to MODE2, the same content can back the conversational endpoint.
The goal is not to fragment the ecosystem — it is to give it a path forward. Sites that adopt AHP are not abandoning llms.txt compatibility; they are making it meaningful.
Appendix C: Extension Mechanism & Content Type Registry
C.1 Extension Philosophy
AHP cannot enumerate every content type or capability pattern the web will produce. Instead, the protocol defines:
- A core registry of well-known content types with standardised payload schemas
- A namespaced extension prefix (
x-) for custom types that any implementation may use without coordination - A promotion path for widely-adopted extensions to become core types in future revisions
This mirrors the design of HTTP headers, MIME types, and HTML data attributes — all of which use the same pattern successfully.
C.2 Core Content Type Registry
The following types are defined by this specification. Payload schemas are normative.
text/answer
Default type. A plain-text (or markdown) response. Used when no richer type applies or as a fallback.
{
"content_type": "text/answer",
"answer": "string — the response text. Markdown is permitted.",
"sources": [ { "title": "string", "url": "string", "relevance": "direct|indirect|background" } ],
"follow_up": { "suggested_queries": ["string"] }
}
application/data
Structured JSON data. Used when the response is machine-readable records rather than prose — contact info, product details, structured metadata.
{
"content_type": "application/data",
"payload": {
"schema": "URI or name identifying the data shape (optional but RECOMMENDED)",
"data": { }
},
"answer": "string — human-readable summary (SHOULD be present)"
}
application/feed
A list of items. Used for search results, article listings, product catalogues, or any ordered collection.
{
"content_type": "application/feed",
"payload": {
"total": 42,
"items": [
{
"title": "string",
"url": "string",
"description": "string",
"published_at": "ISO 8601 datetime or null",
"thumbnail_url": "string or null",
"metadata": { }
}
],
"next_cursor": "string or null — for pagination"
},
"answer": "string — summary of results"
}
media/video
A video resource. Payload provides stream URL, format, and metadata sufficient for a capable agent to present, embed, or describe the content.
{
"content_type": "media/video",
"payload": {
"stream_url": "string — primary playback URL (HLS, DASH, MP4, etc.)",
"format": "hls | dash | mp4 | webm | string",
"duration_seconds": 0,
"width": 1920,
"height": 1080,
"thumbnail_url": "string or null",
"title": "string",
"description": "string or null",
"published_at": "ISO 8601 datetime or null",
"subtitles": [
{ "lang": "BCP 47 language tag", "url": "string — VTT or SRT" }
],
"chapters": [
{ "title": "string", "start_seconds": 0 }
],
"alternate_formats": [
{ "format": "string", "url": "string", "quality": "string" }
]
},
"answer": "string — description of the video"
}
media/audio
An audio resource. Covers podcasts, music, voice recordings, and audio streams.
{
"content_type": "media/audio",
"payload": {
"stream_url": "string",
"format": "mp3 | ogg | aac | flac | string",
"duration_seconds": 0,
"title": "string",
"description": "string or null",
"published_at": "ISO 8601 datetime or null",
"thumbnail_url": "string or null",
"transcript_url": "string or null — VTT, SRT, or plain text",
"chapters": [
{ "title": "string", "start_seconds": 0 }
]
},
"answer": "string — description of the audio"
}
media/image
One or more images. Used for photos, illustrations, diagrams, or galleries.
{
"content_type": "media/image",
"payload": {
"images": [
{
"url": "string",
"alt": "string — descriptive alt text. REQUIRED.",
"width": 0,
"height": 0,
"format": "jpeg | png | webp | gif | svg | string",
"title": "string or null"
}
],
"layout": "single | gallery | carousel"
},
"answer": "string — description of the image(s)"
}
file/download
A downloadable file. Used for PDFs, datasets, archives, documents, or any non-media binary.
{
"content_type": "file/download",
"payload": {
"url": "string — download URL",
"filename": "string",
"mime_type": "string — IANA MIME type",
"size_bytes": 0,
"checksum": "string — SHA-256 hex digest (RECOMMENDED)",
"description": "string or null",
"expires_at": "ISO 8601 datetime or null — if the URL is time-limited"
},
"answer": "string — description of the file"
}
application/action-result
The outcome of a MODE3 action capability. Used when an action was performed and a result is being returned.
{
"content_type": "application/action-result",
"payload": {
"action": "string — name of the action performed",
"success": true,
"result": { },
"side_effects": [
{ "type": "string", "description": "string" }
]
},
"answer": "string — plain-language summary of what happened"
}
C.3 Extension Types
Any content type prefixed with x- is an extension type. Extension types are not defined by this specification and carry no compatibility guarantees.
Format: x-{vendor}/{type}
Examples:
x-shopify/cart— Shopify cart statex-realestate/listing— Property listing with geo, pricing, photosx-health/appointment— Healthcare appointment booking result
Rules for extension types:
- MUST be prefixed with
x- - SHOULD include a vendor namespace to avoid collisions
- The payload schema is defined entirely by the implementer
answerSHOULD still be present for agents that cannot interpret the extension type- Extension types SHOULD be documented publicly if intended for third-party use
Promotion to core: Extension types that see broad adoption across multiple independent implementations MAY be proposed for inclusion in the core registry via the standard contribution process. Proposals should include real-world usage evidence and at least two independent implementations.
C.4 Registering a Content Type
To formally propose a new core content type:
- Open an issue in the spec repository with the label
content-type-proposal - Provide: the type string, a use case description, a normative payload schema, at least one worked example, and evidence of need (why existing types don’t cover it)
- Discussion and refinement happens in the issue
- If accepted, a PR adds it to Appendix C and the type is included in the next minor revision
Until a proposal is accepted, use the x- prefix.
Agent Handshake Protocol — Draft 0.1 Authors: Nick Allain, [contributors] Repository: https://github.com/AHP-Organization/agent-handshake-protocol Spec site: https://agenthandshake.dev