A2UI 1.0 on AWS: a Strands Currency Agent with Amazon Nova, AgentCore Runtime and ECS Express Mode A developer built a currency agent that answers with generated UI, running entirely on AWS: a Strands agent on Amazon Nova 2 Lite looks up exchange rates via an MCP server, emits A2UI 1.0 components, and a Next.js CopilotKit front end renders cards and charts over AG-UI. The agent runs on Amazon Bedrock AgentCore Runtime and the UI on Amazon ECS Express Mode, with a SigV4 proxy keeping AWS credentials out of the browser. In testing, ten prompts run ten times each rendered cleanly in 100 of 100 replies both locally and through the deployed app. This article provides a step by step guide to a currency agent that answers with generated UI, run entirely on AWS. A Strands agent on Amazon Nova 2 Lite looks up exchange rates, describes its answer as A2UI 1.0 components, and a Next.js CopilotKit front end renders the cards and charts over AG-UI. The agent runs on Amazon Bedrock AgentCore Runtime and the UI runs on Amazon ECS Express Mode. It is the AWS sibling of a Google ADK and Gemini build of the same app. https://github.com/xbill9/currency-strands-agui-a2ui1 https://github.com/xbill9/currency-strands-agui-a2ui1 The A2UI 1.0 pipeline carries over to Strands and Amazon Nova unchanged, and the deployed app renders every reply clean. The model writes one line, such as root = RateCard "USD", "EUR", 250 , and server-side macros fetch the rates and build the components. Ten prompts, run ten times each, rendered cleanly in 100 of 100 replies with the agent and UI on a laptop, and in 100 of 100 replies through the deployed app: ECS Express Mode to AgentCore Runtime to us.amazon.nova-2-lite-v1:0 . | Piece | Where it runs | Code | |---|---|---| | React UI: Next.js, CopilotKit, @a2ui/react | ECS Express Mode, port 3000 | frontend-react/ | | SigV4 proxy to AgentCore | the same Next.js server | frontend-react/src/app/api/agentcore/route.ts | | Strands agent, AG-UI at /invocations | AgentCore Runtime, protocol AGUI | app/agent/main.py | | A2UI tool, prompt, macros | inside the agent | app/agent/currency agent/ | | MCP server: get exchange rate FastMCP | stdio child of the agent | app/agent/mcp server.py | The browser never holds AWS credentials. CopilotKit's runtime posts each AG-UI run to /api/agentcore on the Next.js server, which signs it with the ECS task role and streams the agent's AG-UI events back. Versions, read from the machine on 2026-11-11: | Component | Version | |---|---| | strands-agents / ag-ui-strands | 1.64.0 / 0.3.0 | | a2ui-agent-sdk / a2ui-core | 0.11.0 / 0.6.0 | | mcp / fastmcp | 2.3.0 / 4.1.8 | | next / react / @copilotkit/react-core | 16.1.1 / 19.2.7 / 1.56.4 | | @a2ui/react / @a2ui/web core | 0.15.0 / 0.15.0 | | AgentCore CLI | 1.0.2 | | Python / Node | 3.14.7 / v24.21.0 | | Model | us.amazon.nova-2-lite-v1:0 | aws login npm install -g @aws/agentcore for the deploy git clone https://github.com/xbill9/currency-strands-agui-a2ui1.git cd currency-strands-agui-a2ui1 pip install -e app/agent cd app/agent && PORT=8090 python3 main.py In a second terminal: cd frontend-react && npm install --ignore-scripts --legacy-peer-deps PORT=3001 AGENT URL=http://127.0.0.1:8090/invocations npx next dev --webpack -p 3001 curl -s http://127.0.0.1:8090/ping {"status":"healthy"} The UI is at http://localhost:3001 http://localhost:3001 . The same main.py runs locally and on AgentCore Runtime: /invocations and /ping on port 8080 is the AgentCore contract, and PORT moves it for local work. The model writes A2UI Express and passes it to one tool. The tool compiles the block, validates it against the catalogs the model sees, expands the macros, and validates the result against the catalogs the browser renders: python @tool name=TOOL NAME, context=True def send a2ui json to client a2ui payload: str, tool context: ToolContext - str: try: wire = compile a2ui a2ui payload except Exception as e: noqa: BLE001 return json.dumps {ERROR KEY: f"Failed to call A2UI tool {TOOL NAME}: {e}"} tool context.agent.cancel after current tools=True return json.dumps {VALIDATED KEY: wire} php def compile a2ui payload: str - list dict : ... validate payload a2ui processor.active catalogs, messages typed = message adapter.validate python m for m in messages expanded = macro expander.transform to transport typed wire = m.model dump by alias=True, exclude none=True for m in expanded validate payload BASE CATALOGS, wire return wire ag-ui-strands streams the tool result to the browser as an AG-UI TOOL CALL RESULT , and the React app renders validated a2ui json with @a2ui/react . The system prompt comes from the same A2UI SDK the ADK build uses: the role, then generate base rules , generate catalog instructions include schema=True and generate examples from the Express format's prompt generator. tool context.agent.cancel after current tools=True ends the turn after a successful send, so the card is the whole reply and no second model call follows. It runs only on success. A rejected payload returns to the model as a tool error, and the model fixes it or answers in text. Three macros, shared with the ADK build, turn one call into a full card. Each fetches its figures from the Frankfurter API on the server, so no rate passes through the model: | Macro | The model writes | The server builds | |---|---|---| | RateCard | root = RateCard "USD", "EUR", 250 | heading, rate line, divider, source line in a Card | | RateHistoryChart | root = RateHistoryChart "USD", "JPY", 90 | heading, LineChart, low/high/latest line | | RateComparison | root = RateComparison "USD", "EUR","GBP","JPY","CAD" | one rate per currency and a BarChart of the change | Macro arguments are untrusted input: macros.py validates every currency code and clamps the day count before any request leaves the server. RescuingBedrockModel wraps the Bedrock ConverseStream. Text from