Skip to content

feat(spec): add core A2UI templates specification, version 0.1 schema, and examples - #2380

Open
jacobsimionato wants to merge 5 commits into
a2ui-project:mainfrom
jacobsimionato:feat/templates-spec
Open

feat(spec): add core A2UI templates specification, version 0.1 schema, and examples#2380
jacobsimionato wants to merge 5 commits into
a2ui-project:mainfrom
jacobsimionato:feat/templates-spec

Conversation

@jacobsimionato

Copy link
Copy Markdown
Collaborator

Summary

This PR introduces the authoritative A2UI Templates Specification, formal JSON Schema, and multi-document YAML test fixtures under specification/proposals/templates/.

Templates provide a declarative, human-readable mechanism for authoring reusable UI subtrees in nested YAML. At generation time, an LLM emits compact, high-level template signatures (such as UserProfile("u1", "Alice")), which the backend expands into canonical Basic Catalog primitives in a single synchronous pass before emitting standard A2UI protocol messages (createSurface, updateComponents) to the client.


Architectural Highlights

  • Zero Client Overhead: Client renderers (@a2ui/react, @a2ui/lit, @a2ui/angular, @a2ui/flutter) require no custom widget code or dynamic plugins. They continue rendering 100% standard Basic Catalog primitives.
  • Context & Token Efficiency: Agents output concise function-like signatures rather than deeply nested component trees.
  • Deterministic Synthetic ID Generation: Implements canonical ID synthesis ({parent}_{slot}_{index}_{type}) guaranteeing zero ID collisions when multiple template instances are placed on a single surface.
  • Loop Expansion Boundaries: Differentiates server-side synchronous unrolling (for literal parameter arrays) from client-side reactive loops bound to the client DataModel.
  • Two-Way DataModel Interoperability: Preserves dynamic data binding paths ({path: "/user/name"}) and path prefix plumbing.
  • Strict Template Versioning (0.1): Enforces top-level version: "0.1" across all template definitions to support smooth schema evolution across future protocol releases.
  • Safety & Recursion Guards: Formal call-stack tracking algorithm (TemplateCycleError) and MAX_EXPANSION_DEPTH = 32 recursion limits.

