Skip to content

Allow mixing of catalogs, and use catalogId to resolve name conflicts#2020

Open
wrenj wants to merge 6 commits into
mainfrom
namespac
Open

Allow mixing of catalogs, and use catalogId to resolve name conflicts#2020
wrenj wants to merge 6 commits into
mainfrom
namespac

Conversation

@wrenj

@wrenj wrenj commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

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 catalogId on 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

  • Catalog Mixing: Enabled the mixing of multiple catalogs within a single surface, allowing for more flexible UI composition.
  • Explicit Catalog Resolution: Introduced a mandatory catalogId property for all components and function calls to ensure unambiguous resolution across mixed catalogs.
  • API Simplification: Removed the catalogId property from the createSurface message, shifting the responsibility of catalog identification to individual components and function calls.
  • Component Constraints: Added a rootOnly boolean property to component definitions to restrict specific components to top-level placement.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +438 to +442
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).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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:

  1. Component-level catalogId: If the component object itself specifies a catalogId, 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.

  2. Surface-level catalogId: If the component does not have a catalogId, but the createSurface message provided a default catalogId for the surface, the renderer MUST look for the component type exclusively within that default catalog. If not found, it is an error.

  3. Automatic Resolution: If neither the component nor the surface specifies a catalogId, the renderer searches for the component type across all catalogs listed in supportedCatalogIds.

    • 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
  1. In A2UI protocol documentation and implementation, clarify that the client determines a function's execution boundary (e.g., clientOnly status) at runtime by reading its configuration from the active catalog definition.

@jacobsimionato jacobsimionato Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

  1. Client supports standard catalog with "heading" component
  2. Agent is developed which uses "heading" with no catalog ID specified.
  3. User uses agent, and content is cached at orchestrator level
  4. New client version released which now supports "advanced_catalog" with colliding "heading" component
  5. Existing cached content can no-longer be rendered because the "heading" component reference is now ambiguous
  6. 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sg, updated to require catalogId on component, and remove it from createSurface

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
- 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
  1. In A2UI protocol documentation and implementation, clarify that the client determines a function's execution boundary (e.g., clientOnly status) 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`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +438 to +442
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).

@jacobsimionato jacobsimionato Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

  1. Client supports standard catalog with "heading" component
  2. Agent is developed which uses "heading" with no catalog ID specified.
  3. User uses agent, and content is cached at orchestrator level
  4. New client version released which now supports "advanced_catalog" with colliding "heading" component
  5. Existing cached content can no-longer be rendered because the "heading" component reference is now ambiguous
  6. 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.

Comment thread specification/v1_0/docs/a2ui_protocol.md Outdated
@a2ui-project a2ui-project deleted a comment from gemini-code-assist Bot Jul 20, 2026
- 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`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- 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`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants