DEV Community

Cover image for A2UI x AG-UI: Building an AI Agent that Supports A2UI (v0.9) with CopilotKit and Strands Agents

A2UI x AG-UI: Building an AI Agent that Supports A2UI (v0.9) with CopilotKit and Strands Agents

Original Articles:
https://qiita.com/Takenoko4594/items/dab65ccac9038fa2ad6b

Most chat-based AI agents can only reply with text. But what if your agent could search for tech meetups and show you the results as a list of clickable cards instead of a wall of text?

That's exactly what I tried to build, using CopilotKit together with Strands Agents, AWS's SDK for building AI agents, and a spec called A2UI that lets an agent control the frontend UI.

If you've heard of "Generative UI" but aren't sure how an agent actually gets a button or a card to show up on screen, this post is for you.

Code is here:

https://github.com/KMiya84377/copilotkit_a2ui_strands_app

What is A2UI?

A2UI is one spec for achieving Generative UI

Generative UI is the idea that an AI can dynamically generate the user interface itself — not just text, but interactive buttons, cards, graphs, and more, depending on the situation.

A2UI, provided by Google, is one of the specs that makes this possible.

A2UI represents UI as JSON

So how does A2UI actually pull this off?

Let's think about it from the perspective of the frontend and the AI agent.

A2UI defines the UI not as source code, but as JSON.

The AI agent receives a prompt from the frontend and returns the UI to display as JSON. The frontend then renders the screen based on that JSON.

The JSON is made up of three elements, combined together to build the UI:

Element Description
Surface A canvas-like area where the UI is drawn
Component A UI element placed on the Surface — buttons, text fields, cards, etc.
Data Model The state of the UI/application — e.g. the current value of a text field

A2UI prepares a blank canvas called a Surface, places Components on it, and binds the data to display to those Components via the Data Model.

In other words, A2UI is a mechanism where the AI generates the JSON "blueprint" of the UI, and the frontend renders the UI from that blueprint.

The AI agent assembles the UI with 4 types of JSON messages

So we've covered the three building blocks — Surface, Component, and Data Model. But how are they actually exchanged between the agent and the frontend?

There are four types of JSON messages the AI agent sends to the frontend:

Type Description
createSurface Instructs the frontend to create a Surface
updateComponents Instructs the frontend to add/update Components on a Surface
updateDataModel Instructs the frontend to update the Data Model
deleteSurface Deletes a Surface along with its Components and Data Model

Here's how the flow looks once these messages start flowing and the UI gets built:

The best way to get a feel for the actual JSON exchanged is to check out the sample here:

https://a2ui.org/introduction/what-is-a2ui/#example

A2UI's message spec differs between v0.8 and v0.9. The example above assumes v0.9.

UI patterns come from a catalog

The UI components you specify in updateComponents are managed as a catalog.

A catalog defines "which components are available" and "what properties each component has."

There are broadly two types of catalogs:

Catalog Description
basicCatalog (default catalog) The standard set of UI components provided by A2UI
BYOC (custom catalog) A custom catalog where you can register your own components

https://a2ui.org/concepts/catalogs/

You can browse the components included in basicCatalog here:

https://a2ui.org/reference/components/

The frontend renders with an A2UI-specific renderer

The components defined in the catalog get rendered on the frontend by a renderer.

A renderer is a library that receives A2UI JSON messages and draws the UI on the frontend.

A2UI officially provides renderers for multiple platforms, including React, Lit (Web Components), Angular, and Flutter.

https://a2ui.org/reference/renderers/

Actions
A2UI also lets users interact with the UI it renders.
https://a2ui.org/concepts/actions/

