Your Agent Streams SSE. Your Frontend Still Needs a Message Contract.
Your agent already streams; the real frontend cost is inventing a message contract for runs, tools, tokens, and errors. AG-UI is that contract, already standardized on AgentCore Runtime.

AG-UI is the standardized event protocol AgentCore Runtime already serves over SSE and WebSocket. Swap one app class, translate your stream, stop inventing a private taxonomy.
You shipped streaming SSE, and now a frontend engineer has to invent how runs, tool calls, partial messages, and mid-stream errors look. Someone already wrote that contract.
In this article: You will learn what the AG-UI protocol buys you on Amazon Bedrock AgentCore Runtime, how
serve_ag_uiandAGUIAppwire into a hosted DeepAgents loop, which lifecycle events the SDK actually confirms, and when plain SSE is still the right call. By the end you will know when to standardize the stream, when to open/ws, and why neither exempts you from the session idle clock.
Streaming is enough to build a UI, in the same sense that TCP is enough to build a website.
You yield tokens and tool starts from an async generator. The Runtime bridges them to SSE. A human watches the agent think. That is real progress. What you still do not have is a contract: how a client knows a run started, how a tool call differs from a token, what a partial message looks like before it is complete, and how an error lands mid-stream.
Every team invents those answers. Every team invents them differently. Then someone writes a second frontend.
AG-UI is that contract, standardized. AgentCore Runtime serves it over both SSE on POST /invocations and WebSocket on /ws, from a single entrypoint. This is a mechanical topic, and I am not going to pretend otherwise. Here is what it is and when to bother.

The one-line form
If your agent already exposes an AG-UI-shaped .run() interface, the integration is a single call:
from bedrock_agentcore.runtime import serve_ag_ui
serve_ag_ui(agui_agent)
That is the whole story for agents that already speak the protocol. Most production loops do not, so you take the real form: host the adapter yourself.
The real form: AGUIApp as adapter
Swap BedrockAgentCoreApp for AGUIApp. Keep the container contract. Implement an entrypoint that accepts RunAgentInput, yields lifecycle events around your framework stream, and threads thread_id into the agent config so session continuity still works.
The annotated listing below is the full pattern. Orientation markers call out the host, the typed input, the run open/close events, and the middle translation you still own.
from ag_ui.core import RunAgentInput, RunStartedEvent, RunFinishedEvent
from bedrock_agentcore.runtime import AGUIApp
from langchain_aws import ChatBedrockConverse
from deepagents import create_deep_agent
llm = ChatBedrockConverse(model="us.anthropic.claude-sonnet-4-6")
agent = create_deep_agent(model=llm, tools=[], system_prompt="You are a market analyst.")
app = AGUIApp() # ①
@app.entrypoint # ②
async def my_agent(input_data: RunAgentInput):
user_text = "".join( # ③
getattr(p, "text", "") for m in (input_data.messages or []) if m.role == "user"
for p in (m.content or [])
) or str(input_data)
yield RunStartedEvent(thread_id=input_data.thread_id, run_id=input_data.run_id) # ④
async for ev in agent.astream_events( # ⑤
{"messages": [("user", user_text)]},
{"configurable": {"thread_id": input_data.thread_id}},
version="v2",
):
if ev["event"] == "on_chat_model_stream" and ev["data"]["chunk"].content:
# Translate to AG-UI TextMessageStart/Delta/End here, per ag-ui-protocol. # ⑥
yield ev["data"]["chunk"].content
yield RunFinishedEvent(thread_id=input_data.thread_id, run_id=input_data.run_id) # ⑦
① AGUIApp is the AG-UI host. It replaces BedrockAgentCoreApp while keeping the same container contract.
② The entrypoint accepts RunAgentInput, the protocol's typed request shape for messages, thread, and run ids.
③ User text is flattened from the protocol message list so the agent still receives a plain string prompt.
④ RunStartedEvent opens the run lifecycle so clients can render "run began" before any tokens arrive.
⑤ astream_events is the data-plane loop; thread_id from the protocol input is wired into the DeepAgents config.
⑥ Token chunks are the adapter middle: map model stream events to AG-UI text-message events per ag-ui-protocol.
⑦ RunFinishedEvent closes the run lifecycle with the same thread and run ids the client opened.
Note: The full extracted listing at code/agent-core/appendix-a-ag-ui-frontend/listings/01-agui-app-entrypoint.py shows the complete program.
Everything else about the container stays the same: ARM64, bind 0.0.0.0:8080, /ping, and a per-session microVM. You changed the message surface, not the Runtime contract.

What is verified, and what is yours
Treat this honestly as an adapter. RunAgentInput, RunStartedEvent, RunFinishedEvent, RunErrorEvent, and EventEncoder are confirmed in the SDK. The text-message streaming classes (TextMessageStart, TextMessageDelta, TextMessageEnd) come from the ag-ui-protocol package. Read that package's API rather than trusting tutorial names. Lifecycle events are verified; the translation in the middle is yours.

Notice thread_id again. input_data.thread_id goes straight into the DeepAgents config. If a stateful thing in this stack works, an ID got threaded correctly. AG-UI is just another direction that identifier arrives from: the protocol input, not only the Runtime session header.
When to bother
Use plain SSE when your client is your own code, the rendering is simple, and you control both ends. Homegrown yields are fine, and they are less to learn. Most internal tools land here.
Use AG-UI when more than one client renders your agent; when a frontend team owns the UI and you want to hand them a spec instead of a changelog; or when you want off-the-shelf AG-UI components rather than writing a renderer.
Use the WebSocket when the human talks back mid-run. That is the real discriminator. SSE is one-directional. An approval flow, a mid-run correction, or a "no, not that competitor" is a client-to-agent message, and that wants /ws.

One warning before you build on /ws. A WebSocket held open while a human decides whether to approve something burns the session's 15-minute idle clock while a person has lunch. AG-UI gives you the message contract; it does not give you an exemption from the lifecycle. If the human might be slow, checkpoint and re-invoke rather than holding the connection.

Do this today
- Decide whether you have one client you control or a product surface that will grow a second frontend. That binary chooses plain SSE versus AG-UI.
- If you adopt AG-UI, swap to
AGUIApp(orserve_ag_uiif you already have a.run()surface) and keep the same ARM64 container contract. - Implement
RunStartedEvent/RunFinishedEventaround your existingastream_eventsloop, and wireinput_data.thread_idinto the framework config. - Read
ag-ui-protocolforTextMessageStart/Delta/Endand map token chunks explicitly; do not invent parallel event names. - For mid-run human input, prefer
/wsplus checkpoint-and-reinvoke over holding a socket through a slow approval.
Stop inventing a private event taxonomy
AG-UI is a protocol for a problem you will otherwise solve badly, twice. It costs you one class swap and an event-translation function. It buys you a contract your frontend team can build against without asking you what shape the tool calls come out in.
If your agent has one client and you wrote it, skip this. If your agent is a product, do not invent your own event taxonomy.