Compare Boundaries, Not Brand Names

Agent UI discussions often put A2UI, AG-UI, and an AI framework runtime in one feature table and choose a winner. That framing hides the engineering decision. A declarative UI contract, an event protocol, and a framework-owned renderer can operate at different boundaries:

Boundary Question it answers It does not provide
UI contract What constrained UI description can be rendered? Identity, authorization, business truth
Event protocol How do events, state, and lifecycle updates move? Safe rendering or effect permission
Framework runtime How does this product render and coordinate UI code? Interoperability with every client or service
Action service May this subject perform this effect on this object now? A model-selected policy

The names, message types, transports, SDKs, and support matrices associated with these ecosystems change. Pin a specification or package revision and test it in your own deployment before treating a capability as available.

json
{
  "ui_contract": "pinned-spec-or-schema-commit",
  "event_profile": "pinned-transport-profile",
  "framework_runtime": "package-and-revision",
  "renderer_catalog": "product-catalog-revision",
  "supported_clients": ["tested-client-and-version"],
  "action_api": "server-contract-revision",
  "checked_at": "recorded-time"
}

This record is more useful than a market ranking: it tells an operator what must be retested when a dependency changes.

Start with the User Task

An Agent surface is justified when it makes a user task easier to understand or complete. It is not justified simply because a model can describe components.

User task Useful interface Unsafe shortcut
Compare returned options Read-only cards with source and freshness Treating generated rank or price as authoritative
Refine a search Fixed filters with allowed values Letting the model choose arbitrary query fields
Review a draft Product-owned editor and explicit save Writing to a record from a chat response
Request an external effect Summary, disclosure, and confirmation Making a button execute a model-selected action

Use plain text for explanation. Use a fixed product flow for stable, high-impact workflows. Use a generated contract only when its constrained degrees of freedom improve the experience.

Three Implementation Styles

Declarative UI Contract

An A2UI-style approach sends a description of components and data rather than executable browser or native code. Its value comes from a product-owned renderer that maps a small, reviewed catalog to local components.

It can support an interchange boundary between independently owned Agent services and clients. It does not make a remote Agent trusted: unknown components, rich text, URLs, data references, and actions still need strict admission controls.

Event and State Protocol

An AG-UI-style approach focuses on delivery: progress events, text deltas, lifecycle transitions, user input, state updates, reconnects, and cancellation. It can carry a UI contract, text, or application-defined payload. It does not define which UI is safe to render or which tool call a receiver may execute.

Treat order, duplication, reconnect behavior, backpressure, replay, and event retention as explicit parts of the deployment contract. A streaming transport is not durable workflow state.

Framework-Owned Runtime

A React or other framework runtime can keep rendering code, server data access, and client interaction in one product codebase. That may be the simplest choice when clients and deployment are under one ownership boundary.

It also creates framework and runtime coupling. Do not expose arbitrary component generation or server functions merely because the model can select a tool. Keep the model's output constrained to product-defined intents, and keep credentials, object lookup, and execution behind application services.

Use a Decision Matrix

Choose based on evidence about your system, not a permanent “best” label:

Question Prefer a UI contract when Prefer an event protocol when Prefer a product runtime when
Independent clients/services You need a shared, versioned payload You need lifecycle interoperability One product owns all clients
Delivery behavior Payload can be fetched or updated simply Streaming, resume, and interruption matter Framework transport meets measured needs
Rendering Multiple renderers must consume one catalog Rendering remains application-defined One reviewed component system is sufficient
Upgrade ownership Schema compatibility is governed explicitly Event compatibility is governed explicitly One team can upgrade runtime and clients together
Security boundary Payload is untrusted and strictly validated Events are untrusted and session-bound Server actions remain product-owned and authorized

It is valid to use a product runtime plus a small declarative catalog. It is also valid to use an event stream without a declarative UI. Avoid adding every layer unless it reduces a real ownership or compatibility problem.

The Shared Security Model

No approach turns model output into authority. A generated component tree, event name, JSON Schema validation result, prompt instruction, or tool annotation cannot grant permission.

The receiver must:

  1. authenticate the caller and bind the surface to the current session;
  2. validate contract revision, byte size, component count, nesting, update rate, and allowed properties;
  3. escape text and constrain images, rich content, URLs, callbacks, and data references;
  4. resolve data server-side using tenant and object checks;
  5. map an action only to a reviewed application intent;
  6. reauthorize the exact action and current object state at execution;
  7. bind confirmation and idempotency to subject, tenant, action, and normalized parameters;
  8. record redacted policy and lifecycle evidence.
