{"slug": "a2ui-1-0-on-aws-a-strands-currency-agent-with-amazon-nova-agentcore-runtime-and", "title": "A2UI 1.0 on AWS: a Strands Currency Agent with Amazon Nova, AgentCore Runtime and ECS Express Mode", "summary": "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.", "body_md": "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.\n\n[https://github.com/xbill9/currency-strands-agui-a2ui1](https://github.com/xbill9/currency-strands-agui-a2ui1)\n\n**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`.\n\n| Piece | Where it runs | Code | \n|---|---|---|\n| React UI: Next.js, CopilotKit, `@a2ui/react` | ECS Express Mode, port 3000 | `frontend-react/` | \n| SigV4 proxy to AgentCore | the same Next.js server | `frontend-react/src/app/api/agentcore/route.ts` | \n| Strands agent, AG-UI at `/invocations` | AgentCore Runtime, protocol AGUI | `app/agent/main.py` | \n| A2UI tool, prompt, macros | inside the agent | `app/agent/currency_agent/` | \n| MCP server: `get_exchange_rate` (FastMCP) | stdio child of the agent | `app/agent/mcp_server.py` | \n\nThe 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.\n\nVersions, read from the machine on 2026-11-11:\n\n| Component | Version | \n|---|---|\n| `strands-agents` /`ag-ui-strands` | 1.64.0 / 0.3.0 | \n| `a2ui-agent-sdk` /`a2ui-core` | 0.11.0 / 0.6.0 | \n| `mcp` /`fastmcp` | 2.3.0 / 4.1.8 | \n| `next` /`react` /`@copilotkit/react-core` | 16.1.1 / 19.2.7 / 1.56.4 | \n| `@a2ui/react` /`@a2ui/web_core` | 0.15.0 / 0.15.0 | \n| AgentCore CLI | 1.0.2 | \n| Python / Node | 3.14.7 / v24.21.0 | \n| Model | `us.amazon.nova-2-lite-v1:0` | \n\n`aws login`\n`npm install -g @aws/agentcore`) for the deploy\n\n```\ngit clone https://github.com/xbill9/currency-strands-agui-a2ui1.git\ncd currency-strands-agui-a2ui1\npip install -e app/agent\ncd app/agent && PORT=8090 python3 main.py\n```\n\nIn a second terminal:\n\n```\ncd frontend-react && npm install --ignore-scripts --legacy-peer-deps\nPORT=3001 AGENT_URL=http://127.0.0.1:8090/invocations npx next dev --webpack -p 3001\ncurl -s http://127.0.0.1:8090/ping\n{\"status\":\"healthy\"}\n```\n\nThe 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.\n\nThe 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:\n\n``` python\n@tool(name=TOOL_NAME, context=True)\ndef send_a2ui_json_to_client(a2ui_payload: str, tool_context: ToolContext) -> str:\n    try:\n        wire = compile_a2ui(a2ui_payload)\n    except Exception as e:  # noqa: BLE001\n        return json.dumps({ERROR_KEY: f\"Failed to call A2UI tool {TOOL_NAME}: {e}\"})\n    tool_context.agent.cancel(after_current_tools=True)\n    return json.dumps({VALIDATED_KEY: wire})\nphp\ndef compile_a2ui(payload: str) -> list[dict]:\n    ...\n    validate_payload(a2ui_processor.active_catalogs, messages)\n    typed = [_message_adapter.validate_python(m) for m in messages]\n    expanded = macro_expander.transform_to_transport(typed)\n    wire = [m.model_dump(by_alias=True, exclude_none=True) for m in expanded]\n    validate_payload(BASE_CATALOGS, wire)\n    return wire\n```\n\n`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`.\n\nThe 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.\n\n`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.\n\nThree 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:\n\n| Macro | The model writes | The server builds | \n|---|---|---|\n| `RateCard` | `root = RateCard(\"USD\", \"EUR\", 250)` | heading, rate line, divider, source line in a Card | \n| `RateHistoryChart` | `root = RateHistoryChart(\"USD\", \"JPY\", 90)` | heading, LineChart, low/high/latest line | \n| `RateComparison` | `root = RateComparison(\"USD\", [\"EUR\",\"GBP\",\"JPY\",\"CAD\"])` | one rate per currency and a BarChart of the change | \n\nMacro arguments are untrusted input: `macros.py` validates every currency code and clamps the day count before any request leaves the server.\n\n`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:\n\n``` python\nclass RescuingBedrockModel(BedrockModel):\n    async def stream(self, *args, **kwargs):\n        async for event in rescue_stream(super().stream(*args, **kwargs)):\n            yield event\n```\n\nThe 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.\n\n`main.py` mounts the Strands agent as an AG-UI endpoint with `ag-ui-strands`:\n\n```\nagui_agent = StrandsAgent(agent=agent, name=\"currency_agent\",\n                          description=\"An agent that can help with currency conversions\")\napp = FastAPI(title=\"Strands currency agent\", telemetry={\"auto_configure\": False})\nadd_strands_fastapi_endpoint(app, agui_agent, \"/invocations\")\nadd_ping(app, \"/ping\")\n```\n\nThe AgentCore project declares the protocol, the runtime and the model:\n\n```\n{\n  \"name\": \"currency_agent\",\n  \"build\": \"CodeZip\",\n  \"entrypoint\": \"main.py\",\n  \"codeLocation\": \"app/agent\",\n  \"runtimeVersion\": \"PYTHON_3_14\",\n  \"protocol\": \"AGUI\",\n  \"envVars\": [{ \"name\": \"BEDROCK_MODEL_ID\", \"value\": \"us.amazon.nova-2-lite-v1:0\" }]\n}\n./infra/configure_aws_target.sh\nagentcore deploy\n```\n\n`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.\n\n`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.\n\nAgentCore's invoke endpoint takes SigV4-signed requests, so the Next.js server signs each run with the ECS task role:\n\n``` js\nconst signed = await new SignatureV4({\n  service: \"bedrock-agentcore\", region, credentials: defaultProvider(), sha256: Sha256,\n}).sign(new HttpRequest({\n  method: \"POST\", protocol: \"https:\", hostname: host, path,\n  query: { qualifier: \"DEFAULT\" },\n  headers: { host, \"content-type\": \"application/json\", accept: \"text/event-stream\",\n             \"x-amzn-bedrock-agentcore-runtime-session-id\": sessionId(threadId) },\n  body,\n}));\n```\n\nThe 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.\n\n```\n./infra/deploy_ui.sh\n```\n\nThe 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.\n\n`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.\n\n```\nUI_URL=https://<your-endpoint>.ecs.us-east-1.on.aws \\\n  python3 tools/render_suite.py 10 nova-2-lite-on-agentcore evidence/render-suite-aws.jsonl\n# nova-2-lite-on-agentcore: 100 of 100 replies rendered clean (10 runs per prompt)\n10 of 10: pass | Compare 1 USD in EUR, GBP, JPY and CAD\n10 of 10: pass | Compare the euro, pound and Swiss franc against the dollar in a bar chart\n10 of 10: pass | Convert 100 USD to XYZ\n10 of 10: pass | How much is 250 USD in EUR?\n10 of 10: pass | Show a bar chart of 1 USD in EUR, GBP, JPY, CHF and CAD\n10 of 10: pass | Show me EUR to USD for the past 6 months and the current rate\n10 of 10: pass | Show me the USD to JPY trend over the last 90 days\n10 of 10: pass | What's a thousand euros in dollars?\n10 of 10: pass | What's the weather in Paris?\n10 of 10: pass | Write the A2UI block for a USD to EUR rate card directly in your reply text. Do not call any tool.\n```\n\n| Where | Model | Clean renders | \n|---|---|---|\n| Agent and UI on a laptop | `us.amazon.nova-2-lite-v1:0` | 100 of 100 | \n| ECS Express Mode → AgentCore Runtime | `us.amazon.nova-2-lite-v1:0` | 100 of 100 | \n\nThe ECS service bills by the hour while it runs. These remove everything the two deploy steps created:\n\n```\naws ecs delete-express-gateway-service \\\n  --service-arn arn:aws:ecs:$REGION:$ACCOUNT:service/default/currency-strands-ui\naws cloudformation delete-stack --stack-name AgentCore-currencystrands-default\naws ecr delete-repository --repository-name currency-strands-ui --force\naws iam delete-role-policy --role-name currency-strands-ui-task --policy-name invoke-agentcore-runtime\naws iam delete-role --role-name currency-strands-ui-task\n```\n\n|  | ADK build | This build | \n|---|---|---|\n| Agent framework | Google ADK `LlmAgent` | Strands Agents `Agent` | \n| Model | `gemini-3.8-flash` on the Gemini API | `us.amazon.nova-2-lite-v1:0` on Amazon Bedrock | \n| AG-UI adapter | `ag_ui_adk` | `ag-ui-strands` | \n| Ending the turn after the UI | ADK's `skip_summarization` | `agent.cancel(after_current_tools=True)` on success | \n| A2UI as reply text | `after_model_callback` writes a function call | `RescuingBedrockModel` rewrites the ConverseStream | \n| MCP server | separate process over HTTP | stdio child of the agent | \n| Agent hosting | Cloud Run (A2A server) | AgentCore Runtime, protocol AGUI | \n| UI hosting | local | ECS Express Mode, SigV4 proxy to AgentCore | \n| A2UI pipeline and macros | `a2ui-agent-sdk` 0.11.0, three macros | the same | \n| Clean renders, 10 prompts × 10 runs | 100 of 100 | 100 of 100 locally, 100 of 100 on AWS | \n\nPick 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.\n\nOn 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.\n\nThe 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:\n\n`us.amazon.nova-2-lite-v1:0`: 100 of 100 replies rendered clean locally and 100 of 100 through the deployed app`agentcore deploy`\n`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.\n\nThe 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.", "url": "https://wpnews.pro/news/a2ui-1-0-on-aws-a-strands-currency-agent-with-amazon-nova-agentcore-runtime-and", "canonical_source": "https://dev.to/aws-builders/a2ui-10-on-aws-a-strands-currency-agent-with-amazon-nova-agentcore-runtime-and-ecs-express-mode-2pgl", "published_at": "2026-11-12 13:42:26+00:00", "updated_at": "2026-11-12 13:53:14.365296+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "agent-protocols", "large-language-models", "developer-tools"], "entities": ["AWS", "Amazon Nova", "Amazon Bedrock AgentCore", "Amazon ECS Express Mode", "Strands", "CopilotKit", "Next.js", "A2UI"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/a2ui-1-0-on-aws-a-strands-currency-agent-with-amazon-nova-agentcore-runtime-and", "markdown": "https://wpnews.pro/news/a2ui-1-0-on-aws-a-strands-currency-agent-with-amazon-nova-agentcore-runtime-and.md", "text": "https://wpnews.pro/news/a2ui-1-0-on-aws-a-strands-currency-agent-with-amazon-nova-agentcore-runtime-and.txt", "jsonld": "https://wpnews.pro/news/a2ui-1-0-on-aws-a-strands-currency-agent-with-amazon-nova-agentcore-runtime-and.jsonld"}}