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
AHP defines three discovery mechanisms, each targeting a distinct class of visiting agent. Sites SHOULD implement all three; the well-known URI (§3.1) is the only MUST.
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).
This is the universal fallback: any agent can fetch it directly without prior knowledge of the site’s structure.
3.2 HTTP Link Response Header
Sites SHOULD include an RFC 8288 Link header on every HTTP response — proactively, on all responses including API endpoints, 404 pages, and redirects:
Link: </.well-known/agent.json>; rel="ahp-manifest"; type="application/agent+json"
This mechanism targets agents that make raw HTTP requests and inspect response headers before or instead of parsing body content — including HTTP-native tool-calling agents and agents that issue HEAD requests for lightweight pre-flight discovery. It communicates manifest location at the transport layer, without requiring the agent to receive or parse any response body.
Implementation note: Sites using a reverse proxy can add this header globally with a single configuration line:
# nginx
add_header Link '</.well-known/agent.json>; rel="ahp-manifest"; type="application/agent+json"' always;
3.3 In-Page Agent Notice
Many AI agents interact with websites via headless browsers, HTML-to-text pipelines, or by receiving page content from a human user. In these cases, the agent’s underlying LLM reads rendered page content directly — it may not inspect headers 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:
<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. The text MUST NOT be removed by JavaScript hydration in a way that eliminates it from the rendered text visible to headless browsers.
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 read 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.4 Capability Negotiation (Accept Header)
This is not a discovery mechanism — it is a negotiation signal for agents that already know a site supports AHP.
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:
- A
302redirect to/.well-known/agent.json, or - A
200response containing the manifest directly
An agent sending this header already has prior knowledge of AHP (from a previous crawl, a directory, or a user instruction). The response shortcuts the discovery flow and delivers the manifest immediately.
3.5 Discovery Priority
Discovery priority depends on the agent’s access model. Two cases apply:
HTTP-capable agents (agents that can make direct HTTP requests to the site):
- Fetch
/.well-known/agent.jsondirectly (§3.1) — cheapest, most reliable, no body parsing required - Inspect the HTTP
Linkresponse header forrel="ahp-manifest"on an incidental request (§3.2) — useful when the agent is already making a request for another purpose - Check rendered page content for
aria-label="AI Agent Notice"or classahp-notice(§3.3) — last resort; most expensive and most fragile (depends on DOM structure, JS hydration, and CSS visibility)
Headless browser / content-only agents (agents that received page content from a browser pipeline or from a user, without direct HTTP access):
- Check rendered page content for
aria-label="AI Agent Notice"or classahp-notice(§3.3) — only mechanism available without making a separate HTTP request
Agents with prior knowledge of a site’s AHP support MAY use the Accept: application/agent+json header (§3.4) to bypass discovery entirely and retrieve the manifest directly.
Rationale: The well-known URI is a single HTTP GET with no body parsing — O(1) and deterministic. In-page notice parsing depends on document structure, JavaScript execution, and CSS visibility handling; it should be a fallback for agents that have no other option, not a first step for agents that can make HTTP requests.
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
},
"integrations": {
"mcp": {
"url": "/mcp",
"version": "2024-11-05"
},
"openapi": {
"url": "/openapi.json",
"version": "3.1.0"
}
}
}
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) |
integrations | object | Platform compatibility declarations (see §4.4). Each key is a platform identifier; each value contains at minimum a url field. |
4.4 Platform Integrations
The integrations object in the manifest declares compatibility with external agent platforms. It enables visiting agents built on those platforms to interact with AHP sites through familiar, platform-native protocols — without requiring the platform to natively understand AHP.
Supported integration types:
4.4.1 MCP (Model Context Protocol)
MCP is Anthropic’s open protocol for exposing tools and resources to Claude-based agents. AHP MODE3 capabilities map directly to MCP tools; the AHP knowledge base maps to MCP resources.
"integrations": {
"mcp": {
"url": "/mcp",
"version": "2024-11-05"
}
}
An AHP server declaring an MCP integration MUST serve a JSON-RPC 2.0 endpoint at the declared URL supporting at minimum:
initialize— protocol handshaketools/list— returns all MODE3 action capabilities as MCP tool definitionstools/call— invokes a capability and returns the resultresources/list— returns MODE1/MODE2 content endpoints as MCP resources
The mapping from AHP capabilities to MCP tools is defined in Appendix D.
Claude Desktop, the Claude API in agentic mode, and any MCP-compatible client can connect to an AHP site’s MCP endpoint directly, with no knowledge of AHP required.
4.4.2 OpenAPI / GPT Actions
OpenAI’s GPT Actions use OpenAPI 3.x specs to define callable API endpoints for Custom GPTs and the ChatGPT API. An AHP site can expose its capabilities as an OpenAPI spec, making them available as GPT Actions without requiring OpenAI to natively support AHP.
"integrations": {
"openapi": {
"url": "/openapi.json",
"version": "3.1.0"
}
}
An AHP server declaring an OpenAPI integration MUST serve a valid OpenAPI 3.1.x document at the declared URL. The document MUST describe each AHP capability as a distinct path operation. The mapping from AHP capabilities to OpenAPI path items is defined in Appendix E.
A ChatGPT user creating a Custom GPT points their action at /openapi.json; all AHP capabilities become available as GPT Actions with no additional configuration.
4.4.3 Compatibility Principles
Platform integrations follow these principles:
- Additive only. An
integrationsblock never modifies AHP behaviour — it declares additional access paths. A site withoutintegrationsis fully AHP-compliant; a site withintegrationsis AHP-compliant plus interoperable with additional platforms. - Proxied, not duplicated. Integration endpoints SHOULD proxy to the underlying AHP endpoint (
POST /agent/converse), not implement separate logic. A single concierge implementation serves all access paths. - Same auth model. Authentication requirements declared in the manifest apply equally to integration endpoints. A bearer-token-required capability requires the same bearer token via MCP or OpenAPI as via direct AHP.
- Declared, not assumed. An agent MUST NOT assume MCP or OpenAPI endpoints exist without checking the
integrationsblock in the manifest first.
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 — bounds the LLM cost of a single agent interaction; most legitimate queries resolve in 1–3 turns. Implementers serving complex multi-step workflows MAY increase this limit, but SHOULD require authentication for sessions above 10 turns.
- Session expiry after 10 minutes of inactivity — limits in-memory session accumulation on the server; balances useful multi-turn context against resource consumption. Implementers with authenticated, long-running agent workflows MAY extend this to 30–60 minutes.
- Request body cap of 8KB — prevents prompt injection via oversized payloads and bounds memory allocation per request. Typical well-formed AHP requests are under 1KB; 8KB is generous headroom for structured
contextobjects while excluding pathological inputs. - Rate limiting per IP and optionally per
requesting_agent— see Section 11 for required headers and recommended defaults (30 req/min unauthenticated, 120 req/min authenticated).
These defaults are conservative starting points, not mandated constants. Implementers SHOULD adjust them based on their cost model, authentication posture, and expected agent behaviour.
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 SHOULD respect ai_train: false by not including the response content in training pipelines. AHP does not technically enforce this — it is a declaration of intent and a legal/ethical signal. A future revision may define cryptographic content-signal assertions to enable stronger enforcement.
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
14.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.
14.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 }} │
14.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 HTTP
Linkresponse header to all responses:Link: </.well-known/agent.json>; rel="ahp-manifest"; type="application/agent+json"(Section 3.2) - Add the in-page agent notice to HTML pages (Section 3.3)
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.
Appendix D: MCP Compatibility
This appendix defines the normative mapping from AHP capabilities to MCP (Model Context Protocol) tool and resource definitions, and the required behaviour for AHP servers exposing an MCP integration endpoint.
D.1 Transport
AHP MCP endpoints MUST support the HTTP transport (JSON-RPC 2.0 over HTTP POST). Servers MAY additionally support Server-Sent Events (SSE) for streaming responses.
Endpoint: declared in integrations.mcp.url (e.g. POST /mcp) Content-Type: application/json Protocol version: 2024-11-05 (MCP 1.0)
D.2 Required Methods
| JSON-RPC method | AHP behaviour |
|---|---|
initialize | Returns server info, protocol version, and capability declaration |
tools/list | Returns all MODE3 action_required: true capabilities as MCP tool definitions |
tools/call | Invokes the named AHP capability via POST /agent/converse; proxies auth |
resources/list | Returns MODE1 content endpoints and MODE2 knowledge capabilities as MCP resources |
resources/read | Fetches a resource URI and returns its content |
MODE2 non-action capabilities MAY be exposed as both MCP tools (callable with a query string) and MCP resources (fetchable at a URI). Implementers SHOULD prefer exposing them as tools for maximum compatibility.
D.3 Capability → Tool Mapping
Each AHP MODE3 capability maps to one MCP tool definition:
AHP capability:
{
"name": "inventory_check",
"description": "Check stock levels and availability for a product",
"mode": "MODE3",
"auth_required": true,
"response_types": ["application/data", "text/answer"]
}
MCP tool definition:
{
"name": "inventory_check",
"description": "Check stock levels and availability for a product",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Natural language query about product availability"
},
"session_id": {
"type": "string",
"description": "Optional: continue an existing conversation session"
}
},
"required": ["query"]
}
}
Rules:
nameMUST match the AHP capabilitynameexactlydescriptionSHOULD be taken from the AHP capabilitydescription- All tools MUST accept
query(string, required) andsession_id(string, optional) - Tool-specific parameters MAY be added for capabilities with structured inputs
D.4 tools/call → AHP Request Mapping
When a MCP client calls a tool, the server MUST proxy it to POST /agent/converse:
// MCP tools/call request
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "inventory_check",
"arguments": {
"query": "Is AHP-IMPL-001 in stock?",
"session_id": "abc-123"
}
}
}
Maps to:
// AHP /agent/converse request
{
"capability": "inventory_check",
"query": "Is AHP-IMPL-001 in stock?",
"session_id": "abc-123",
"auth": "<bearer token from MCP _meta or Authorization header>"
}
The MCP response MUST contain the AHP response.answer as content[0].text, and MAY include additional content blocks for structured data.
D.5 Authentication Passthrough
MCP does not define a native auth mechanism. AHP MCP endpoints MUST accept bearer tokens via:
- The HTTP
Authorization: Bearer <token>header on the MCP request, OR - A
_meta.authfield in theparamsobject of anytools/callrequest
The server MUST apply the same auth requirements to MCP tool calls as to direct AHP requests.
D.6 initialize Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {},
"resources": {}
},
"serverInfo": {
"name": "<manifest.name>",
"version": "<ahp version>",
"ahp": "0.1",
"manifest": "/.well-known/agent.json"
}
}
}
The serverInfo.manifest field lets MCP-native clients discover the full AHP manifest for richer interaction.
Appendix E: OpenAPI / GPT Actions Compatibility
This appendix defines the normative mapping from AHP capabilities to OpenAPI 3.1.x path items, and the required behaviour for AHP servers exposing an OpenAPI integration endpoint.
E.1 Document Structure
The OpenAPI document served at integrations.openapi.url MUST conform to OpenAPI 3.1.0 and MUST include:
info.title— frommanifest.nameinfo.description— frommanifest.descriptioninfo.version— AHP protocol versionservers[0].url— the site’s base URL- One path per AHP capability (see §E.2)
- A
components.securitySchemesblock if authentication is required
E.2 Capability → Path Mapping
Each AHP capability maps to a POST operation at /capabilities/{capability_name}:
AHP capability:
{
"name": "get_quote",
"description": "Calculate a price quote for one or more products",
"mode": "MODE3",
"auth_required": true
}
OpenAPI path item:
/capabilities/get_quote:
post:
operationId: get_quote
summary: Calculate a price quote for one or more products
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [query]
properties:
query:
type: string
description: Natural language description of the quote request
session_id:
type: string
description: Optional session ID to continue an existing conversation
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/AHPResponse'
'400':
description: Bad request
'401':
description: Authentication required
'429':
description: Rate limit exceeded
The server MUST proxy requests to this path to POST /agent/converse with capability set to the path’s capability name.
E.3 AHPResponse Schema
All capability paths share a common response schema:
components:
schemas:
AHPResponse:
type: object
properties:
status:
type: string
enum: [success, clarification_needed, accepted, error]
session_id:
type: string
response:
type: object
properties:
content_type:
type: string
answer:
type: string
sources:
type: array
items:
type: object
meta:
type: object
properties:
mode:
type: string
cached:
type: boolean
tokens_used:
type: integer
securitySchemes:
bearerAuth:
type: http
scheme: bearer
E.4 GPT Actions Integration
To add an AHP site as a ChatGPT GPT Action:
- Create a Custom GPT at chat.openai.com
- Under “Actions”, add a new action
- Enter the site’s OpenAPI URL (e.g.
https://example.com/openapi.json) - ChatGPT imports all AHP capabilities as callable functions
- If the site requires authentication, add the bearer token under “Authentication”
The AHP site’s concierge handles all natural language processing; the GPT receives structured AHP responses and can present them to the user.
E.5 Capability Filtering
The OpenAPI document SHOULD only expose capabilities appropriate for the integration context:
- MODE1 capabilities (static content) SHOULD be omitted or exposed as GET operations on their content URLs, not as POST capabilities
- MODE3 capabilities with
auth_required: trueMUST include the appropriate security scheme - Async capabilities (returning
status: accepted) MUST document the polling pattern in their operation description
Agent Handshake Protocol — Draft 0.1 Authors: Nick Allain, [contributors] Repository: https://github.com/AHP-Organization/agent-handshake-protocol Spec site: https://agenthandshake.dev