This opens the door to Human-in-the-Loop flows and more interactive Generative UI experiences. (I'll save that for a future post — it gets complicated fast.)

The overall architecture

Here's the overall architecture of the app I built.

We've covered the basics of A2UI — from here, let's talk about what happens when you combine it with CopilotKit and Strands Agents.

I should mention that AWS already has a sample combining CopilotKit and Generative UI, which I used as a reference:

https://github.com/aws-samples/sample-FAST-applications/tree/main/samples/copilotkit-generative-ui

Building an AG-UI-compliant agent with CopilotKit

CopilotKit is a framework for building AI agents. With it, you can easily build agents that have a graphical UI powered by things like A2UI.

CopilotKit talks to the agent through AG-UI, an event-based protocol that connects the frontend and the AI agent.

https://github.com/ag-ui-protocol/ag-ui

Because the frontend and the agent communicate through this shared protocol, the two stay loosely coupled — and you can swap out the agent implementation without touching the frontend.

Combining AG-UI with Strands Agents

Strands Agents is AWS's SDK for building AI agents.

https://github.com/strands-agents/harness-sdk/tree/main/strands-py

It doesn't support AG-UI out of the box, so you need to add a dedicated adapter to make it AG-UI compatible.

https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/aws-strands/python/src/ag_ui_strands

Adding A2UI on top

With the CopilotKit + AG-UI setup in place, you can layer A2UI on top.

A2UI and AG-UI sit at different layers: AG-UI is the transport that carries events back and forth, while A2UI is the content of the messages carried over that transport.

For this project, I deployed everything on AWS using:

  • Frontend: AWS Amplify (Hosting)
  • Backend: AWS Lambda + Amazon API Gateway (HTTPS)
  • AI Agent: Amazon Bedrock AgentCore

Implementation

Now let's get into the actual code. The agent I built searches for tech meetups and proposes results visually as cards or lists.

Frontend

First, the A2UI-related code on the frontend.

Defining schemas and components

I defined components like StudyEventCard (a meetup card) and StudyEventList (a list view). Each component's schema is defined with Zod and consumed as props.

https://github.com/KMiya84377/copilotkit_a2ui_strands_app/blob/main/frontend/src/lib/a2ui/StudyEventCard.tsx

From these components and schemas, I built a catalog.

Setting includeBasicCatalog: true pulls in A2UI's default components (basicCatalog). Setting it to false means only your custom components are available.

https://github.com/KMiya84377/copilotkit_a2ui_strands_app/blob/main/frontend/src/lib/a2ui/app-catalog.tsx#L18-L31

Next, createA2UIMessageRenderer builds a renderer from that catalog.

https://github.com/KMiya84377/copilotkit_a2ui_strands_app/blob/main/frontend/src/components/chat/index.tsx#L24-L27

Finally, I wire the catalog and renderer into CopilotKit.

https://github.com/KMiya84377/copilotkit_a2ui_strands_app/blob/main/frontend/src/components/chat/index.tsx#L68-L77

A2UI renderer for CopilotKit
A2UI publishes an official React renderer, but CopilotKit ships its own renderer customized for CopilotKit:
https://github.com/CopilotKit/CopilotKit/tree/v1.56.5/packages/a2ui-renderer

Backend

Next, the backend implementation.

Enabling A2UI in the CopilotKit Runtime

Adding a2ui: { injectA2UITool: true } to the CopilotRuntime config enables A2UI.

https://github.com/KMiya84377/copilotkit_a2ui_strands_app/blob/dev/frontend/amplify/functions/copilotkit/handler.ts#L100-L112

With injectA2UITool: true, A2UIMiddleware from @ag-ui/a2ui-middleware is automatically applied to request handling.

A2UIMiddleware injects a render_a2ui tool into the AI agent — but only if the frontend has registered a catalog. It also injects instructions into the context so the agent knows how to use that tool.

The catch: the Strands Agents AG-UI adapter (covered next) ignores context, so I had to add those instructions myself.

AI Agent

The agent combines Strands Agents with an AG-UI adapter.

Wrapping with the AG-UI adapter's StrandsAgent

Wrapping the agent in StrandsAgent makes Strands Agents AG-UI compatible.

https://github.com/KMiya84377/copilotkit_a2ui_strands_app/blob/main/agent/strands_agents.py#L127-L132

I covered this part in more detail in a separate post (Japanese):

https://qiita.com/Takenoko4594/items/2e9e171d60854d77652f

Preparing an A2UI skill

As mentioned above, ag_ui_strands (the AG-UI adapter) currently can't automatically inject context into the agent.

As a workaround, I prepared a Strands Agents Skill.

https://strandsagents.com/docs/user-guide/concepts/plugins/skills/

By writing how to use A2UI in this file, the agent loads it as part of its system prompt and learns how to call the render_a2ui tool.

https://github.com/KMiya84377/copilotkit_a2ui_strands_app/blob/main/agent/skills/a2ui-common/SKILL.md

Loading the skill into the agent

The AgentSkills plugin automatically merges any SKILL.md files under a given directory into the agent's system prompt.

https://github.com/KMiya84377/copilotkit_a2ui_strands_app/blob/main/agent/strands_agents.py#L113-L125

And with that, A2UI works on an agent built with CopilotKit and Strands Agents.

Wrapping up

This was my first dive into A2UI with CopilotKit and Strands Agents.

A2UI gives the AI agent a lot of freedom to combine components however it wants — but it's also constrained to whatever components are predefined in the catalog.

Honestly, this design balance is the hard part (frontend pros might disagree). If you make each component too big/general, the AI loses freedom and flexibility. If you split things into too many small components, the AI's choices become inconsistent run-to-run, and the user experience suffers.

Getting this balance right depends not just on component granularity, but also on how smart the LLM is and how you prompt it — so I think it's something you tune iteratively by actually running it. (A2UI is tricky!)

Top comments (0)