AG-UI accessibility
Map documented AG-UI runs, subagents, partial steps, responses, tools, and interactions into paced accessible workflow updates.
Install the AG-UI adapter
npm install @generative-a11y/core @generative-a11y/dom @generative-a11y/ag-uiUse the agent your app already owns. See Compatibility for supported host dependencies.
Subscribe to the public agent API
Create the binding
Add this function to your browser app. It creates a runtime and browser delivery, then subscribes to your existing agent.
import { createRuntime } from "@generative-a11y/core";
import { bindRuntime } from "@generative-a11y/dom";
import { bindAgent, type AgentSource } from "@generative-a11y/ag-ui";
export function connectAccessibility(agent: AgentSource) {
const runtime = createRuntime({});
const delivery = bindRuntime(runtime);
const binding = bindAgent({
runtime,
scopeId: "research-agent",
agent,
});
return () => {
binding.dispose();
delivery.dispose();
runtime.dispose();
};
}Connect it to your app lifecycle
Call this once when your agent is ready. Here,
agent is your existing public AG-UI agent.
import { connectAccessibility } from "./accessibility/ag-ui";
const disconnectAccessibility = connectAccessibility(agent);Keep the binding alive across responses. Call the returned function when the surface unmounts or before replacing the agent:
disconnectAccessibility();Keep one owner for each resource
The example owns its core runtime and DOM delivery. If your app already has
these, pass that runtime directly to bindAgent and clean up only its returned
binding. In React, use the runtime from A11yProvider and return
() => binding.dispose() from your effect. Do not attach a second delivery
layer to that provider.
Keep scopeId stable and unique for each mounted agent. The adapter leaves your
visible UI and actions with the host application.
Optional configuration
maxTrackedEntities defaults to 1000 and bounds adapter identity records.
copy supplies localized tool and interaction labels. See the
bindAgent reference for
all options and the localization guide for copy
configuration.
Compatibility
| Dependency | Supported range | Checked version |
|---|---|---|
| Node.js | 22+ | — |
@ag-ui/client | >=0.0.59 <0.0.60 | 0.0.59 |
Your existing app supplies the host dependency. The adapter itself needs no React rendering layer; this example uses core and DOM directly.
Lifecycle mapping
| Public subscriber evidence | Reports |
|---|---|
| Run and subagent callbacks | Run lifecycle and documented parent relationships |
| Assistant text callbacks | Response text and completion |
| Tool start and result callbacks | Tool lifecycle |
| Interrupt IDs and resume input | Input requests and resolutions |
| Step callbacks | Partial-identity diagnostics only |
Text, tool, and interaction events retain run ownership when exposed. Step
callbacks provide stepName without stable step IDs. The adapter keeps the name
as a label and omits stepId; core does not create step snapshots,
announcements, or run summary counts from these diagnostics. Same-name
concurrent steps cannot be correlated exactly.
Expected screen-reader behavior
Core paces response text and prioritizes input requests and failure states. DOM updates live regions without moving focus during normal agent activity. A DOM transcript records browser writes; test spoken behavior with real assistive technology.
What the protocol can report
Run identity is exact; step and hierarchy fidelity are partial. A top-level
parentRunId describes lineage and is not reinterpreted as delegation. Replay
and reconnection fidelity are partial because subscriptions have no mandatory
persistent cursor. Retry and connection event fidelity are unavailable. Custom
events and arbitrary state deltas require an explicit host mapping.
Troubleshooting
- Repeated updates: attach only one subscriber per agent with a stable
scopeId. - Missing interactions: check that callbacks contain stable interrupt IDs and the binding was attached before the run began.
- Updates stop in a long session: review
maxTrackedEntities. The adapter suppresses further dispatch after capacity is reached; use a new binding at a real session boundary instead of inferring missing events.