TL;DR
In this guide, you will learn how to build a frontend for your AWS Strands Agents using AG-UI Protocol and CopilotKit. AWS Strands will power the AI agents backend, while CopilotKit powers the frontend, and then AG-UI creates a bridge that enables the frontend to communicate with the backend.
Before we jump in, here is what we will cover:
What is AWS Strands?
Setting up a AWS Strands + AG-UI + CopilotKit agent using CLI
Integrating your AWS Strands agent with AG-UI protocol in the backend
Building a frontend for your AWS Strands + AG-UI agent using CopilotKit
Here is a preview of what you can build using AWS Strands + AG-UI + CopilotKit.
What is AWS Strands?
AWS Strands, also known as Strands Agents, is an open-source agent development framework developed by the AWS team for building and deploying AI agents.
The framework adopts a model-driven approach where it leverages model reasoning to plan, orchestrate tasks, and reflect on goals.
Moreover, Strands is model-agnostic and supports various LLM providers, including Amazon Bedrock, Anthropic, OpenAI, Meta Llama, and local models via Ollama or LiteLLM.
Below are the key features and capabilities of AWS Strands:
Tool Integration: It provides straightforward mechanisms for integrating custom tools and comes with native support for the Model Context Protocol (MCP), which allows agents to access a vast ecosystem of third-party tools.
Multi-Agent Systems: Strands simplifies the creation of complex systems where multiple specialized agents collaborate on complex tasks.
Observability: It includes native features for observability, such as traces, metrics, and logs, which are essential for debugging and monitoring agent performance.
You can learn more about AWS Strands if you visit the Strands Agents docs.
Prerequisites
Node.js 20+
Python 3.12+
OpenAI API Key (for the strands agent)
-
Any of the following package managers:
- pnpm (recommended)
- npm
- yarn
- bun
Setting up a AWS Strands + AG-UI + CopilotKit agent using CLI
In this section, you will learn how to set up a full-stack AWS Strands agent using a CLI command that sets up the backend using AG-UI protocol and the frontend using CopilotKit.
Let’s get started.
Step 1: Run CLI command
If you don’t already have an AWS Strands agent, you can set up one quickly by running the CLI command below in your terminal.
npx copilotkit@latest create -f aws-strands-py
Then give your project a name as shown below.
Step 2: Install dependencies
Once your project has been created successfully, install dependencies using your preferred package manager:
# Using pnpm (recommended)
pnpm install
# Using npm
npm install
# Using yarn
yarn install
# Using bun
bun install
Step 3: Set up your OpenAI key
After installing the dependencies, create a .env file in the root folder and add your OpenAI API key.
OPENAI_API_KEY="your-open-api-key-here”
Step 4: Run development server
Then start the development server using your preferred package manager:
# Using pnpm
pnpm dev
# Using npm
npm run dev
# Using yarn
yarn dev
# Using bun
bun run dev
Once the development server is running, navigate to http://localhost:3000/, and you should see your AWS Strands + AG-UI + CopilotKit agent up and running.
Congrats! You've successfully set up a full-stack AWS Strands agent. Your AI agent is now ready to use! Try playing around with the suggestions in the chat to test the integration.
Integrating your AWS Strands agent with AG-UI protocol in the backend
In this section, you will learn how to integrate your AWS Strands agent with AG-UI protocol, to expose it to the frontend.
Let’s jump in.
Step 1: Install AWS Strands + AG-UI packages
To get started, install the AWS Strands + AG-UI packages, along with other necessary dependencies, using the commands below.
uv pip install ag-ui-protocol strands-agents[OpenAI] strands-agents-tools ag_ui_strands
Step 2: Import required packages
Once you have installed the required packages, import them as shown below in a main.py file.
# AG-UI / Strands integration helpers:
# - StrandsAgent: Wrapper that makes a Strands Agent compatible with AG-UI protocol
# - StrandsAgentConfig: Configuration for shared state and tool behaviors
# - ToolBehavior: Defines how specific tools interact with the AG-UI state system
# - create_strands_app: Factory function that creates a FastAPI application
from ag_ui_strands import (
StrandsAgent,
StrandsAgentConfig,
ToolBehavior,
create_strands_app,
)
# dotenv: Loads environment variables from a .env file for local development
from dotenv import load_dotenv
# Pydantic: Used for data validation and schema definition
from pydantic import BaseModel, Field
# Strands SDK:
# - Agent: The core agent class that orchestrates LLM interactions
# - tool: Decorator to mark functions as tools the agent can call
from strands import Agent, tool
# OpenAI model wrapper from Strands for using GPT models
from strands.models.openai import OpenAIModel
// ...
Step 3: Define your AWS Strands Agent Data Models
After importing required packages, define Pydantic models for structured data validation to ensure that tool inputs are validated adequately before processing, as shown below.
# Pydantic: Used for data validation and schema definition
from pydantic import BaseModel, Field
// ...
class ProverbsList(BaseModel):
"""Pydantic model representing the entire proverbs list.
We use a dedicated model to validate tool inputs for `update_proverbs`.
The `proverbs` field is a list of strings describing the stored proverbs.
"""
# The proverbs field is a list of strings with a description for the LLM
proverbs: List[str] = Field(description="The complete list of proverbs")
// ...
Step 4: Define your AWS Strands Agent Tools
Once you have defined your agent data models, define your agent tools decorated with @tool to register them with the Strands framework, as shown below.
// ...
@tool
def get_weather(location: str):
"""Backend tool example: returns weather information for a location.
This is a simple synchronous tool demonstrating a backend operation. In a
real app this would call an external weather API. Here it returns a JSON
string for simplicity so agents and UI can display structured data.
Args:
location: The location to get weather for (e.g., "Seattle, WA")
Returns:
A JSON string containing weather information.
"""
# Return a JSON-encoded placeholder response
# In production, this would call an actual weather API like OpenWeatherMap
return json.dumps({"location": "70 degrees"})
@tool
def set_theme_color(theme_color: str):
"""Frontend-only tool example: request a UI theme color change.
Frontend tools are declared on the backend so the agent can request UI
actions, but actual execution happens in the browser via a frontend
integration (e.g., `useFrontendTool`). Because the frontend performs the
action, this backend implementation returns `None`.
Args:
theme_color: CSS color (hex, name, or rgb) that the UI should apply
"""
# Return None because the actual implementation is in the frontend
# The frontend will intercept this tool call and apply the theme change
return None
@tool
def update_proverbs(proverbs_list: ProverbsList):
"""Backend tool: replace the entire proverbs list.
IMPORTANT: This tool expects the complete list of proverbs, not just
additions. When the UI or user wants to modify the list, always send the
full array so the backend can store a canonical snapshot.
Args:
proverbs_list: A validated `ProverbsList` instance containing the
complete array of proverbs.
Returns:
A success message string on completion.
"""
# In a real app, this would persist to a DB. Here we return success.
# The state synchronization happens via the ToolBehavior config below.
return "Proverbs updated successfully."
// ...
Step 5: Define State Management Functions
After defining your agent tools, configure state management functions to handle state synchronization between the agent and the UI, as shown below.
// ...
def build_proverbs_prompt(input_data, user_message: str) -> str:
"""Build a model prompt that includes the current proverbs state.
When the agent composes messages for the model, we inject the current
`proverbs` array into the prompt so the model can reference or modify it.
This function acts as a "state context builder" - it takes the current
shared state and incorporates it into the prompt sent to the LLM.
Args:
input_data: The shared state container (may include a `state` key)
user_message: The user's raw request/message
Returns:
The combined prompt string includes proverbs when available.
"""
# Step 5a: Try to extract the state dictionary from input_data
state_dict = getattr(input_data, "state", None)
# Step 5b: Check if we have a valid state with proverbs
if isinstance(state_dict, dict) and "proverbs" in state_dict:
# Step 5c: Pretty-print the proverbs array for the model to read easily
# Using indent=2 makes the JSON human-readable in the prompt
proverbs_json = json.dumps(state_dict["proverbs"], indent=2)
# Step 5d: Combine the proverbs context with the user message
return (
f"Current proverbs list:\n{proverbs_json}\n\nUser request: {user_message}"
)
# Step 5e: Fall back to the plain user message if no proverbs state is available
return user_message
async def proverbs_state_from_args(context):
"""Extract a `{'proverbs': [...]}` snapshot from tool arguments.
This function is used by the shared state system when the `update_proverbs`
tool runs. It inspects the tool input and returns a small dictionary
containing only the `proverbs` array so AG-UI can update its local state.
This is an async function because AG-UI's state handling may involve
async operations in more complex scenarios.
Args:
context: ToolResultContext containing `tool_input` (string or dict)
Returns:
A dictionary like `{"proverbs": [...]}` or `None` on error.
"""
try:
# Step 5f: Get the raw tool input from the context
tool_input = context.tool_input
# Step 5g: If the tool input was serialized to a string, parse it back
# This handles cases where the LLM sends the input as a JSON string
if isinstance(tool_input, str):
tool_input = json.loads(tool_input)
# Step 5h: Some call sites may package the list under `proverbs_list` key
# This provides flexibility in how the data is structured
proverbs_data = tool_input.get("proverbs_list", tool_input)
# Step 5i: Ensure we extract the array safely with proper type checking
if isinstance(proverbs_data, dict):
proverbs_array = proverbs_data.get("proverbs", [])
else:
proverbs_array = []
# Step 5j: Return the state snapshot in the expected format
return {"proverbs": proverbs_array}
except Exception:
# Step 5k: Return None to indicate we couldn't build a valid state snapshot
# AG-UI will handle this gracefully and not update the state
return None
// ...
Step 6: Configure Shared State between your Agent and UI
Once you have defined state management functions, configure how your AWS Strands agent and the UI share state and behave for specific tools using AG-UI’s StrandsAgentConfig class and state management functions you defined earlier, as shown below.
// ...
shared_state_config = StrandsAgentConfig(
# Step 6a: Inject proverbs into the prompt using the builder function above
# This ensures the LLM always has context about the current proverbs list
state_context_builder=build_proverbs_prompt,
# Step 6b: Define tool-specific behaviors for AG-UI integration
# This dictionary maps tool names to their ToolBehavior configurations
tool_behaviors={
"update_proverbs": ToolBehavior(
# Step 6c: Skip messages snapshot to avoid redundant state updates
# When this is True, the tool result won't trigger a full message sync
skip_messages_snapshot=True,
# Step 6d: Use our custom state extractor function
# This tells AG-UI how to extract state changes from tool arguments
state_from_args=proverbs_state_from_args,
)
},
)
// ...
Step 7: Configure your AWS Strands Agent
After configuring shared state, configure your AWS Strands agent by first initializing the OpenAI model using your OpenAI API key, as shown below.
// ...
# Step 7a: Retrieve the OpenAI API key from environment variables
# Default to empty string if not set (will cause authentication errors)
api_key = os.getenv("OPENAI_API_KEY", "")
# Step 7b: Create the OpenAI model instance with configuration
model = OpenAIModel(
# Pass API key through client_args for the underlying OpenAI client
client_args={"api_key": api_key},
# Use GPT-4o (the optimized version); change as appropriate for your account
model_id="gpt-4o",
)
// ...
Then define the system prompt that sets the agent's persona and behavior, as shown below.
// ...
system_prompt = (
"You are a helpful and wise assistant who helps manage a collection of proverbs."
)
// ...
Finally, create a Strands Agent with the model and declared tools, as shown below.
// ...
strands_agent = Agent(
model=model, # The LLM to use
system_prompt=system_prompt, # Agent's persona
tools=[update_proverbs, get_weather, set_theme_color], # Available tools
)
// ...
Step 8: Integrate your AWS Strands Agent with AG-UI Protocol
Once you have configured your AWS Strands agent, integrate it with AG-UI protocol using AG-UI’s StrandsAgent wrapper, as shown below.
// ...
# Step 9a: Create a StrandsAgent wrapper that adds AG-UI capabilities
agui_agent = StrandsAgent(
agent=strands_agent, # The underlying Strands agent
name="proverbs_agent", # Unique identifier for this agent
description="A proverbs assistant that collaborates with you to manage proverbs",
config=shared_state_config, # Shared state and tool behavior configuration
)
// ...
Step 9: Create a FastAPI Server
After integrating your AWS Strands agent with AG-UI protocol, create a FastAPI server using the AG-UI factory function to expose your Strands + AG-UI agent to the frontend, as shown below.
// ...
app = create_strands_app(agui_agent, "/")
if __name__ == "__main__":
# Step 11a: Import uvicorn ASGI server (only needed when running directly)
import uvicorn
# Step 11b: Start the development server with auto-reload enabled
# - "main:app": Module and app variable reference for uvicorn
# - host="0.0.0.0": Listen on all network interfaces
# - port=8000: Default HTTP port for development
# - reload=True: Auto-restart on code changes (development mode)
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
Congrats! You've successfully integrated your Python AWS Strands Agent with AG-UI protocol and it is available at http://localhost:8000 (or specified port) endpoint.
Let’s now see how to add a frontend to your AG-UI wrapped AWS Strands agent.
Building a frontend for your AWS Strands + AG-UI agent using CopilotKit
In this section, you will learn how to add a frontend to your AWS Strands + AG-UI agent using CopilotKit, which runs anywhere that React runs.
Let’s get started.
Step 1: Install CopilotKit packages
To get started, install the latest CopilotKit packages in your frontend.
npm install @copilotkit/react-ui @copilotkit/react-core @copilotkit/runtime @ag-ui/client
Step 2: Set up the Copilot Runtime instance
Once you have installed the CopilotKit packages, set up the Copilot runtime instance with an HttpAgent example in the /api/copilotkit API route to enable your frontend to make HTTP requests to the backend.
import {
CopilotRuntime,
ExperimentalEmptyAdapter,
copilotRuntimeNextJSAppRouterEndpoint,
} from "@copilotkit/runtime";
import { HttpAgent } from "@ag-ui/client";
import { NextRequest } from "next/server";
// 1. You can use any service adapter here for multi-agent support. We use
// the empty adapter since we're only using one agent.
const serviceAdapter = new ExperimentalEmptyAdapter();
// 2. Create the CopilotRuntime instance and utilize the Strands AG-UI
// integration to set up the connection.
const runtime = new CopilotRuntime({
agents: {
// Our FastAPI endpoint URL
strands_agent: new HttpAgent({ url: "http://localhost:8000" }),
},
});
// 3. Build a Next.js API route to handle CopilotKit runtime requests.
export const POST = async (req: NextRequest) => {
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
runtime,
serviceAdapter,
endpoint: "/api/copilotkit",
});
return handleRequest(req);
};
Step 3: Set up CopilotKit provider
After setting up a Copilot Runtime instance, set up the CopilotKit provider component that manages your ADK agent sessions.
To set up the CopilotKit Provider, the component must wrap the Copilot-aware parts of your application.
For most use cases, it's appropriate to wrap the CopilotKit provider around the entire app, e.g., in your layout.tsx file.
// Step 1: Import necessary types and components from Next.js and CopilotKit
import type { Metadata } from "next";
import { CopilotKit } from "@copilotkit/react-core";
import "./globals.css";
import "@copilotkit/react-ui/styles.css";
// Step 2: Define metadata for the application, used by Next.js for SEO and page headers
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
// Step 3: Define the RootLayout component, which wraps the entire application
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
// Step 4: Return the JSX structure for the layout
return (
<html lang="en">
<body className={"antialiased"}>
{/* Step 5: Wrap the children components with CopilotKit provider to enable CopilotKit functionality */}
<CopilotKit runtimeUrl="/api/copilotkit" agent="strands_agent">
{children}
</CopilotKit>
</body>
</html>
);
}
Step 4: Set up a Copilot UI component
Once you have set up the CopilotKit Provider, add a Copilot UI component to enable interaction with your ADK agent. CopilotKit ships with several built-in chat components, which include CopilotPopup, CopilotSidebar, and CopilotChat.
To set up a Copilot UI component, define it alongside your core page components, e.g., in your page.tsx file.
"use client";
import { CopilotKitCSSProperties, CopilotSidebar } from "@copilotkit/react-ui";
import { useState } from "react";
export default function CopilotKitPage() {
// ...
return (
<main
// Step 2d-i: Apply theme color as a CSS custom property
// CopilotKit components read this variable to style their elements
style={
{ "--copilot-kit-primary-color": themeColor } as CopilotKitCSSProperties
}
>
{/* Step 2d-ii: CopilotSidebar provides the chat interface */}
<CopilotSidebar
// Step 2d-iii: Configuration options for the sidebar behavior
clickOutsideToClose={false} // Keep sidebar open when clicking outside
defaultOpen={true} // Start with sidebar visible
// Step 2d-iv: Labels customize the sidebar header and welcome message
labels={{
title: "Popup Assistant",
initial: "👋 Hi, there! You're chatting with a Strands agent.",
}}
// Step 2d-v: Suggestions provide quick-action buttons for users
// Each suggestion sends a predefined message when clicked
suggestions={[
{
title: "Generative UI",
message: "What's the weather in San Francisco?",
},
{
title: "Frontend Tools",
message: "Set the theme to green.",
},
{
title: "Writing Agent State",
message: "Add a proverb about AI.",
},
]}
>
{/* Step 2d-vi: Child content is pushed to the side by the sidebar */}
<YourMainContent themeColor={themeColor} />
</CopilotSidebar>
</main>
);
}
Step 5: Sync your AWS Strands + AG-UI agent state with the Frontend
After setting up a Copilot UI component, sync your AWS Strands + AG-UI agent state with the frontend using CopilotKit hooks.
To sync your AWS Strands + AG-UI agent state with the frontend, use the CopilotKit useCoAgent hook that allows you to share state bidirectionally between your application and the agent.
"use client";
import { useCoAgent } from "@copilotkit/react-core";
function YourMainContent({ themeColor }: { themeColor: string }) {
// 🪁 Shared State: https://docs.copilotkit.ai/coagents/shared-state
const { state, setState } = useCoAgent({
// Agent name must match the CopilotKit Provider StrandsAgent name
name: "strands_agent",
initialState: {
proverbs: [
"CopilotKit may be new, but it's the best thing since sliced bread.",
],
},
})
// ...
return (
// ...
)
Step 6: Rendering your AWS Strands + AG-UI Agent Tool Calls
Once you have synced your agent state with the frontend, render your agent's tool calls with custom UI components using the CopilotKit’s [useRenderToolCall](https://docs.copilotkit.ai/reference/hooks/useRenderToolCall) hook to provide the user with feedback about what your agent is doing, specifically when your agent is calling tools, as shown below.
"use client";
import {
useDefaultTool,
useRenderToolCall,
} from "@copilotkit/react-core";
// ...
function YourMainContent({ themeColor }: { themeColor: string }) {
// -------------------------------------------------------------------------
// Step 3c: Generative UI for Weather Tool
// -------------------------------------------------------------------------
// 🪁 Generative UI: https://docs.copilotkit.ai/strands/generative-ui/backend-tools
//
// This hook tells CopilotKit how to render the "get_weather" tool call.
// Instead of showing raw JSON, it displays a custom WeatherCard component.
useRenderToolCall(
{
// Step 3c-i: Tool name to intercept for custom rendering
name: "get_weather",
// Step 3c-ii: Parameter schema (for documentation/type checking)
parameters: [
{
name: "location",
description: "The location to get the weather for.",
required: true,
},
],
// Step 3c-iii: Render function called when this tool is executed
// Returns a React component that displays the tool result visually
render: (props) => (
<WeatherCard themeColor={themeColor} location={props.args.location} />
),
},
// Step 3c-iv: Dependency array - re-creates the render when themeColor changes
[themeColor],
);
// -------------------------------------------------------------------------
// Step 3d: Default Generative UI (Fallback Renderer)
// -------------------------------------------------------------------------
// 🪁 Default Generative UI: https://docs.copilotkit.ai/strands/generative-ui/backend-tools
//
// This hook provides a fallback UI for any tool that doesn't have a
// specific useRenderToolCall defined. It ensures all tools have some
// visual representation in the chat.
useDefaultTool(
{
// Step 3d-i: Default render function receives all tool props
render: (props) => (
<DefaultToolComponent themeColor={themeColor} {...props} />
),
},
// Step 3d-ii: Dependency array for re-render on theme changes
[themeColor],
);
return (
// ...
);
}
Then try asking the agent for the weather in a location. You should see the custom UI component that we added, which renders the get weather tool call and displays the arguments that were passed to the tool.
Step 7: Declaring Frontend Tools
After rendering your agent's tool calls, you can declare frontend tools using the CopilotKit’s [useFrontendTool](https://docs.copilotkit.ai/reference/hooks/useFrontendTool) hook that your agent can call to interact with client-side primitives such as:
Reading or modifying React component state
Accessing browser APIs like localStorage, sessionStorage, or cookies
Triggering UI updates or animations
Interacting with third-party frontend libraries
Performing actions that require the user's immediate browser context
You can declare frontend tools and use them within your AWS Strands agent, as shown below.
"use client";
import {
useFrontendTool,
} from "@copilotkit/react-core";
export default function CopilotKitPage() {
// -------------------------------------------------------------------------
// Step 2a: Local State for Theme Color
// -------------------------------------------------------------------------
// Initialize with a default indigo color. This state will be updated
// when the agent calls the "set_theme_color" frontend tool.
const [themeColor, setThemeColor] = useState("#6366f1");
// Step 2b: Debug Effect - logs theme color changes to console
useEffect(() => {
console.log(themeColor);
}, [themeColor]);
// -------------------------------------------------------------------------
// Step 2c: Frontend Tool Declaration
// -------------------------------------------------------------------------
// 🪁 Frontend Actions: https://docs.copilotkit.ai/guides/frontend-actions
//
// This hook declares a tool that the agent can call, but the execution
// happens here in the browser (not on the backend server).
//
// When the backend agent calls "set_theme_color", CopilotKit intercepts
// the call and runs the handler function defined here instead.
useFrontendTool({
// Step 2c-i: Tool name must match the backend tool declaration
name: "set_theme_color",
// Step 2c-ii: Parameter schema for the LLM to understand the tool
parameters: [
{
name: "theme_color",
description: "The theme color to set. Make sure to pick nice colors.",
required: true,
},
],
// Step 2c-iii: Handler function executed when the tool is called
// Destructures the theme_color from the tool arguments and updates the state
handler({ theme_color }) {
setThemeColor(theme_color);
},
});
// -------------------------------------------------------------------------
// Step 2d: Render the Page Layout
// -------------------------------------------------------------------------
return (
<main
// Step 2d-i: Apply theme color as a CSS custom property
// CopilotKit components read this variable to style their elements
style={
{ "--copilot-kit-primary-color": themeColor } as CopilotKitCSSProperties
}
>
{/* Step 2d-ii: CopilotSidebar provides the chat interface */}
<CopilotSidebar
// Step 2d-iii: Configuration options for the sidebar behavior
clickOutsideToClose={false} // Keep sidebar open when clicking outside
defaultOpen={true} // Start with sidebar visible
// Step 2d-iv: Labels customize the sidebar header and welcome message
labels={{
title: "Popup Assistant",
initial: "👋 Hi, there! You're chatting with a Strands agent.",
}}
// Step 2d-v: Suggestions provide quick-action buttons for users
// Each suggestion sends a predefined message when clicked
suggestions={[
{
title: "Generative UI",
message: "What's the weather in San Francisco?",
},
{
title: "Frontend Tools",
message: "Set the theme to green.",
},
{
title: "Writing Agent State",
message: "Add a proverb about AI.",
},
]}
>
{/* Step 2d-vi: Child content is pushed to the side by the sidebar */}
<YourMainContent themeColor={themeColor} />
</CopilotSidebar>
</main>
);
}
Then try asking the agent to set the UI theme to green. You should see the theme change to green, as shown below.
Step 8: Stream your AWS Strands + AG-UI agent responses in the frontend
To stream your AWS Strands + AG-UI agent responses or results in the frontend, pass the agent’s state field values to the frontend components, as shown below.
"use client";
import { useCoAgent } from "@copilotkit/react-core";
function YourMainContent({ themeColor }: { themeColor: string }) {
// 🪁 Shared State: https://docs.copilotkit.ai/coagents/shared-state
const { state, setState } = useCoAgent<AgentState>({
name: "strands_agent",
initialState: {
proverbs: [
"CopilotKit may be new, but it's the best thing since sliced bread.",
],
},
})
// ...
return (
<div
// Step 3e-i: Dynamic background color from theme state
style={{ backgroundColor: themeColor }}
className="h-screen flex justify-center items-center flex-col transition-colors duration-300"
>
{/* Step 3e-ii: Glass-morphism card container for proverbs */}
<div className="bg-white/20 backdrop-blur-md p-8 rounded-2xl shadow-xl max-w-2xl w-full">
{/* Step 3e-iii: Page header */}
<h1 className="text-4xl font-bold text-white mb-2 text-center">
Proverbs
</h1>
<p className="text-gray-200 text-center italic mb-6">
This is a demonstrative page, but it could be anything you want! 🪁
</p>
<hr className="border-white/20 my-6" />
{/* Step 3e-iv: Proverbs list - maps over shared state array */}
<div className="flex flex-col gap-3">
{state.proverbs?.map((proverb, index) => (
<div
key={index}
className="bg-white/15 p-4 rounded-xl text-white relative group hover:bg-white/20 transition-all"
>
{/* Step 3e-v: Proverb text content */}
<p className="pr-8">{proverb}</p>
{/* Step 3e-vi: Delete button - appears on hover */}
<button
onClick={() =>
// Step 3e-vii: Update shared state by filtering out this proverb
// This change will sync back to the backend agent
setState({
...state,
proverbs: state.proverbs?.filter((_, i) => i !== index),
})
}
className="absolute right-3 top-3 opacity-0 group-hover:opacity-100 transition-opacity
bg-red-500 hover:bg-red-600 text-white rounded-full h-6 w-6 flex items-center justify-center"
>
✕
</button>
</div>
))}
</div>
{/* Step 3e-viii: Empty state message when no proverbs exist */}
{state.proverbs?.length === 0 && (
<p className="text-center text-white/80 italic my-8">
No proverbs yet. Ask the assistant to add some!
</p>
)}
</div>
</div>
);
}
If you query your AWS Strands + AG-UI agent, you should see the agent’s response or results streaming in the UI, as shown below.
Conclusion
In this guide, we have walked through the steps to build a frontend for your AWS Strands agent using the AG-UI protocol and CopilotKit.
While we’ve explored a couple of features, we have barely scratched the surface of the countless use cases for CopilotKit, ranging from building interactive AI chatbots to building agentic solutions—in essence, CopilotKit lets you add a ton of functional AI capabilities to your products in minutes.
Hopefully, this guide makes it easier for you to integrate AI-powered Copilots into your existing application.
Follow CopilotKit on Twitter and say hi. If you'd like to build something cool, join the Discord community.






Top comments (4)
Comprehensive tutorial. We are an AWS shop and would love to go through this starter build to see if it's a good fit for what we are trying to accomplish.
Nice, we are building with AWS Strands. Where do I go if I get hung up?
Amazing to see a tutorial using AWS Strands agents!
Nicely done, Bonnie!
Great stuff and was super easy to follow!