Skip to content

feat(spec-v1.0): support mixable catalogs and component-level catalogId resolution - #2079

Merged
jacobsimionato merged 11 commits into
a2ui-project:mainfrom
jacobsimionato:spec-multi-catalog
Jul 29, 2026
Merged

feat(spec-v1.0): support mixable catalogs and component-level catalogId resolution#2079
jacobsimionato merged 11 commits into
a2ui-project:mainfrom
jacobsimionato:spec-multi-catalog

Conversation

@jacobsimionato

@jacobsimionato jacobsimionato commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Description of Changes

This PR updates the A2UI v1.0 specification, JSON schemas, and Python SDK to support mixing multiple catalogs within a single UI surface, based on the design consensus from #2020 and review feedback:

  1. Mixable Catalogs & Inline Catalog Support:

    • Clarified in renderer_capabilities.json and agent_capabilities.json that supportedCatalogIds can be mixed within a single surface.
    • Documented that the set of available catalogs for a surface includes both supportedCatalogIds and the catalogId of any inline catalog declared in inlineCatalogs (when supported by the agent).
    • Added explicit notes in a2ui_protocol.md and evolution_guide.md requiring that all catalogs mixed within a surface (at the component/function-call level or surface-default level) must conform to a unified A2UI specification version.
  2. Optional Component & Function catalogId:

    • Added an optional catalogId property (string) to ComponentCommon and FunctionCommon (allOf pattern) in common_types.json.
    • Catalog functions in catalog.json inherit FunctionCommon via allOf, allowing catalogId overrides on function calls while avoiding repetitive property definitions.
    • IndexSystemFunction (@index) is defined as a spec system function and explicitly does not inherit FunctionCommon or permit catalogId.
    • Excluded the controversial rootOnly addition.
  3. Optional Surface Default catalogId:

    • Made catalogId optional in CreateSurfaceMessage (required: ["surfaceId"]), retaining it as an optional surface-level default catalogId for components and functions that do not specify one.
    • Reinstated the requirement sentence in UpdateComponentsMessage, UpdateDataModelMessage, and DeleteSurfaceMessage descriptions stating that createSurface must have been previously sent for the surfaceId.
  4. Deterministic Component & Function Resolution Order:

    • Documented the 3-step component and function resolution logic in a2ui_protocol.md and evolution_guide.md:
      1. Component/Function-level catalogId: Checked first if explicitly specified.
      2. Surface default catalogId: Checked second if defined in createSurface.
      3. Resolution error: If neither exists, resolution fails and the component is not rendered (or function call is rejected). There is no fallback to catalogs declared in renderer capabilities.
    • Renamed legacy clientCapabilities references to rendererCapabilities in a2ui_protocol.md.
  5. Python SDK Schema Helper Fix:

    • Updated CatalogSchemaHelper in agent_sdks/python/a2ui_agent/src/a2ui/inference_formats/experimental/express/schema_helper.py to crawl allOf subschemas when looking up function parameters and argument definitions.
  6. Test Cases:

    • Added test cases in specification/v1_0/test/cases/initial_state_validation.json, call_function_message.json, and function_catalog_validation.json verifying valid component/function calls with catalogId and rejecting invalid @index calls with catalogId.

Rationale

Previously, each surface was strictly bound to a single catalog defined at surface creation time. In real-world composite application scenarios, surfaces often combine components or function calls from multiple catalogs (e.g. core UI components, domain-specific extensions, inline catalogs, and third-party tools). This change enables multi-catalog composition per surface while maintaining backward compatibility through surface default catalog fallbacks and deterministic resolution rules.

Testing / Running Instructions

  1. Run v1.0 Schema Tests:

    python3 specification/v1_0/test/run_tests.py

    (Expected result: 138 passed, 0 failed)

  2. Run Monorepo Validator:

    python3 specification/scripts/validate.py

    (Expected result: Overall Validation: PASSED)

@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 introduces support for mixing multiple catalogs within a single UI surface, updating the protocol documentation, JSON schemas, and test cases accordingly. Key changes include making catalogId optional on createSurface to act as a surface-level default, and adding an optional catalogId to components and function calls for explicit overrides. The review feedback highlights a critical JSON Schema validation bug where catalogId on FunctionCall will fail validation due to strict property evaluation rules, and suggests defining a FunctionCommon schema to resolve it. Additionally, the feedback recommends removing an outdated reference to clientCapabilities and clarifying that the 3-step resolution logic applies to both components and function calls.

Comment thread specification/v1_0/json/common_types.json
Comment thread specification/v1_0/docs/a2ui_protocol.md Outdated
Comment thread specification/v1_0/docs/a2ui_protocol.md Outdated

- `surfaceId` (string, required): The unique identifier for the UI surface to be rendered. This must be globally unique for the renderer's lifetime.
- `catalogId` (string, required): A string that uniquely identifies the catalog (components and functions) used for this surface. Note that `catalogId` is a string identifier, not a resolvable URI; while it is conventionally formatted as a URI (e.g., `https://mycompany.com/1.0/somecatalog`) to avoid naming collisions across organizations, it does not need to point to any deployed resource or downloadable file. Renderer and agent developers must agree on shared catalogs with well-known IDs in order to build systems that are compatible with each other.
- `catalogId` (string, optional): A string that uniquely identifies the default catalog (components and functions) used for this surface. Note that `catalogId` is a string identifier, not a resolvable URI; while it is conventionally formatted as a URI (e.g., `https://mycompany.com/1.0/somecatalog`) to avoid naming collisions across organizations, it does not need to point to any deployed resource or downloadable file. Components and function calls on this surface that do not explicitly specify their own `catalogId` will use this surface-level default `catalogId`.

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.

