WebAgents is a small, open protocol proposal for giving browser agents a safe directory of first-party website actions and one dependency-aware batch endpoint.
An agent normally spends a model round trip discovering each control, performing one action, observing the new page, and planning again. A participating site publishes the exact actions it supports, so the agent can plan a complete workflow and submit it once. The site still owns authentication, authorization, validation, rate limits, pacing, state, and business logic.
GET /webagents.md discover a compact action directory
POST /api/agent/workflow submit one bounded operation graph
read → write → write → final read return verified results in one response
WebAgents is not remote code execution, a page-authored prompt, or permission to bypass the visible product. Descriptions and results are untrusted data. Action names resolve only through a server-owned allowlist.
- One discovery fetch replaces repeated accessibility-tree inspection.
$refpasses one operation's result into a later input without another model turn.- Independent reads can run concurrently.
- Writes stay in deterministic list order.
- A final read verifies the state in the same batch.
- Server-declared pacing keeps fast batches realistic and rate-limit friendly.
The result is especially useful for multi-step tasks such as “find this exact item, create an account, perform an authorized action, then verify it.” It does not make the network instantaneous and it does not remove application checks.
- Define a manifest:
{
"version": "0.1",
"workflow": {
"endpoint": "/api/agent/workflow",
"maxOperations": 8,
"parallel": true,
"references": true,
"pacing": { "minIntervalMs": 120, "maxConcurrency": 2 }
},
"actions": {
"catalog.find": {
"pathPrefixes": ["/products"],
"description": "Find an exact product; reference productId.",
"effect": "read",
"inputSchema": {
"type": "object",
"properties": { "query": { "type": "string" } },
"required": ["query"]
}
},
"cart.add": {
"pathPrefixes": ["/products"],
"description": "Add one productId to the active cart.",
"effect": "write"
},
"cart.state": {
"pathPrefixes": ["/products"],
"description": "Return the active cart for verification.",
"effect": "read"
}
}
}-
Serve that JSON in one fenced block at
/webagents.mdand as raw JSON at/.well-known/webagents.json. -
Route the manifest's endpoint to handlers backed by the same service layer as the visible UI:
import { createNodeAdapter, WebAgentsError } from "webagents-protocol";
const webagents = createNodeAdapter({
origin: "https://shop.example",
manifest,
getContext: (req, res) => loadSessionAndRequestContext(req, res),
handlers: {
"catalog.find": ({ input, context }) => context.catalog.find(input.query),
"cart.add": async ({ input, context }) => {
if (!context.user) {
throw new WebAgentsError("Sign in first.", { status: 401, code: "unauthorized" });
}
return context.cart.add(input.productId);
},
"cart.state": ({ context }) => context.cart.read(),
},
});
const server = http.createServer(async (req, res) => {
if (await webagents.handle(req, res)) return;
return handleYourExistingSite(req, res);
});The package is currently installable from this repository:
npm install github:BetterWright/WebAgentsThe adapter has no runtime dependencies. The complete session-aware implementation is in examples/node-server.
{
"version": "0.1",
"operations": [
{
"id": "find",
"action": "catalog.find",
"input": { "query": "wireless keyboard" }
},
{
"id": "add",
"action": "cart.add",
"input": { "productId": { "$ref": "find.productId" } }
},
{
"id": "verify",
"action": "cart.state",
"input": {}
}
]
}$ref implies a dependency on find. Because add is a write, a conforming server also makes the later verify read depend on it. The response is keyed by operation id:
{
"ok": true,
"protocol": "webagents/0.1",
"pacing": { "minIntervalMs": 120, "maxConcurrency": 2 },
"results": {
"find": { "productId": "kbd-75" },
"add": { "added": true },
"verify": { "items": [{ "productId": "kbd-75", "quantity": 1 }] }
}
}- Clients try
https://origin.example/webagents.md, thenhttps://origin.example/.well-known/webagents.json. - Markdown contains exactly one
webagentsorwebagents-jsonfenced JSON block. Surrounding prose is never executable. - The endpoint must resolve to the active page's origin.
pathPrefixeskeeps unrelated actions out of the model context. It is a relevance hint, not authorization.- If discovery is absent, invalid, or unsafe, the client falls back to normal browser interaction.
Validate a local manifest:
node bin/webagents.js validate examples/node-server/webagents.json --origin https://example.comSmoke-test a deployed site without running any actions:
node bin/webagents.js check https://example.com/products/keyboardThe live checker verifies both GET and HEAD discovery paths, MIME types, normalized mirror equality, same-origin endpoint resolution, and active-path action scoping. It deliberately never POSTs.
A deployed example is available at bhbench.ssh.codes/webagents.md.
- Use one fixed
PUBLIC_ORIGIN; do not trust arbitrary forwarded host headers. - Keep the endpoint same-origin and HTTPS.
- Reuse the website's normal cookie or token session.
- Authenticate and authorize every action inside its handler.
- Validate action inputs in the application even when a schema is published.
- Rate-limit authentication and expensive actions.
- Hash passwords, redact secrets, and never place credentials in results or logs.
- Let the server enforce its configured pacing; ignore faster client hints.
- Serialize writes, bound bodies/results, reject cycles, and use database transactions where atomicity matters.
- End mutating workflows with a read and test the public origin after every deploy.
Read the normative SPECIFICATION.md, the deployment-focused SECURITY.md, and TROUBLESHOOTING.md before production use.
Version 0.1 is an implementable protocol proposal. Compatibility changes require a new manifest version. Contributions and real-world deployment reports are welcome.
MIT licensed.