A2UI 1.0 Express Delivery

·
A2UIGenerative UILocal LLMsGemma 4A2UI Express
Reviewed by

A2UI has released 1.0, which drastically increases support for local models. First, we want to congratulate Google, CopilotKit, and the whole community on this release.

For those new to this protocol, here's a quick refresher. As with every other protocol, we need to tell the agent, via a skill or system prompt, how it's defined and how to construct user interfaces.

The protocol sends JSON messages that describe the UI. In the streamed variant, we get JSON Lines (JSONL). To render something, we need to create a surface first with the createSurface event, which has a surfaceId. After that event, we can send updateComponents events to update a specific surface and add components like a button to the surface. These components are defined in catalogs like this official one. It's a JSON schema that defines their properties as well as callable functions via @call.

The changes made in version 1.0 are production improvements. To give you an idea, we will list a few:

  • createSurface Now supports components in the same payload, which improves latency.
  • catalogId Can be set per component or function call.
  • Data bindings and function calls are now recognizable as distinct from ordinary text (@path, @call, @index).
  • Now we have composition rules with allowedParents, allowedChildren, and an instructions field in the catalogs.

This list isn't complete, but it shows the direction the team took. For us, the biggest impact is "A2UI Express." It's a DSL (Domain-Specific Language) that solves several issues we have with A2UI right now. Several months ago, we wrote an article about the two biggest issues for our use case:

  • Context size: the A2UI v0.9 basic catalog consumed about 8,000 tokens in the context.
  • JSON generation doesn't work well on local models and often generates errors. LLMs make errors with complex nested schemas, even 26B models.

A2UI Express is a compiler with Python-like syntax. This is code mode for A2UI. Another benefit is real token reduction, since we don't need the JSON overhead. The other benefit is compiler errors.

But before diving into compiler errors, take a look at a simple card:

<a2ui>title = Title("Signal & Sound — “The Quiet Hour”")episode_info = Column([    Title("Episode 42", size="xl"),    Title("Duration 38 minutes", size="lg"),    Caption("Host: Anaïs Chen", size="md", color="primary")], gap=4)schedule = Text("New episodes every Friday.")root = Card(children=[title, episode_info, schedule], gap=12)</a2ui>

CleanShot 2026-11-03 at 14.32.31

This A2UI Express program is generated by a fine-tuned "Gemma 4 E2B" model, which you can try online. The compiler compiles the Express code back into a single A2UI definition (createSurface with all components). That means the UI still gets JSON, and we don't need to change the renderer. Kudos for that. With the format unchanged, we will see improvements in newly trained models.

A2UI Express grammar is line-oriented so that the host can build the component tree line by line. Here is another example where the LLM generated the code differently:

<a2ui>heading = Title("Book Delivery")price_atlas = Text("Atlas $31.75.")price_notebook = Text("Notebook $9.20.")price_shipping = Text("Shipping $4.05.")price_total = Text("Total $45.00.")name = TextField("Full name", $/name)email = TextField("Contact email", $/email)street = TextField("Street", $/street)city = TextField("City", $/city)postcode = TextField("Postcode", $/postcode)country = Select("Country", ["Austria", "Germany"], $/country)delivery = Select("Delivery", ["Standard", "Express"], $/delivery)notes = TextArea("Order notes", $/notes)review = Button("Review order", submit=true)form = Form(children=[name, email, street, city, postcode, country, delivery, notes, review], onSubmit=Event("submit_order", {name: $/name, email: $/email, street: $/street, city: $/city, postcode: $/postcode, country: $/country, delivery: $/delivery, notes: $/notes}))root = Card(children=[heading, form])</a2ui>

Here our LLM created Express code with data binding ($/). These variables are then reused in the submit event and can be passed to the function.

In A2UI Express, our code sits between <a2ui> and </a2ui>, separating the UI from any chat text. The reserved variable root is the entry point of the tree.

We could explain how the compiler works, from line parsing to the ANTLR grammar, but a more important topic is error recovery. The A2UI Express language is still not that easy for an LLM. It can be nested (like Jetpack Compose or SwiftUI), as we showed in the first example, so the small local models still won't get it correct on the first run.

Here, the team has created an error recovery process, a so-called refinement loop:

  1. The compiler detects the line with the syntax error and removes the subtree from the AST (Abstract Syntax Tree).
  2. Based on that error, we create a correction prompt with detailed compiler information.
  3. Next, we verify whether the code is fixed. Otherwise, we need to restart the process with the new error.
  4. When we have valid Express code, we swap it back so the final rendering is correct.

This error-recovery loop gives us a chance to take corrective action.

If you'd like to explore A2UI, AG-UI, and agentic applications in more depth, we offer dedicated hands-on workshops and consulting services covering the latest developments.

Public workshop · 9–10 December 2026

Agentic Applications

Early Bird

€990

Until 22 November 2026, 23:59 (Vienna time)

Per person · Plus applicable VAT

2 days · Live onlineExplore the workshop

Using the A2UI Agent SDK (TypeScript)

We can use the @a2ui/agent package, which is published at version 0.1.0 at the time of writing. The SDK supports both JSON and Express formats.

Here is a minimal example:

import {  A2uiGenerator,  ExpressFormatFactory,  InMemoryCatalogProvider,} from '@a2ui/agent';const componentCatalog = CatalogConfig.fromPath('path/to/catalog.json');const generator = new A2uiGenerator({  catalogProvider: new InMemoryCatalogProvider([componentCatalog]),  formatFactory: new ExpressFormatFactory(),});const processor = generator.createProcessor(rendererCapabilities); const systemPrompt = `${instructions}\n\n${processor.promptSnippet}`;const modelReply = await callModel(systemPrompt, request.userMessage);const parts = processor.parseResponse(modelReply); // TextPart | A2uiPart, validated

componentCatalog is our JSON schema definition for a custom catalog. Using it, we create an A2uiGenerator with the Express format. With the generator, we can create a processor that contains a prompt snippet the LLM needs to understand the Express language (in-context learning). After the model returns the answer, we parse the A2uiPart.

In our earlier benchmarks, many LLMs failed because of incorrect wrapper tags and other issues. These issues should be addressed by using the single <a2ui> wrapper.

Conclusion

The A2UI team has addressed many past issues and taken it a step further by implementing code mode for A2UI with their own DSL. As JSON is still the target output format, all existing code and renderers still work.

We have created an online sample that uses a Gemma 4 E2B model for this. Use the fine-tuned variant. The sample runs fully in your browser and shows what's possible and what still needs some work.

CleanShot 2026-11-03 at 14.36.39

Stay Updated

Get new essays and workshop announcements in your inbox.

Choose your newsletters

Choose either or both. Unsubscribe at any time. Privacy · Manage subscriptions

Want to learn more? Check out our hands-on workshops.

Browse Workshops

Comments

No comments yet. Be the first.