What about adding a note:

Catalog IDs defined at the component and function-call levels and surface-level catalogId must conform to a unified A2UI specification version.

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.

Added a note based on your suggestion, but with slightly different wording.


This structure is designed to be both flexible and strictly validated.

#### Mixable catalogs and component resolution logic

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.

How do we handle inlineCatalogs if specified?

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.

I updated the wording to specify that inline catalogs are also included in the catalogs that can be mixed.

- Added `actionResponse` message structure (`ActionResponseMessage`) to allow the agent to respond to a specific action call using a unique `actionId` with a `value` or `error`.
- Added `callFunction` message structure (`CallFunctionMessage`) to support agent-initiated function execution. Removed `callableFrom` and `returnType` properties from the wire payload, relying on runtime catalog verification.
- Updated the `createSurface` message (`CreateSurfaceMessage`) to rename the `theme` field to `surfaceProperties`, and allowed passing initial `components` and `dataModel` directly inside the payload.
- Updated the `createSurface` message (`CreateSurfaceMessage`) to rename the `theme` field to `surfaceProperties`, allowed passing initial `components` and `dataModel` directly inside the payload, and made `catalogId` an optional parameter that acts as the surface's default catalog.

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.

Nit: I know you didn't write this part, but Is it really a "rename"? I'd more think of it as removing theme and adding surfaceProperties: they're very different.

(Feel free to ignore, this is a tiny thing)

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.

Let me do this as a follow up.

- Set the `version` field in all streamed JSON envelopes to `"v1.0"`.
- Change the MIME type of A2UI payloads in transport layers from `application/json+a2ui` to `application/a2ui+json`.
- Rename the `theme` field in `createSurface` messages to `surfaceProperties` and remove `primaryColor`. You can also pass initial `components` and `dataModel` directly in the `createSurface` payload.
- Rename the `theme` field in `createSurface` messages to `surfaceProperties` and remove `primaryColor`. You can pass initial `components` and `dataModel` directly in the `createSurface` payload, and `catalogId` is now optional (acting as the default catalog for that surface).

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.

Nit: Yeah, could you clarify about theme being removed and surfaceProperties added here too?

(Feel free to ignore, this is a tiny thing)

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.

Let me do this as a follow up.

"supportedCatalogIds": {
"type": "array",
"description": "An array of strings, where each string is an ID identifying a Catalog Definition Schema that the agent can generate. This is not necessarily a resolvable URI.",
"description": "An array of strings, where each string is an ID identifying a Catalog Definition Schema that the agent can generate. Multiple catalogs can be mixed in a single surface.",

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.

Do we need the "resolvable URI" message still? It might matter if an agent gets the idea it can/must download it. I'd even go so far as to say "This is not a resolvable URI" or "This is only an identifying string" or some such.

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.

Agreed. I added back that sentence.

### `createSurface`

This message signals the renderer to create a new surface and begin rendering it. A surface must be created before any `updateComponents` or `updateDataModel` messages can be sent to it. While typically achieved by the agent sending a `createSurface` message, an agent may skip this if it knows the surface has already been created (e.g., by another agent). Once a surface is created, its `surfaceId` and `catalogId` are fixed; to reconfigure them, the surface must be deleted and recreated.
This message signals the renderer to create a new surface and begin rendering it. A surface must be created before any `updateComponents` or `updateDataModel` messages can be sent to it. While typically achieved by the agent sending a `createSurface` message, an agent may skip this if it knows the surface has already been created (e.g., by another agent). Once a surface is created, its `surfaceId` and default `catalogId` (if provided) are fixed; to reconfigure them, the surface must be deleted and recreated.

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.

rm this line - for security an agent has to create a surface and then its the only agent that can interact with it. there is no pre created surfaces (eg, how would orchestrator routing work?)

While typically achieved by the agent sending a createSurface message, an agent may skip this if it knows the surface has already been created (e.g., by another agent)

@jacobsimionato jacobsimionato Jul 29, 2026

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.

Well, this is true for the chat-based superapp use case where there are multiple agents which don't trust each other and therefore manage their own surfaces.

But there are other use cases where surfaces can be preexisting, especially non-chat canvas or dashboard use cases. The client may have a single surface with a hardcoded surfaceId that the agent continually updates. Or one agent may create a surface which another trusted agent then modifies.

I updated the wording based on your feedback to suggest that agents can only modify existing surfaces if they have access rights to them.

@wrenj

wrenj commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

not for this PR, but we should start building a list of standard errors in client_to_server_schema

@jacobsimionato
jacobsimionato enabled auto-merge (squash) July 29, 2026 05:11
@jacobsimionato
jacobsimionato merged commit 706ed4d into a2ui-project:main Jul 29, 2026
25 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in A2UI Jul 29, 2026
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.

4 participants