cd /news/ai-agents/a2ui-1-0-on-aws-a-strands-currency-a… · home › topics › ai-agents › article
[ARTICLE · art-168343] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

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.

by read9 min views2 publishedNov 12, 2026

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

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. 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:

@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 <a2ui onward is held out of the chat, and at the end of the message each block becomes a toolUse for the UI tool with stop reason tool_use, so Strands runs the tool as if the model had called it:

class RescuingBedrockModel(BedrockModel):
    async def stream(self, *args, **kwargs):
        async for event in rescue_stream(super().stream(*args, **kwargs)):
            yield event

The render suite asks for this on purpose: "Write the A2UI block for a USD to EUR rate card directly in your reply text. Do not call any tool." It rendered as a card in 10 of 10 runs locally and 10 of 10 on AWS.

main.py mounts the Strands agent as an AG-UI endpoint with ag-ui-strands:

agui_agent = StrandsAgent(agent=agent, name="currency_agent",
                          description="An agent that can help with currency conversions")
app = FastAPI(title="Strands currency agent", telemetry={"auto_configure": False})
add_strands_fastapi_endpoint(app, agui_agent, "/invocations")
add_ping(app, "/ping")

The AgentCore project declares the protocol, the runtime and the model:

{
  "name": "currency_agent",
  "build": "CodeZip",
  "entrypoint": "main.py",
  "codeLocation": "app/agent",
  "runtimeVersion": "PYTHON_3_14",
  "protocol": "AGUI",
  "envVars": [{ "name": "BEDROCK_MODEL_ID", "value": "us.amazon.nova-2-lite-v1:0" }]
}
./infra/configure_aws_target.sh
agentcore deploy

agentcore deploy packages app/agent with the dependencies in its pyproject.toml and deploys a CloudFormation stack holding the runtime and its IAM role. The MCP server ships in the same zip and runs as a stdio child of the agent.

bedrock-agentcore[strands-agents] caps mcp below 2.0, and FastMCP 4 needs mcp 2.3. The agent's pyproject.toml lists strands-agents, ag-ui-strands, fastmcp>=4.1.8 and mcp>=2.3,<2.4 directly, and the AG-UI app needs nothing from the bedrock-agentcore package.

AgentCore's invoke endpoint takes SigV4-signed requests, so the Next.js server signs each run with the ECS task role:

const signed = await new SignatureV4({
  service: "bedrock-agentcore", region, credentials: defaultProvider(), sha256: Sha256,
}).sign(new HttpRequest({
  method: "POST", protocol: "https:", hostname: host, path,
  query: { qualifier: "DEFAULT" },
  headers: { host, "content-type": "application/json", accept: "text/event-stream",
             "x-amzn-bedrock-agentcore-runtime-session-id": sessionId(threadId) },
  body,
}));

The session id is the CopilotKit thread id, padded to the 33 characters AgentCore requires, so each conversation keeps its own runtime session. A 5xx from the runtime is retried before any byte reaches the browser.

./infra/deploy_ui.sh

The script builds the image, pushes it to Amazon ECR, creates a task role allowed only bedrock-agentcore:InvokeAgentRuntime on this one runtime, and creates or updates the service with aws ecs create-express-gateway-service. ECS Express Mode provisions the load balancer, the HTTPS endpoint and the scaling, and the script prints the URL.

tools/render_suite.py sends each prompt through the UI's CopilotKit endpoint. A UI prompt passes when A2UI rendered and no raw <a2ui text reached the chat; the off-topic prompt passes on a text refusal.

UI_URL=https://<your-endpoint>.ecs.us-east-1.on.aws \
  python3 tools/render_suite.py 10 nova-2-lite-on-agentcore evidence/render-suite-aws.jsonl
10 of 10: pass | Compare 1 USD in EUR, GBP, JPY and CAD
10 of 10: pass | Compare the euro, pound and Swiss franc against the dollar in a bar chart
10 of 10: pass | Convert 100 USD to XYZ
10 of 10: pass | How much is 250 USD in EUR?
10 of 10: pass | Show a bar chart of 1 USD in EUR, GBP, JPY, CHF and CAD
10 of 10: pass | Show me EUR to USD for the past 6 months and the current rate
10 of 10: pass | Show me the USD to JPY trend over the last 90 days
10 of 10: pass | What's a thousand euros in dollars?
10 of 10: pass | What's the weather in Paris?
10 of 10: pass | Write the A2UI block for a USD to EUR rate card directly in your reply text. Do not call any tool.
Where Model Clean renders
Agent and UI on a laptop us.amazon.nova-2-lite-v1:0 100 of 100
ECS Express Mode → AgentCore Runtime us.amazon.nova-2-lite-v1:0 100 of 100

The ECS service bills by the hour while it runs. These remove everything the two deploy steps created:

aws ecs delete-express-gateway-service \
  --service-arn arn:aws:ecs:$REGION:$ACCOUNT:service/default/currency-strands-ui
aws cloudformation delete-stack --stack-name AgentCore-currencystrands-default
aws ecr delete-repository --repository-name currency-strands-ui --force
aws iam delete-role-policy --role-name currency-strands-ui-task --policy-name invoke-agentcore-runtime
aws iam delete-role --role-name currency-strands-ui-task
ADK build This build
Agent framework Google ADK LlmAgent Strands Agents Agent
Model gemini-3.8-flash on the Gemini API us.amazon.nova-2-lite-v1:0 on Amazon Bedrock
AG-UI adapter ag_ui_adk ag-ui-strands
Ending the turn after the UI ADK's skip_summarization agent.cancel(after_current_tools=True) on success
A2UI as reply text after_model_callback writes a function call RescuingBedrockModel rewrites the ConverseStream
MCP server separate process over HTTP stdio child of the agent
Agent hosting Cloud Run (A2A server) AgentCore Runtime, protocol AGUI
UI hosting local ECS Express Mode, SigV4 proxy to AgentCore
A2UI pipeline and macros a2ui-agent-sdk 0.11.0, three macros the same
Clean renders, 10 prompts × 10 runs 100 of 100 100 of 100 locally, 100 of 100 on AWS

Pick by where the rest of the system lives. The A2UI half is portable: the same SDK, catalogs, macros and React renderer run under both frameworks, and only two framework hooks change, the call that ends the turn and the place where reply-text A2UI is caught.

On AWS, AgentCore Runtime takes an AG-UI app as is, and ECS Express Mode gives the UI an HTTPS endpoint from one command. The one piece of glue is the SigV4 proxy in the UI server, which also keeps credentials out of the browser.

The goal of this article was to run the A2UI 1.0 currency agent entirely on AWS, with Strands Agents and an Amazon model. The key to the solution was keeping the A2UI pipeline whole, with the model writing one macro call and the server building and validating the components, and adapting only the framework hooks and the hosting. The results were:

us.amazon.nova-2-lite-v1:0: 100 of 100 replies rendered clean locally and 100 of 100 through the deployed appagentcore deploy bedrock-agentcore[strands-agents] caps mcp below 2.0; FastMCP 4 needs Scope: one AWS account in us-east-1, us.amazon.nova-2-lite-v1:0 at temperature 0.2, ECB reference rates from the Frankfurter API, measured on 2026-11-11 with ten runs per prompt, locally and through the deployed app.

The strategy for running a generative UI agent on AWS with Strands, Amazon Nova, AgentCore Runtime and ECS Express Mode was validated with an incremental step by step approach.

── more in #ai-agents 4 stories · sorted by recency
── more on @aws 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a2ui-1-0-on-aws-a-st…] indexed:0 read:9min 2026-11-12 · —