Site action manifest

Status: draft 0.1 (agent-actions/0.1). Covers friction #6 (forms driven by pixels or guessed selectors), #11 (most sites have no action API), #22 (no discovery of which service can do a task) and #23 (multi-step flows have no machine-readable steps). Reference implementation: agent_index/actions.py.

A site publishes /.well-known/agent-actions.json. Agents call the actions it lists instead of clicking through pages, so a redesign no longer breaks them.

Manifest

{
  "version": "agent-actions/0.1",
  "site": "shop.example",
  "auth": {"type": "agent-grant"},
  "actions": [
    {
      "name": "add_to_cart",
      "description": "Add a product to the cart",
      "method": "POST",
      "path": "/api/cart",
      "effect": "write",
      "scope": "cart:write",
      "input": {
        "type": "object",
        "required": ["product_id", "quantity"],
        "properties": {
          "product_id": {"type": "string"},
          "quantity": {"type": "integer", "minimum": 1, "maximum": 20}
        }
      }
    }
  ],
  "flows": [
    {
      "name": "buy_product",
      "steps": [
        {"action": "search_products", "with": {"query": "$input.query"}},
        {"action": "add_to_cart", "with": {"product_id": "$steps.0.items.0.id", "quantity": "$input.quantity"}},
        {"action": "checkout", "with": {"cart_id": "$steps.1.cart_id"}}
      ]
    }
  ]
}
Field Meaning
input JSON Schema (2020-12). Agents validate before sending and get every problem back as structured errors.
effect read, write, send, spend or delete. Agents feed it to their approval policy, so spending or deleting can wait for the person.
scope The grant scope the action needs (see identity).
flows Ordered steps. $input.x reads the flow's inputs; $steps.N.path reads an earlier step's output.
cost For spend actions: {"field": "price", "currency": "USD"} names the input that holds the amount, or {"amount": 25, "currency": "USD"} fixes it, so the agent can check its mandate and ask before sending.
endpoints Added by the middleware: where receipts, refunds, revocations and negotiation live.

Errors

Actions fail with {"error": {"code": "...", "message": "...", "field": "...", "retry_after": 30}} using the codes in agent errors.

Discovery

agent-index loads manifests (discover_actions) and searches across them (find_action "buy printer paper"), returning which site offers which action. A shared registry of manifests is the same idea at web scale, and fits the index naturally: the crawler picks up /.well-known/agent-actions.json alongside pages.

Relation to existing work

The input schemas match MCP tool definitions, so a site can serve the same actions as an MCP server. OpenAPI describes endpoints but not effects, scopes for delegated agents, or flows; a manifest can be generated from an OpenAPI file plus those three additions.

Serving actions

A site doesn't write an API layer. It hands the middleware one function per action:

def checkout(inp, ctx):          # inp is already validated against the schema
    deal = ctx.agreement({"product_id": inp["product_id"]})   # a negotiated price, if any
    receipt = ctx.charge(price, "USD", item=inp["product_id"])  # under the caller's mandate
    return {"order_id": receipt["id"]}

app = AgentFriendly(app, host="shop.example", actions=MANIFEST, merchant=shop,
                    handlers={"checkout": checkout, ...})

The middleware routes method and path to the handler, rejects bad input with every problem listed (problems), requires a grant for any action with a scope or an effect other than read, checks the scope, and turns AgentErrors and crashes into structured errors (never a traceback).

Calling actions

AgentClient.call_action and run_flow (MCP tools of the same names) fetch the manifest, validate input, sign each request with the narrowest grant that has the action's scope, and ask the person through the approval policy before spend, send and delete actions (needs_human, then confirm=true). If a flow fails, the error names the step and carries the outputs completed so far. Manifests the agent or the crawler sees are stored in the shared index, so find_action searches every site any agent has seen.