Files Added

  • specification/proposals/templates/README.md: Complete, authoritative specification with pipeline diagrams, grammar rules, and multi-language conformance checklist.
  • specification/proposals/templates/schema/template_definition.json: Canonical JSON Schema (Draft 2020-12) enforcing required version: "0.1", templateId, parameters, and nested layout.
  • specification/proposals/templates/examples/*.yaml: 11 multi-document YAML template fixtures and test suites (basic_cards, nested_lists, slot_composition, user_profile, salary_card, team_roster, etc.).

Stack Context

  • PR 1 of 4: Core Specification, Schema & Test Fixtures (This PR)
  • PR 2 of 4: Python Agent SDK Template Engine Implementation
  • PR 3 of 4: Community Templates Sample App & Interactive Studio
  • PR 4 of 4: Future Proposals (Client-Expanded Templates & Python Fluent Builders)

@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 the A2UI Templates Specification, including a comprehensive markdown specification, a JSON schema for template definitions, and multiple YAML examples demonstrating template usage. The review feedback highlights several critical issues: the JSON schema needs to enforce that either template or item is provided in LoopDefinition, and it lacks a mechanism to declare required parameters. Additionally, there are duplicate templateId registrations across example files, type mismatches in section_card.yaml and two_column_layout.yaml, and the term normalized_type in the ID synthesis specification requires explicit definition in the README.

Comment thread specification/proposals/templates/schema/template_definition.json
Comment thread specification/proposals/templates/schema/template_definition.json
Comment thread specification/proposals/templates/examples/section_card.yaml Outdated
Comment thread specification/proposals/templates/examples/slot_composition.yaml Outdated
Comment thread specification/proposals/templates/examples/two_column_layout.yaml Outdated
Comment thread specification/proposals/templates/README.md
@jacobsimionato

Copy link
Copy Markdown
Collaborator Author

Thank you for the detailed feedback! All comments have been addressed in the latest commit:

  1. LoopDefinition: Added anyOf: [{"required": ["template"]}, {"required": ["item"]}] to ensure loops explicitly specify what component layout to instantiate. Also constrained children in NestedTemplateNode to prevent generic object matching.
  2. Required Parameters: Added top-level requiredParameters array and supported required boolean in ParameterDefinition to declare required parameters.
  3. headerAction Type: Changed headerAction from type: action to type: child in section_card.yaml to match its usage in container children.
  4. Disambiguated Template IDs: Renamed template IDs in multi-doc bundles (BasicUserProfile, BasicMetricCard, CompositeSectionCard, TwoColumnLayoutWithHeader, NestedTeamRoster, NestedTeamCard) to ensure zero naming collisions across all example files.
  5. headerChild Sample Data: Updated two_column_layout.yaml sampleData from "" to a valid component node {"component": "Text", "text": "Header Title"}.
  6. normalized_type Clarification: Explicitly clarified in README.md Section 7 that normalized_type refers to lowercase component/template names.

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.

Would it make sense for this to be a YAML schema instead of a JSON schema?

I mean, given that the format is in YAML, that makes sense to me.

e.g. https://yaml-schema.net/

I'm not sure it actually matters much, and I think the end result is the same, so maybe it makes sense to keep all the schema in one form. It was just a thought.

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 read that people normally use JSON schema to represent schemas for YAML files, so I just stuck with that. I don't feel strongly though - we can change this at any time.

From search:
"The most widely used method to validate YAML files (such as GitHub Actions, Kubernetes manifests, or custom configs) is to write a standard JSON Schema but format it as a YAML file"

The template engine operates entirely server-side within the A2UI Agent SDK:

```
+-------------------------------------------------------------------------------+

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.

This could easily be a Mermaid diagram and be a lot nicer.

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.

Updated the execution pipeline ASCII box diagram to a clean Mermaid flowchart diagram!

@@ -0,0 +1,582 @@
# A2UI Templates Specification

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: Should it maybe be "A2UI Template Specification" (singular)?

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.

Updated the title to singular: # A2UI Template Specification.


## Abstract

This document defines the authoritative specification for the **A2UI Templates System**.

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.

Is it a System or a Specification?

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.

Clarified phrasing to: "This document defines the authoritative specification for A2UI Templates."


This document defines the authoritative specification for the **A2UI Templates System**.

Templates provide a declarative, human-readable mechanism for authoring reusable UI subtrees in nested YAML or via native programmatic render functions. At generation time, an agent or Large Language Model (LLM) outputs compact, high-level template invocations (such as `UserProfile("u1", "Alice")` or `PayrollSummary("Eng", true)`). The backend SDK expands these invocations into standard, flat A2UI Basic Catalog components in a single synchronous pass before emitting standard A2UI protocol messages (`updateComponents`) to the client.

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.

The examples are all "programmatic render functions", so maybe we don't need the "nested YAML" case in the first sentence? Besides, seems weird to next YAML inside of A2UI (or maybe that's not what that means)?

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 actually a little confused about this reading the doc: it seems like the "nested YAML" part is the template definitions, while the "programmatic render functions" is the instantiations. This description makes it sound like they are interchangable.

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.

Re whether we need YAML at all, I think the main use case is to provide a smooth workflow from composer app to code, which people seem to like. E.g. you get the composer to create some YAML, then any SDK can use it. If we only allow the programmatic option, then the composer needs to output code, which is more of a pain, and ideally it should also be able to import code (to preview / modify existing layouts) which is probably too painful for us to pursue.

Let's discuss whether YAML is worthwhile tomorrow. I think @yjbanov maybe agrees with you that we should focus on programmatic.

Re nesting YAML inside A2UI. We're not doing that - the template process parses the YAML and then outputs pure JSON. The reason to use nested YAML here is because it's a better authoring experience for humans. Most of the time people author A2UI content and have a bad time, they should actually be authoring templates. Now we are designing templates, let's make it a human-friendly format (whether data or programmatic API).


## 2. Template Taxonomy

A2UI defines three template modalities:

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.

Modalities feels like the wrong word. To me that implies doing the same thing in different ways. This seems like they are three different parts that serve different purposes.

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.

Changed "modalities" to "authoring styles" (Static YAML Templates, Dynamic Data-Binding Templates, and Dynamic Programmatic Render Functions).

3. The synthetic catalog is passed to `PromptGenerator` (Express, Elemental, Atom, Direct JSON), which automatically writes the syntax rules and documentation into the system prompt.

### Surface Isolation Boundary
```

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.

This could be a Mermaid diagram too.

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.

Replaced the isolation boundary ASCII art with a clean Mermaid flowchart diagram!

### Rule 2: Embedded String Interpolation
If a parameter token appears alongside other characters (e.g. `"Hello, ${userName}!"` or `"${dept} - ${code}"`):
- All tokens are replaced by their string representations.
- Evaluates to a native string.

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.

Hmm. But what about the reactive UI client side of things? What if I want to use the formatString function to put a data model item into the string and have it be reactive if the data model item changes? Can I escape the string interpolation so that is possible?

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.

That is a great observation and highlights an inherent tension: in A2UI catalogs, the client runtime already uses ${/path} for reactive data model interpolation inside formatString(...). Using ${param} for server-side template expansion means any client-side expressions in template layouts would either need escaping (\${/path}) or risk colliding.

We plan to transition template substitution to standard double curly braces {{ param }} (like Jinja/Mustache/Helm). That cleanly separates the two scopes without requiring escaping:

  • {{ param }} is evaluated server-side at template expansion time.
  • ${/path} is untouched by the template engine and passes through for client-side reactive evaluation (e.g. formatString("Hello ${/user/name}, dept: {{ dept }}")).

For now, \${...} escapes any literal ${...} so it passes through to the client intact.

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.

Update: We have gone ahead and implemented this across the specification and all template examples! Double-curly Mustache syntax ({{ param }}) is now the canonical format for template parameter substitutions (with optional whitespace, e.g. {{ param }} or {{param}}).

This cleanly disambiguates compile-time template substitutions from client-side reactive expressions (${/data/path} and catalog functions like formatString(...)), ensuring client expressions pass through untouched without requiring any escaping.

## 9. Error Handling, Cycle Guards & Safety

### Recursion & Circular Reference Detection
Templates can invoke other templates. However, circular references (`A` invokes `B`, which invokes `A`) must be caught immediately to prevent stack overflow.

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 nesting? If I have a "Card" with a "Carousel" inside of it, containing other "Card"s, I would think that should be allowed, since they're different instances of the Card template.

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.

Clarified this in Section 11! The cycle guard specifically prevents definition-level circular references (where a template's own layout unconditionally references itself in a cycle, causing infinite expansion).

In contrast, instance nesting—where a caller passes a Card instance into a slot of another Card instance (e.g. Card(child=Carousel(items=[Card(...)])))—is fully supported and encouraged, as each instance has a unique synthetic ID and is bounded by caller input.


- **Call Stack Tracking**: The `TemplateProcessor` maintains an active `_call_stack: Set[str]` throughout expansion.
- **Cycle Guard**: Before expanding any template, if `template_id in _call_stack`, expansion terminates immediately with `TemplateCycleError: Circular template reference detected: A -> B -> A`.
- **Maximum Depth Guard**: An absolute limit (`MAX_EXPANSION_DEPTH = 32`) enforces termination even in non-identical runaway recursion.

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.

We should standardize on the max depth defaults. I think in other cases, we're using 50.

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.

Updated MAX_EXPANSION_DEPTH default from 32 to 50 to standardize across A2UI!

@jacobsimionato

Copy link
Copy Markdown
Collaborator Author

I have addressed the feedback. Please take another look.

},
"additionalProperties": false
},
{"type": "array"},

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.

Should we limit this to be an array of NestedTemplateNode, rather than arbitrary array?

Templates decouple their local invocation name from their optional global identity:

- **`name`** (Required): Component tag name used in layout trees and LLM prompts (`^[A-Za-z][A-Za-z0-9_]*$`). `templateId` is accepted as a backwards-compatible alias.
- **`id`** (Optional): Globally unique URI or URN string (e.g. `https://company.org/templates/card/v1.json`) for package distribution and version pinning.

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 enforce or validate the global uniqueness of template IDs? Is there a plan for a centralized template registry for discovery and resolution?


- **`name`** (Required): Component tag name used in layout trees and LLM prompts (`^[A-Za-z][A-Za-z0-9_]*$`). `templateId` is accepted as a backwards-compatible alias.
- **`id`** (Optional): Globally unique URI or URN string (e.g. `https://company.org/templates/card/v1.json`) for package distribution and version pinning.
- **`imports`** (Optional): List of global template IDs, or dictionary mapping local aliases to global template IDs (`imports: { VendorCard: "https://vendor.com/card.json" }`).

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 allow remote network fetching at runtime, or must all imported template IDs be pre-loaded into a local registry ahead of time?

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