Labs/Dual-audience interfaces
08

The page as an agent reads it

Toggle between what a person sees, what an agent perceives through the accessibility tree, and the tools a page exposes directly.

GoalDesign components that people and agents can both operate — and see why screenshots are the worst interface for an agent.

accessibility treeWebMCPtool affordancesagent-ready components
Build
Agent lens
1 · Human viewsemantic HTML

Subscription

You're on the Team plan · $48/month · renews Dec 1

Plan

Danger zone

Permanently deletes your workspace.

Toggle the build: to a person, the two pages look the same. Everything that differs is invisible.

2 · Accessibility tree (simplified)14 nodes · 0 problems
main"Subscription settings"
└heading"Subscription"[level 3]
└button"Billing settings"
└paragraph"You're on the Team plan · $48/month · renews Dec 1"
└radiogroup"Plan"
└radio"Starter, $12 per month"[not checked]
└radio"Team, $48 per month"[checked]
└radio"Business, $120 per month"[not checked]
└switch"Email receipts"[on]
└button"Save changes"
└link"Download invoices"
└region"Danger zone"
└button"Pause subscription"
└button"Delete account"[described by: permanently deletes your workspace]

This is roughly what a screen reader announces and what an accessibility-tree agent reads: role, name, state. Red lines are where both get lost.

3 · Ask an agent to do ituses tools, verifies by reading state
  1. Pick a request. Then switch the build and ask again.
How it works: one component, three audiencesclick to collapse

Screenshots are the worst interface for an agent. Pixels carry no names, no roles, no state and no consequences. Agents that only see pixels guess.

The accessibility tree is the free upgrade. Real buttons, labels, groups and states make a page legible to screen readers and to agents at once. Same bug, same fix.

Tools are the explicit upgrade. Declaring what can be called, with typed inputs and a risk class, lets an agent act without clicking at all, and lets the host stop it at the irreversible ones.

<button
  data-ui="DangerButton"
  data-tool="delete_account()"
  data-risk="irreversible"
  aria-describedby="delete-desc"
  onClick={confirmThenDelete}
>
  Delete account
</button>

// WebMCP-style registration (illustrative; check the current draft API)
navigator.modelContext?.registerTool({
  name: "pause_subscription",
  description: "Pause billing until a date. Reversible by the user.",
  inputSchema: { type: "object",
    properties: { until: { type: "string", format: "date" } },
    required: ["until"] },
  execute: ({ until }) => pauseSubscription(until),
})