Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the A2UI protocol to support mixable catalogs, allowing components to specify an optional catalogId to resolve name collisions. It makes catalogId optional in createSurface and removes the requirement for it in subsequent surface-related messages. Documentation in a2ui_protocol.md and evolution_guide.md has been updated to reflect these changes. The reviewer suggested further clarifying the component resolution algorithm and ensuring consistency in the documentation regarding runtime execution boundaries.
| Clients can support components from multiple catalogs simultaneously (mixable catalogs). When a client declares `supportedCatalogIds`, it means components from any of those catalogs can be used. In the event of a component name collision (e.g., two catalogs define a `Button` component), the renderer uses the following resolution order: | ||
|
|
||
| 1. The component's `catalogId`, if provided. | ||
| 2. The `createSurface` message's `catalogId`, if provided. | ||
| 3. If neither resolves the collision unambiguously, the renderer returns an error (ambiguous catalog component). |
There was a problem hiding this comment.
The current description of component resolution logic is focused on collisions but is a bit ambiguous about non-collision scenarios and error conditions. This could lead to inconsistent implementations.
To make the specification clearer and more robust, I suggest expanding this section to describe the full resolution algorithm explicitly. Additionally, please ensure the documentation clarifies that the client determines a function's execution boundary (e.g., clientOnly status) at runtime by reading its configuration from the active catalog definition.
Here is a suggested replacement for lines 438-442:
Clients can support components from multiple catalogs simultaneously. When a component needs to be rendered, its type (e.g., "Button") is resolved to a specific catalog definition using the following priority order:
-
Component-level
catalogId: If the component object itself specifies acatalogId, the renderer MUST look for the component type exclusively within that catalog. If the component type is not found in that catalog, it is an error. -
Surface-level
catalogId: If the component does not have acatalogId, but thecreateSurfacemessage provided a defaultcatalogIdfor the surface, the renderer MUST look for the component type exclusively within that default catalog. If not found, it is an error. -
Automatic Resolution: If neither the component nor the surface specifies a
catalogId, the renderer searches for the component type across all catalogs listed insupportedCatalogIds.- If the component type is found in exactly one supported catalog, it is resolved unambiguously.
- If the component type is found in more than one supported catalog, this is a name collision, and the renderer MUST return an "ambiguous catalog component" error.
- If the component type is not found in any supported catalog, it is an "unknown component" error.
In all cases, any catalogId specified on a component or surface must be one of the IDs declared in the client's supportedCatalogIds. Furthermore, the client determines a function's execution boundary (e.g., clientOnly status) at runtime by reading its configuration from the active catalog definition.
References
- In A2UI protocol documentation and implementation, clarify that the client determines a function's execution boundary (e.g.,
clientOnlystatus) at runtime by reading its configuration from the active catalog definition.
There was a problem hiding this comment.
I think we should never do automatic resolution, because it means that an agent needs to know the exact set of catalogs that the client supports in order to know whether an explicit catalogId is required. When the client's set of catalogs expands, this can create new collisions that break existing agents and cached content unexpectedly.
E.g.
- Client supports standard catalog with "heading" component
- Agent is developed which uses "heading" with no catalog ID specified.
- User uses agent, and content is cached at orchestrator level
- New client version released which now supports "advanced_catalog" with colliding "heading" component
- Existing cached content can no-longer be rendered because the "heading" component reference is now ambiguous
- New requests to the existing agent now also return ambiguous content, because the agent developer did not know that the client added support for an additional "heading" component.
As time goes on, I expect collisions to be frequent, because as soon as we run out of options to expand material catalog in a backwards-compatible way, we may want to introduce a new version of it "material_2" catlaog, which will certainly want to have colliding component names.
There was a problem hiding this comment.
sg, updated to require catalogId on component, and remove it from createSurface
There was a problem hiding this comment.
In the long term, I definitely prefer what you have now, e.g. each Component has it's own catalogId baked in. It's a simple mental model that way - no tiered resolution logic. So I'm happy with this as-is!
My main fear is that requiring catalog ID on each component causes an immediate regression in inference performance for any people doing Gen UI with the JSON format who migrate to v1.0 protocol. Though, realistically, most people are not doing request-time Gen UI, and those that are can either stick with v0.9, or migrate to v1.0 + Express format. WDYT @gspencergoog ?
In my comment above, I actually was actually arguing that it's okay to keep catalogId in createSurface, but that if there is no catalogId in either createSurface or on the specific component, then we should error out rather than automatically picking from a catalog, which the original text implied.
There was a problem hiding this comment.
Why can't catalogId be optional on createSurface, where if it is specified it becomes the default for everything that doesn't specify it. Then those doing inference with the JSON format can optimize if they have only one catalog by specifying the "default" catalog id on the createSurface, and individual catalog ids on things that aren't the default. It even seems sensible as an optimization for the transport protocol, since then you can reduce the payload size (although I guess in practice gzip will do that for you).
And yeah, if you don't specify one on createSurface or on the component, then it's an error because it's not defined by the time you need it.
| - The `@index` built-in function dynamically retrieves iteration indices during list template rendering. The `@` prefix is reserved for core system context evaluations. | ||
| - Catalogs may now use `$ref` to reference other catalogs. This removes the requirement that catalogs be freestanding / have no external references except to core libraries. | ||
| - The `supportedCatalogIds` in `a2uiClientCapabilities` are all mixable, meaning a client can support components from multiple catalogs simultaneously. | ||
| - Components have an optional `catalogId`, and the `catalogId` property on `createSurface` is now optional. In the event of a component name collision, the renderer looks at the component's `catalogId` to resolve it, then falls back to the `createSurface`'s `catalogId`, and if unresolved, returns an error for ambiguous catalog component. |
There was a problem hiding this comment.
This summary of the resolution logic is good, but it's slightly imprecise and could be inconsistent if the more detailed explanation in a2ui_protocol.md is updated for clarity. To ensure consistency, I recommend updating this summary to better reflect the full resolution process.
| - Components have an optional `catalogId`, and the `catalogId` property on `createSurface` is now optional. In the event of a component name collision, the renderer looks at the component's `catalogId` to resolve it, then falls back to the `createSurface`'s `catalogId`, and if unresolved, returns an error for ambiguous catalog component. | |
| - Components can have an optional `catalogId` to override the surface's default. The `catalogId` on `createSurface` is also now optional. Component types are resolved by checking the component's `catalogId`, then the surface's `catalogId`, and finally by searching all supported catalogs. Name collisions across multiple catalogs result in an error if not explicitly resolved. Execution boundaries (e.g., `clientOnly`) are determined at runtime via the active catalog definition. |
References
- In A2UI protocol documentation and implementation, clarify that the client determines a function's execution boundary (e.g.,
clientOnlystatus) at runtime by reading its configuration from the active catalog definition.
| - `id` (`ComponentId`, required): A unique string that identifies this specific component instance. This is used for parent-child references. | ||
| - `component` (string, required): Specifies the component's type (e.g., `"Text"`). | ||
| - `catalogId` (string, optional): A string that uniquely identifies the catalog for this component, overriding the surface's default catalog. Useful when combining components from multiple catalogs. | ||
| - `rootOnly` (boolean, optional, default `false`): If true, this component must be the top-level element and cannot be placed inside a `ChildList`. Top-level root components (e.g., a `Canvas`) will generally have this set to `true`. |
There was a problem hiding this comment.
To me, this seems to be leaking the details of specific catalogs into the core specification, so I'd prefer to not add this. Ultimately, rootOnly is something that a catalog can describe, and the most valuable place to enforce it is on the agent (allows for retries) or on the orchestrator (defend against malformed content). Agent developers and orchestrators can implement this validation in code on their side if they want as an extension to A2UI.
There was a problem hiding this comment.
It ended up here because its in common_types, since thats where we define Component and ChildList. I'm fine removing these details from the a2ui_protocol.md spec if its too wordy here.
given the change (we mix all the components in catalogs together) we need some mechanism to say don't mix these catalogs/components. eg, gemini consumer has action cards, they add material3 catalog but don't want action cards nested in there.
why first class support?
- its managing interactions between catalogs
- there is no way to describe this in json schema (it won't look up component ids)
- it should be generally useful for removing theme/createSurface properties, since we can cleanly say here is a top level component for storing whatever properties
| Clients can support components from multiple catalogs simultaneously (mixable catalogs). When a client declares `supportedCatalogIds`, it means components from any of those catalogs can be used. In the event of a component name collision (e.g., two catalogs define a `Button` component), the renderer uses the following resolution order: | ||
|
|
||
| 1. The component's `catalogId`, if provided. | ||
| 2. The `createSurface` message's `catalogId`, if provided. | ||
| 3. If neither resolves the collision unambiguously, the renderer returns an error (ambiguous catalog component). |
There was a problem hiding this comment.
I think we should never do automatic resolution, because it means that an agent needs to know the exact set of catalogs that the client supports in order to know whether an explicit catalogId is required. When the client's set of catalogs expands, this can create new collisions that break existing agents and cached content unexpectedly.
E.g.
- Client supports standard catalog with "heading" component
- Agent is developed which uses "heading" with no catalog ID specified.
- User uses agent, and content is cached at orchestrator level
- New client version released which now supports "advanced_catalog" with colliding "heading" component
- Existing cached content can no-longer be rendered because the "heading" component reference is now ambiguous
- New requests to the existing agent now also return ambiguous content, because the agent developer did not know that the client added support for an additional "heading" component.
As time goes on, I expect collisions to be frequent, because as soon as we run out of options to expand material catalog in a backwards-compatible way, we may want to introduce a new version of it "material_2" catlaog, which will certainly want to have colliding component names.
| - Updated the `createSurface` message (`CreateSurfaceMessage`) to rename the `theme` field to `surfaceProperties`, allowed passing initial `components` and `dataModel` directly inside the payload, and removed `catalogId`. | ||
| - Added a required `catalogId` to the base component definition (`ComponentCommon`) to cleanly isolate component namespaces. | ||
| - Added a required `catalogId` to the `FunctionCall` definition so functions can be unambiguously resolved across catalogs. | ||
| - Added a `rootOnly` boolean (default: false) to `ComponentCommon` to indicate if a component must be the top-level element. This allows restricting top-level components (like a Canvas) from being nested inside a `ChildList`. |
There was a problem hiding this comment.
| - Added a `rootOnly` boolean (default: false) to `ComponentCommon` to indicate if a component must be the top-level element. This allows restricting top-level components (like a Canvas) from being nested inside a `ChildList`. | |
| - Added a `rootOnly` boolean (default: false) to `ComponentCommon` to indicate if a component must be the top-level element. This allows restricting top-level components (like a `Canvas`) from being nested inside a `ChildList`. |
| - Updated the `createSurface` message (`CreateSurfaceMessage`) to rename the `theme` field to `surfaceProperties`, allowed passing initial `components` and `dataModel` directly inside the payload, and removed `catalogId`. | ||
| - Added a required `catalogId` to the base component definition (`ComponentCommon`) to cleanly isolate component namespaces. | ||
| - Added a required `catalogId` to the `FunctionCall` definition so functions can be unambiguously resolved across catalogs. | ||
| - Added a `rootOnly` boolean (default: false) to `ComponentCommon` to indicate if a component must be the top-level element. This allows restricting top-level components (like a Canvas) from being nested inside a `ChildList`. |
There was a problem hiding this comment.
I'm not sure I understand how rootOnly works. Is it just saying "This component must have the component id 'root'"? If that's what it means, I find that a little weird, and it seems redundant. Can you explain what the motivation for it is?
Summary of Changes
This pull request updates the A2UI protocol to support the simultaneous use of multiple catalogs within a single UI surface. By requiring an explicit
catalogIdon individual components and function calls, the change eliminates potential name collisions and removes the need for surface-level catalog definitions. These modifications improve the scalability of the protocol, allowing for more complex, multi-catalog UI implementations while maintaining strict component resolution.Highlights
catalogIdproperty for all components and function calls to ensure unambiguous resolution across mixed catalogs.catalogIdproperty from thecreateSurfacemessage, shifting the responsibility of catalog identification to individual components and function calls.rootOnlyboolean property to component definitions to restrict specific components to top-level placement.