python
from dataclasses import dataclass

@dataclass(frozen=True)
class UiIntent:
    name: str
    action_token: str
    confirmation_id: str | None

def accept_intent(session, intent, tokens, inventory):
    if intent.name not in {"select_offer", "request_confirmation"}:
        return {"status": "rejected", "reason": "unknown_intent"}

    offer = tokens.resolve(intent.action_token, subject_id=session.subject_id)
    if offer is None or not inventory.is_available(offer.id):
        return {"status": "rejected", "reason": "stale_or_unavailable"}

    return {"status": "accepted", "offer_id": offer.public_id}

This is an application policy fragment, not an implementation of any protocol or SDK. A consequential write needs a separate, idempotent endpoint that rechecks the user, object, parameters, confirmation, and business invariants immediately before the effect.

Transport and Rendering Need Failure Semantics

An attractive demo often omits the cases users notice most: a duplicate click, a stale screen, a reconnection after an effect, or a renderer upgrade halfway through a session.

Define the following before rollout:

Concern Contract to document
Event delivery Ordering, deduplication identity, reconnect cursor, timeout, backpressure
Surface lifecycle Create, replace, expiry, cleanup, unsupported-client fallback
Data freshness Source, observation time, refresh behavior, stale display policy
Actions Confirmation, cancellation, idempotency, outcome-unknown response
Compatibility Schema/event version negotiation, migration fixtures, rollback
Privacy Redaction, retention, deletion propagation, tenant isolation

A completed render is not proof of a completed side effect. A completed event stream is not proof that all clients observed the same state.

Accessibility and Trustworthy Presentation

Generated UI is still product UI. The catalog must specify semantic roles, labels, keyboard navigation, focus after incremental updates, contrast, zoom, reduced motion, localization, pluralization, and right-to-left layout.

Protect users from UI-level prompt injection and phishing:

  • distinguish Agent-generated content from product-owned navigation and security controls;
  • show provenance and freshness for externally sourced claims;
  • reserve credential, payment, and permission controls for fixed components;
  • open external destinations only through a reviewed policy;
  • require explicit, comprehensible confirmation for destructive or externally visible actions.

These controls matter regardless of whether the payload arrived through a declarative contract, an event protocol, or a framework runtime.

Test the Choice

Adoption should end with a test plan, not an architecture diagram:

Test Failure to demonstrate
Contract fuzzing Unknown property, cyclic tree, oversized payload, unsafe URL
Event replay Duplicate event, gap after reconnect, out-of-order update
Authorization Cross-tenant token, stale object, model-supplied identifier
Effect recovery Timeout after execution, duplicate confirmation, cancellation race
Accessibility Keyboard-only path, screen-reader update, long localized text
Upgrade Older client, incompatible catalog, renderer rollback
Privacy Sensitive payload in telemetry, deletion from cached surfaces

Measure user completion, clarification requests, rejection rate, recovery time, and accessibility failures on representative tasks. Do not use a generic vendor comparison as proof that an architecture fits your users.

A2UI, AG-UI, and Framework Runtimes Can Coexist

A UI contract can travel through an event protocol. A product runtime can render a strict catalog. A remote Agent may propose a surface while a host application owns the renderer and action service. These are composition choices, not a prediction that one ecosystem will replace the others.

For the underlying UI contract boundary, see A2UI: Building Safe Agent-Driven User Interface Contracts. For service-to-service delegation, see A2A Protocol: Inter-Agent Trust and Task Boundaries. For tool and resource boundaries, see MCP Protocol: Production Boundaries for Tools and Resources.

FAQ

Can an event stream carry a declarative UI payload?

Yes, if the deployment defines the message envelope, schema revision, size limits, session binding, validation behavior, and client compatibility. The receiver must still treat the payload as untrusted and render only its approved catalog.

Is a declarative contract safer than a framework runtime?

They address different risks. A strict declarative catalog can reduce arbitrary-code execution in a payload boundary. A product-owned runtime can safely render reviewed components. Both need server-side authorization, data filtering, URL controls, observability, and recovery behavior.

Should a team standardize on one approach now?

Standardize the contracts your own product can govern: identity propagation, action authorization, audit events, accessibility, payload limits, and upgrade tests. Adopt an external protocol or runtime only after pinning its revision and proving compatibility with the clients you support.

What should be logged?

Log a redacted contract revision, surface and action identifiers, policy decision, lifecycle class, latency, and correlation ID. Do not default to storing raw prompts, UI payloads, private data, or hidden model reasoning.

Further Reading