[{"data":1,"prerenderedAt":3770},["ShallowReactive",2],{"blog-ag-ui-part-1-how-agents-talk-to-your-frontend":3},{"post":4,"minutes":1454,"surround":3765},{"id":5,"title":6,"body":7,"color":3744,"cover":3745,"coverAlt":3746,"crossPost":3747,"date":3748,"description":3749,"draft":3750,"extension":3751,"meta":3752,"navigation":2951,"path":3753,"seo":3754,"stem":3755,"tags":3756,"tldr":3760,"updated":3747,"__hash__":3764},"blogs\u002Fblogs\u002Fag-ui-part-1-how-agents-talk-to-your-frontend.md","AG-UI Part 1: How Agents Talk to Your Frontend",{"type":8,"value":9,"toc":3730},"minimark",[10,23,37,45,50,57,64,68,71,74,77,81,84,93,178,181,185,193,406,413,421,424,431,434,462,466,469,587,594,597,618,625,629,632,635,728,731,962,965,1485,1492,1496,1503,1998,2009,2262,2265,2271,2275,2278,2285,2600,2607,2749,2756,2759,2795,2798,2804,2815,2818,2822,2829,2832,2835,3311,3324,3335,3624,3628,3658,3662,3665,3668,3685,3689,3726],[11,12,13,14,18,19,22],"p",{},"Every team that connects an agent to a frontend ends up inventing a private event format to do it. ",[15,16,17],"strong",{},"AG-UI"," (Agent-User Interaction Protocol) is an open, MIT-licensed attempt to agree on that format once, and it reached ",[15,20,21],{},"1.0"," this autumn. In this post I explain how it works and build a tiny server and client from scratch, with no SDK and no API key.",[11,24,25,26,31,32,36],{},"If you have ever hand-rolled a streaming chat over SSE (I did, for the ",[27,28,30],"a",{"href":29},"\u002Fchat","Amit.AI chat"," on this site, covered in ",[27,33,35],{"href":34},"\u002Fblogs\u002Fbuilding-amit-ai-part-1-the-frontend","Building Amit.AI, Part 1","), AG-UI will feel familiar. It is that idea, grown up and standardised.",[11,38,39,40,44],{},"This is Part 1, about the protocol itself. Part 2 (coming soon) covers what you can send ",[41,42,43],"em",{},"through"," it: generative UI, where the agent decides which interface the user sees.",[46,47,49],"h2",{"id":48},"the-protocol-in-30-seconds","The protocol in 30 seconds",[11,51,52,53,56],{},"I made a short video of the whole idea. The line is the connection between the agent and your app, and each capsule sliding down it is one event. Watch for the last scene, where the user's answer goes back up: that is a ",[41,54,55],{},"new"," request, not a message on the old stream. I come back to that in the interrupts section below.",[58,59],"video-embed",{"id":60,"provider":61,"ratio":62,"title":63},"C1D15HhbyCs","youtube","9\u002F16","AG-UI in 30 seconds: events flow from an agent to your app along one connection",[46,65,67],{"id":66},"_1-the-problem-agents-are-not-requestresponse","1. The problem: agents are not request\u002Fresponse",[11,69,70],{},"A classic API call is simple. You send a request, you wait, you get a response.",[11,72,73],{},"An agent run is not like that. It can take a minute. It streams text. It calls a tool, then another, sometimes in parallel. It wants to show a plan. It might need you to approve something before it carries on. And if every framework invents its own way of describing all that, every frontend has to be rewritten for every backend.",[11,75,76],{},"So everyone ends up building the same thing: a private, half-documented event format between their agent and their UI. AG-UI is an attempt to agree on that format once.",[46,78,80],{"id":79},"_2-where-it-fits-the-protocol-stack","2. Where it fits: the protocol stack",[11,82,83],{},"There are now a few agent protocols, and they get confused a lot. They answer different questions:",[11,85,86],{},[87,88],"img",{"alt":89,"height":90,"src":91,"width":92},"Diagram of an agent at the centre. AG-UI connects it up to your app, MCP connects it down to tools and data, A2A connects it sideways to other agents. A2UI and MCP Apps ride inside AG-UI.",1400,"https:\u002F\u002Fres.cloudinary.com\u002Fc552lpdm\u002Fimage\u002Fupload\u002Fprotocol-stack.webp",2400,[94,95,96,112],"table",{},[97,98,99],"thead",{},[100,101,102,106,109],"tr",{},[103,104,105],"th",{},"Protocol",[103,107,108],{},"Connects",[103,110,111],{},"Question it answers",[113,114,115,133,148,162],"tbody",{},[100,116,117,123,126],{},[118,119,120],"td",{},[15,121,122],{},"MCP",[118,124,125],{},"agent ↔ tools and data",[118,127,128,129,132],{},"How does my agent ",[41,130,131],{},"act","?",[100,134,135,140,143],{},[118,136,137],{},[15,138,139],{},"A2A",[118,141,142],{},"agent ↔ other agents",[118,144,128,145,132],{},[41,146,147],{},"delegate",[100,149,150,154,157],{},[118,151,152],{},[15,153,17],{},[118,155,156],{},"agent ↔ the user's app",[118,158,128,159,132],{},[41,160,161],{},"talk to the person",[100,163,164,169,172],{},[118,165,166],{},[15,167,168],{},"A2UI \u002F MCP Apps",[118,170,171],{},"the UI payload itself",[118,173,174,177],{},[41,175,176],{},"What"," UI should be rendered?",[11,179,180],{},"A real agent often speaks several in a single run: MCP inward to call a CRM, AG-UI outward to stream its progress. AG-UI also does not tell you what the UI looks like. Specs like A2UI describe the interface, and AG-UI is the pipe that delivers it.",[46,182,184],{"id":183},"_3-the-wire-format-one-request-in-events-out","3. The wire format: one request in, events out",[11,186,187,188,192],{},"The request is a ",[189,190,191],"code",{},"RunAgentInput",". The fields that matter:",[194,195,200],"pre",{"className":196,"code":197,"language":198,"meta":199,"style":199},"language-json shiki shiki-themes tokyo-night","{\n  \"threadId\": \"thread-1\",\n  \"runId\": \"run-1\",\n  \"state\": {},\n  \"messages\": [{ \"id\": \"m1\", \"role\": \"user\", \"content\": \"hi\" }],\n  \"tools\": [],\n  \"context\": [],\n  \"forwardedProps\": {}\n}\n","json","",[189,201,202,211,240,261,278,352,369,385,400],{"__ignoreMap":199},[203,204,207],"span",{"class":205,"line":206},"line",1,[203,208,210],{"class":209},"sgJMe","{\n",[203,212,214,218,222,225,228,231,235,237],{"class":205,"line":213},2,[203,215,217],{"class":216},"sAklC","  \"",[203,219,221],{"class":220},"s3R4Z","threadId",[203,223,224],{"class":216},"\"",[203,226,227],{"class":216},":",[203,229,230],{"class":216}," \"",[203,232,234],{"class":233},"sPY7s","thread-1",[203,236,224],{"class":216},[203,238,239],{"class":216},",\n",[203,241,243,245,248,250,252,254,257,259],{"class":205,"line":242},3,[203,244,217],{"class":216},[203,246,247],{"class":220},"runId",[203,249,224],{"class":216},[203,251,227],{"class":216},[203,253,230],{"class":216},[203,255,256],{"class":233},"run-1",[203,258,224],{"class":216},[203,260,239],{"class":216},[203,262,264,266,269,271,273,276],{"class":205,"line":263},4,[203,265,217],{"class":216},[203,267,268],{"class":220},"state",[203,270,224],{"class":216},[203,272,227],{"class":216},[203,274,275],{"class":209}," {}",[203,277,239],{"class":216},[203,279,281,283,286,288,290,293,295,299,301,303,305,308,310,313,315,318,320,322,324,327,329,331,333,336,338,340,342,345,347,350],{"class":205,"line":280},5,[203,282,217],{"class":216},[203,284,285],{"class":220},"messages",[203,287,224],{"class":216},[203,289,227],{"class":216},[203,291,292],{"class":209}," [{",[203,294,230],{"class":216},[203,296,298],{"class":297},"sySf4","id",[203,300,224],{"class":216},[203,302,227],{"class":216},[203,304,230],{"class":216},[203,306,307],{"class":233},"m1",[203,309,224],{"class":216},[203,311,312],{"class":216},",",[203,314,230],{"class":216},[203,316,317],{"class":297},"role",[203,319,224],{"class":216},[203,321,227],{"class":216},[203,323,230],{"class":216},[203,325,326],{"class":233},"user",[203,328,224],{"class":216},[203,330,312],{"class":216},[203,332,230],{"class":216},[203,334,335],{"class":297},"content",[203,337,224],{"class":216},[203,339,227],{"class":216},[203,341,230],{"class":216},[203,343,344],{"class":233},"hi",[203,346,224],{"class":216},[203,348,349],{"class":209}," }]",[203,351,239],{"class":216},[203,353,355,357,360,362,364,367],{"class":205,"line":354},6,[203,356,217],{"class":216},[203,358,359],{"class":220},"tools",[203,361,224],{"class":216},[203,363,227],{"class":216},[203,365,366],{"class":209}," []",[203,368,239],{"class":216},[203,370,372,374,377,379,381,383],{"class":205,"line":371},7,[203,373,217],{"class":216},[203,375,376],{"class":220},"context",[203,378,224],{"class":216},[203,380,227],{"class":216},[203,382,366],{"class":209},[203,384,239],{"class":216},[203,386,388,390,393,395,397],{"class":205,"line":387},8,[203,389,217],{"class":216},[203,391,392],{"class":220},"forwardedProps",[203,394,224],{"class":216},[203,396,227],{"class":216},[203,398,399],{"class":209}," {}\n",[203,401,403],{"class":205,"line":402},9,[203,404,405],{"class":209},"}\n",[11,407,408,409,412],{},"The response is an SSE stream where each ",[189,410,411],{},"data:"," line is one event. Here is the real output from the demo server I built, trimmed:",[194,414,419],{"className":415,"code":417,"language":418,"meta":199},[416],"language-text","data: {\"type\": \"RUN_STARTED\", \"threadId\": \"t1\", \"runId\": \"r1\"}\n\ndata: {\"type\": \"STATE_SNAPSHOT\", \"snapshot\": {\"status\": \"thinking\", \"steps\": []}}\n\ndata: {\"type\": \"STEP_STARTED\", \"stepName\": \"plan\"}\n\ndata: {\"type\": \"TEXT_MESSAGE_START\", \"messageId\": \"94fc…\", \"role\": \"assistant\"}\n\ndata: {\"type\": \"TEXT_MESSAGE_CONTENT\", \"messageId\": \"94fc…\", \"delta\": \"You \"}\n\ndata: {\"type\": \"TEXT_MESSAGE_CONTENT\", \"messageId\": \"94fc…\", \"delta\": \"said: \"}\n","text",[189,420,417],{"__ignoreMap":199},[11,422,423],{},"That is the whole trick. Everything else is deciding which events to send.",[11,425,426],{},[87,427],{"alt":428,"height":429,"src":430,"width":92},"A request box labelled RunAgentInput feeds an ordered stream of events from RUN_STARTED to RUN_FINISHED, with a column on the right showing what the app does for each event.",1800,"https:\u002F\u002Fres.cloudinary.com\u002Fc552lpdm\u002Fimage\u002Fupload\u002Fevent-stream.webp",[11,432,433],{},"Two details worth knowing:",[435,436,437,456],"ul",{},[438,439,440,455],"li",{},[15,441,442,443,446,447,450,451,454],{},"Every run starts with ",[189,444,445],{},"RUN_STARTED"," and ends with exactly one of ",[189,448,449],{},"RUN_FINISHED"," or ",[189,452,453],{},"RUN_ERROR","."," That gives the UI reliable bookends for spinners and error states.",[438,457,458,461],{},[15,459,460],{},"The format is transport-agnostic."," SSE is the default, there is a binary option, and the docs mention WebSockets and webhooks too.",[46,463,465],{"id":464},"_4-the-event-families","4. The event families",[11,467,468],{},"The docs group events into families. These are the ones you will touch first:",[94,470,471,484],{},[97,472,473],{},[100,474,475,478,481],{},[103,476,477],{},"Family",[103,479,480],{},"Events",[103,482,483],{},"What the UI does with it",[113,485,486,506,530,552,571],{},[100,487,488,491,503],{},[118,489,490],{},"Lifecycle",[118,492,493,495,496,495,498,495,500],{},[189,494,445],{},", ",[189,497,449],{},[189,499,453],{},[189,501,502],{},"STEP_STARTED\u002FFINISHED",[118,504,505],{},"Spinners, progress, error banners",[100,507,508,511,523],{},[118,509,510],{},"Text",[118,512,513,516,517,516,520],{},[189,514,515],{},"TEXT_MESSAGE_START"," → ",[189,518,519],{},"CONTENT",[189,521,522],{},"END",[118,524,525,526,529],{},"Append ",[189,527,528],{},"delta"," to a chat bubble",[100,531,532,535,549],{},[118,533,534],{},"Tool calls",[118,536,537,516,540,516,543,545,546],{},[189,538,539],{},"TOOL_CALL_START",[189,541,542],{},"ARGS",[189,544,522],{},", then ",[189,547,548],{},"TOOL_CALL_RESULT",[118,550,551],{},"Show \"calling send_email…\"",[100,553,554,557,568],{},[118,555,556],{},"State",[118,558,559,495,562,495,565],{},[189,560,561],{},"STATE_SNAPSHOT",[189,563,564],{},"STATE_DELTA",[189,566,567],{},"MESSAGES_SNAPSHOT",[118,569,570],{},"Keep a shared state object in sync",[100,572,573,576,584],{},[118,574,575],{},"Special",[118,577,578,495,581],{},[189,579,580],{},"RAW",[189,582,583],{},"CUSTOM",[118,585,586],{},"Escape hatches for your own events",[11,588,589,590,593],{},"There are also activity events (plans, searches), reasoning events (visible summaries, which replace the older ",[189,591,592],{},"THINKING_*"," events that the docs list as deprecated), and subagent events that attribute output to child agents.",[11,595,596],{},"Three patterns repeat everywhere, and once you see them the protocol is easy to hold in your head:",[598,599,600,606,612],"ol",{},[438,601,602,605],{},[15,603,604],{},"Start → content → end"," for anything that streams (text, tool arguments).",[438,607,608,611],{},[15,609,610],{},"Snapshot → delta"," for state. Send the whole thing once, then send small updates.",[438,613,614,617],{},[15,615,616],{},"Started → finished\u002Ferror"," for anything with a lifecycle.",[11,619,620,621,624],{},"State deltas are plain ",[15,622,623],{},"JSON Patch"," (RFC 6902), so a change to one field is a few bytes instead of the whole object.",[46,626,628],{"id":627},"_5-build-it-a-server-with-no-sdk-and-no-api-key","5. Build it: a server with no SDK and no API key",[11,630,631],{},"I wanted to see the protocol, not an LLM. So the \"agent\" here is a fake: it streams a canned reply word by word. The wire format is real.",[11,633,634],{},"The server uses only the Python standard library. First, the one function that matters, which turns an event into an SSE frame:",[194,636,640],{"className":637,"code":638,"language":639,"meta":199,"style":199},"language-python shiki shiki-themes tokyo-night","def sse(event: dict) -> bytes:\n    return f\"data: {json.dumps(event)}\\n\\n\".encode(\"utf-8\")\n","python",[189,641,642,675],{"__ignoreMap":199},[203,643,644,648,651,654,658,660,663,666,669,672],{"class":205,"line":206},[203,645,647],{"class":646},"sd1Qi","def",[203,649,650],{"class":220}," sse",[203,652,653],{"class":209},"(",[203,655,657],{"class":656},"sT800","event",[203,659,227],{"class":216},[203,661,662],{"class":297}," dict",[203,664,665],{"class":209},")",[203,667,668],{"class":216}," ->",[203,670,671],{"class":297}," bytes",[203,673,674],{"class":209},":\n",[203,676,677,680,683,686,689,692,694,697,699,701,703,706,709,711,713,716,718,720,723,725],{"class":205,"line":213},[203,678,679],{"class":646},"    return",[203,681,682],{"class":646}," f",[203,684,685],{"class":233},"\"data: ",[203,687,688],{"class":646},"{",[203,690,198],{"class":691},"sE3pS",[203,693,454],{"class":216},[203,695,696],{"class":220},"dumps",[203,698,653],{"class":209},[203,700,657],{"class":691},[203,702,665],{"class":209},[203,704,705],{"class":646},"}",[203,707,708],{"class":216},"\\n\\n",[203,710,224],{"class":233},[203,712,454],{"class":216},[203,714,715],{"class":220},"encode",[203,717,653],{"class":209},[203,719,224],{"class":216},[203,721,722],{"class":233},"utf-8",[203,724,224],{"class":216},[203,726,727],{"class":209},")\n",[11,729,730],{},"Streaming text is the start → content → end pattern:",[194,732,734],{"className":637,"code":733,"language":639,"meta":199,"style":199},"def stream_text(message_id: str, text: str):\n    yield {\"type\": \"TEXT_MESSAGE_START\", \"messageId\": message_id, \"role\": \"assistant\"}\n    for word in text.split(\" \"):\n        yield {\"type\": \"TEXT_MESSAGE_CONTENT\", \"messageId\": message_id, \"delta\": word + \" \"}\n        time.sleep(0.05)  # pretend the model is generating\n    yield {\"type\": \"TEXT_MESSAGE_END\", \"messageId\": message_id}\n",[189,735,736,765,822,850,905,927],{"__ignoreMap":199},[203,737,738,740,743,745,748,750,753,755,758,760,762],{"class":205,"line":206},[203,739,647],{"class":646},[203,741,742],{"class":220}," stream_text",[203,744,653],{"class":209},[203,746,747],{"class":656},"message_id",[203,749,227],{"class":216},[203,751,752],{"class":297}," str",[203,754,312],{"class":216},[203,756,757],{"class":656}," text",[203,759,227],{"class":216},[203,761,752],{"class":297},[203,763,764],{"class":209},"):\n",[203,766,767,770,773,775,778,780,782,784,786,788,790,792,795,797,799,803,805,807,809,811,813,815,818,820],{"class":205,"line":213},[203,768,769],{"class":646},"    yield",[203,771,772],{"class":216}," {",[203,774,224],{"class":216},[203,776,777],{"class":233},"type",[203,779,224],{"class":216},[203,781,227],{"class":216},[203,783,230],{"class":216},[203,785,515],{"class":233},[203,787,224],{"class":216},[203,789,312],{"class":216},[203,791,230],{"class":216},[203,793,794],{"class":233},"messageId",[203,796,224],{"class":216},[203,798,227],{"class":216},[203,800,802],{"class":801},"sGX4V"," message_id",[203,804,312],{"class":216},[203,806,230],{"class":216},[203,808,317],{"class":233},[203,810,224],{"class":216},[203,812,227],{"class":216},[203,814,230],{"class":216},[203,816,817],{"class":233},"assistant",[203,819,224],{"class":216},[203,821,405],{"class":216},[203,823,824,827,830,833,835,837,840,842,844,846,848],{"class":205,"line":242},[203,825,826],{"class":646},"    for",[203,828,829],{"class":801}," word ",[203,831,832],{"class":646},"in",[203,834,757],{"class":801},[203,836,454],{"class":216},[203,838,839],{"class":220},"split",[203,841,653],{"class":209},[203,843,224],{"class":216},[203,845,230],{"class":216},[203,847,665],{"class":209},[203,849,674],{"class":216},[203,851,852,855,857,859,861,863,865,867,870,872,874,876,878,880,882,884,886,888,890,892,894,896,899,901,903],{"class":205,"line":263},[203,853,854],{"class":646},"        yield",[203,856,772],{"class":216},[203,858,224],{"class":216},[203,860,777],{"class":233},[203,862,224],{"class":216},[203,864,227],{"class":216},[203,866,230],{"class":216},[203,868,869],{"class":233},"TEXT_MESSAGE_CONTENT",[203,871,224],{"class":216},[203,873,312],{"class":216},[203,875,230],{"class":216},[203,877,794],{"class":233},[203,879,224],{"class":216},[203,881,227],{"class":216},[203,883,802],{"class":801},[203,885,312],{"class":216},[203,887,230],{"class":216},[203,889,528],{"class":233},[203,891,224],{"class":216},[203,893,227],{"class":216},[203,895,829],{"class":801},[203,897,898],{"class":216},"+",[203,900,230],{"class":216},[203,902,230],{"class":216},[203,904,405],{"class":216},[203,906,907,910,912,915,917,921,923],{"class":205,"line":280},[203,908,909],{"class":801},"        time",[203,911,454],{"class":216},[203,913,914],{"class":220},"sleep",[203,916,653],{"class":209},[203,918,920],{"class":919},"sOJ5S","0.05",[203,922,665],{"class":209},[203,924,926],{"class":925},"sbD-w","  # pretend the model is generating\n",[203,928,929,931,933,935,937,939,941,943,946,948,950,952,954,956,958,960],{"class":205,"line":354},[203,930,769],{"class":646},[203,932,772],{"class":216},[203,934,224],{"class":216},[203,936,777],{"class":233},[203,938,224],{"class":216},[203,940,227],{"class":216},[203,942,230],{"class":216},[203,944,945],{"class":233},"TEXT_MESSAGE_END",[203,947,224],{"class":216},[203,949,312],{"class":216},[203,951,230],{"class":216},[203,953,794],{"class":233},[203,955,224],{"class":216},[203,957,227],{"class":216},[203,959,802],{"class":801},[203,961,405],{"class":216},[11,963,964],{},"And a plain chat run is just a sequence of events between the two bookends:",[194,966,968],{"className":637,"code":967,"language":639,"meta":199,"style":199},"def run_events(body):\n    yield {\"type\": \"RUN_STARTED\", \"threadId\": body[\"threadId\"], \"runId\": body[\"runId\"]}\n    yield {\"type\": \"STATE_SNAPSHOT\", \"snapshot\": {\"status\": \"thinking\", \"steps\": []}}\n    yield {\"type\": \"STEP_STARTED\", \"stepName\": \"plan\"}\n    yield {\"type\": \"STATE_DELTA\", \"delta\": [\n        {\"op\": \"replace\", \"path\": \"\u002Fstatus\", \"value\": \"writing\"},\n        {\"op\": \"add\", \"path\": \"\u002Fsteps\u002F-\", \"value\": \"plan\"},\n    ]}\n    yield {\"type\": \"STEP_FINISHED\", \"stepName\": \"plan\"}\n    yield from stream_text(str(uuid.uuid4()), \"This reply is streamed as AG-UI events.\")\n    yield {\"type\": \"RUN_FINISHED\", \"threadId\": body[\"threadId\"], \"runId\": body[\"runId\"],\n           \"outcome\": {\"type\": \"success\"}}\n",[189,969,970,984,1053,1116,1157,1190,1250,1304,1309,1348,1385,1452],{"__ignoreMap":199},[203,971,972,974,977,979,982],{"class":205,"line":206},[203,973,647],{"class":646},[203,975,976],{"class":220}," run_events",[203,978,653],{"class":209},[203,980,981],{"class":656},"body",[203,983,764],{"class":209},[203,985,986,988,990,992,994,996,998,1000,1002,1004,1006,1008,1010,1012,1014,1017,1020,1022,1024,1026,1029,1031,1033,1035,1037,1039,1041,1043,1045,1047,1049,1051],{"class":205,"line":213},[203,987,769],{"class":646},[203,989,772],{"class":216},[203,991,224],{"class":216},[203,993,777],{"class":233},[203,995,224],{"class":216},[203,997,227],{"class":216},[203,999,230],{"class":216},[203,1001,445],{"class":233},[203,1003,224],{"class":216},[203,1005,312],{"class":216},[203,1007,230],{"class":216},[203,1009,221],{"class":233},[203,1011,224],{"class":216},[203,1013,227],{"class":216},[203,1015,1016],{"class":801}," body",[203,1018,1019],{"class":209},"[",[203,1021,224],{"class":216},[203,1023,221],{"class":233},[203,1025,224],{"class":216},[203,1027,1028],{"class":209},"]",[203,1030,312],{"class":216},[203,1032,230],{"class":216},[203,1034,247],{"class":233},[203,1036,224],{"class":216},[203,1038,227],{"class":216},[203,1040,1016],{"class":801},[203,1042,1019],{"class":209},[203,1044,224],{"class":216},[203,1046,247],{"class":233},[203,1048,224],{"class":216},[203,1050,1028],{"class":209},[203,1052,405],{"class":216},[203,1054,1055,1057,1059,1061,1063,1065,1067,1069,1071,1073,1075,1077,1080,1082,1084,1086,1088,1091,1093,1095,1097,1100,1102,1104,1106,1109,1111,1113],{"class":205,"line":242},[203,1056,769],{"class":646},[203,1058,772],{"class":216},[203,1060,224],{"class":216},[203,1062,777],{"class":233},[203,1064,224],{"class":216},[203,1066,227],{"class":216},[203,1068,230],{"class":216},[203,1070,561],{"class":233},[203,1072,224],{"class":216},[203,1074,312],{"class":216},[203,1076,230],{"class":216},[203,1078,1079],{"class":233},"snapshot",[203,1081,224],{"class":216},[203,1083,227],{"class":216},[203,1085,772],{"class":216},[203,1087,224],{"class":216},[203,1089,1090],{"class":233},"status",[203,1092,224],{"class":216},[203,1094,227],{"class":216},[203,1096,230],{"class":216},[203,1098,1099],{"class":233},"thinking",[203,1101,224],{"class":216},[203,1103,312],{"class":216},[203,1105,230],{"class":216},[203,1107,1108],{"class":233},"steps",[203,1110,224],{"class":216},[203,1112,227],{"class":216},[203,1114,1115],{"class":216}," []}}\n",[203,1117,1118,1120,1122,1124,1126,1128,1130,1132,1135,1137,1139,1141,1144,1146,1148,1150,1153,1155],{"class":205,"line":263},[203,1119,769],{"class":646},[203,1121,772],{"class":216},[203,1123,224],{"class":216},[203,1125,777],{"class":233},[203,1127,224],{"class":216},[203,1129,227],{"class":216},[203,1131,230],{"class":216},[203,1133,1134],{"class":233},"STEP_STARTED",[203,1136,224],{"class":216},[203,1138,312],{"class":216},[203,1140,230],{"class":216},[203,1142,1143],{"class":233},"stepName",[203,1145,224],{"class":216},[203,1147,227],{"class":216},[203,1149,230],{"class":216},[203,1151,1152],{"class":233},"plan",[203,1154,224],{"class":216},[203,1156,405],{"class":216},[203,1158,1159,1161,1163,1165,1167,1169,1171,1173,1175,1177,1179,1181,1183,1185,1187],{"class":205,"line":280},[203,1160,769],{"class":646},[203,1162,772],{"class":216},[203,1164,224],{"class":216},[203,1166,777],{"class":233},[203,1168,224],{"class":216},[203,1170,227],{"class":216},[203,1172,230],{"class":216},[203,1174,564],{"class":233},[203,1176,224],{"class":216},[203,1178,312],{"class":216},[203,1180,230],{"class":216},[203,1182,528],{"class":233},[203,1184,224],{"class":216},[203,1186,227],{"class":216},[203,1188,1189],{"class":216}," [\n",[203,1191,1192,1195,1197,1200,1202,1204,1206,1209,1211,1213,1215,1218,1220,1222,1224,1227,1229,1231,1233,1236,1238,1240,1242,1245,1247],{"class":205,"line":354},[203,1193,1194],{"class":216},"        {",[203,1196,224],{"class":216},[203,1198,1199],{"class":233},"op",[203,1201,224],{"class":216},[203,1203,227],{"class":216},[203,1205,230],{"class":216},[203,1207,1208],{"class":233},"replace",[203,1210,224],{"class":216},[203,1212,312],{"class":216},[203,1214,230],{"class":216},[203,1216,1217],{"class":233},"path",[203,1219,224],{"class":216},[203,1221,227],{"class":216},[203,1223,230],{"class":216},[203,1225,1226],{"class":233},"\u002Fstatus",[203,1228,224],{"class":216},[203,1230,312],{"class":216},[203,1232,230],{"class":216},[203,1234,1235],{"class":233},"value",[203,1237,224],{"class":216},[203,1239,227],{"class":216},[203,1241,230],{"class":216},[203,1243,1244],{"class":233},"writing",[203,1246,224],{"class":216},[203,1248,1249],{"class":216},"},\n",[203,1251,1252,1254,1256,1258,1260,1262,1264,1267,1269,1271,1273,1275,1277,1279,1281,1284,1286,1288,1290,1292,1294,1296,1298,1300,1302],{"class":205,"line":371},[203,1253,1194],{"class":216},[203,1255,224],{"class":216},[203,1257,1199],{"class":233},[203,1259,224],{"class":216},[203,1261,227],{"class":216},[203,1263,230],{"class":216},[203,1265,1266],{"class":233},"add",[203,1268,224],{"class":216},[203,1270,312],{"class":216},[203,1272,230],{"class":216},[203,1274,1217],{"class":233},[203,1276,224],{"class":216},[203,1278,227],{"class":216},[203,1280,230],{"class":216},[203,1282,1283],{"class":233},"\u002Fsteps\u002F-",[203,1285,224],{"class":216},[203,1287,312],{"class":216},[203,1289,230],{"class":216},[203,1291,1235],{"class":233},[203,1293,224],{"class":216},[203,1295,227],{"class":216},[203,1297,230],{"class":216},[203,1299,1152],{"class":233},[203,1301,224],{"class":216},[203,1303,1249],{"class":216},[203,1305,1306],{"class":205,"line":387},[203,1307,1308],{"class":216},"    ]}\n",[203,1310,1311,1313,1315,1317,1319,1321,1323,1325,1328,1330,1332,1334,1336,1338,1340,1342,1344,1346],{"class":205,"line":402},[203,1312,769],{"class":646},[203,1314,772],{"class":216},[203,1316,224],{"class":216},[203,1318,777],{"class":233},[203,1320,224],{"class":216},[203,1322,227],{"class":216},[203,1324,230],{"class":216},[203,1326,1327],{"class":233},"STEP_FINISHED",[203,1329,224],{"class":216},[203,1331,312],{"class":216},[203,1333,230],{"class":216},[203,1335,1143],{"class":233},[203,1337,224],{"class":216},[203,1339,227],{"class":216},[203,1341,230],{"class":216},[203,1343,1152],{"class":233},[203,1345,224],{"class":216},[203,1347,405],{"class":216},[203,1349,1351,1354,1356,1358,1361,1363,1366,1368,1371,1374,1376,1378,1381,1383],{"class":205,"line":1350},10,[203,1352,1353],{"class":646},"    yield from",[203,1355,742],{"class":220},[203,1357,653],{"class":209},[203,1359,1360],{"class":297},"str",[203,1362,653],{"class":209},[203,1364,1365],{"class":691},"uuid",[203,1367,454],{"class":216},[203,1369,1370],{"class":220},"uuid4",[203,1372,1373],{"class":209},"())",[203,1375,312],{"class":216},[203,1377,230],{"class":216},[203,1379,1380],{"class":233},"This reply is streamed as AG-UI events.",[203,1382,224],{"class":216},[203,1384,727],{"class":209},[203,1386,1388,1390,1392,1394,1396,1398,1400,1402,1404,1406,1408,1410,1412,1414,1416,1418,1420,1422,1424,1426,1428,1430,1432,1434,1436,1438,1440,1442,1444,1446,1448,1450],{"class":205,"line":1387},11,[203,1389,769],{"class":646},[203,1391,772],{"class":216},[203,1393,224],{"class":216},[203,1395,777],{"class":233},[203,1397,224],{"class":216},[203,1399,227],{"class":216},[203,1401,230],{"class":216},[203,1403,449],{"class":233},[203,1405,224],{"class":216},[203,1407,312],{"class":216},[203,1409,230],{"class":216},[203,1411,221],{"class":233},[203,1413,224],{"class":216},[203,1415,227],{"class":216},[203,1417,1016],{"class":801},[203,1419,1019],{"class":209},[203,1421,224],{"class":216},[203,1423,221],{"class":233},[203,1425,224],{"class":216},[203,1427,1028],{"class":209},[203,1429,312],{"class":216},[203,1431,230],{"class":216},[203,1433,247],{"class":233},[203,1435,224],{"class":216},[203,1437,227],{"class":216},[203,1439,1016],{"class":801},[203,1441,1019],{"class":209},[203,1443,224],{"class":216},[203,1445,247],{"class":233},[203,1447,224],{"class":216},[203,1449,1028],{"class":209},[203,1451,239],{"class":216},[203,1453,1455,1458,1461,1463,1465,1467,1469,1471,1473,1475,1477,1480,1482],{"class":205,"line":1454},12,[203,1456,1457],{"class":216},"           \"",[203,1459,1460],{"class":233},"outcome",[203,1462,224],{"class":216},[203,1464,227],{"class":216},[203,1466,772],{"class":216},[203,1468,224],{"class":216},[203,1470,777],{"class":233},[203,1472,224],{"class":216},[203,1474,227],{"class":216},[203,1476,230],{"class":216},[203,1478,1479],{"class":233},"success",[203,1481,224],{"class":216},[203,1483,1484],{"class":216},"}}\n",[11,1486,1487,1488,1491],{},"Wrap that in an HTTP handler that sets ",[189,1489,1490],{},"Content-Type: text\u002Fevent-stream"," and flushes after each frame, and you have an AG-UI-shaped endpoint.",[46,1493,1495],{"id":1494},"_6-the-client-it-is-just-a-reducer","6. The client: it is just a reducer",[11,1497,1498,1499,1502],{},"Same trick as my chat stream: ",[189,1500,1501],{},"fetch"," the body, buffer it, split on a blank line, parse each frame. A network chunk can end mid-frame, so you keep the leftover for the next read.",[194,1504,1508],{"className":1505,"code":1506,"language":1507,"meta":199,"style":199},"language-js shiki shiki-themes tokyo-night","async function* events(res) {\n  const reader = res.body.getReader();\n  const decoder = new TextDecoder();\n  let buffer = '';\n  while (true) {\n    const { value, done } = await reader.read();\n    if (done) return;\n    buffer += decoder.decode(value, { stream: true }).replace(\u002F\\r\\n\u002Fg, '\\n');\n    let sep;\n    while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\n      const frame = buffer.slice(0, sep);\n      buffer = buffer.slice(sep + 2);\n      const data = frame\n        .split('\\n')\n        .filter((l) => l.startsWith('data:'))\n        .map((l) => l.slice(5).trimStart())\n        .join('\\n');\n      if (data) yield JSON.parse(data);\n    }\n  }\n}\n","js",[189,1509,1510,1532,1562,1581,1596,1610,1645,1663,1723,1733,1772,1802,1829,1842,1857,1896,1933,1949,1981,1987,1993],{"__ignoreMap":199},[203,1511,1512,1516,1519,1522,1524,1527,1529],{"class":205,"line":206},[203,1513,1515],{"class":1514},"sN7LL","async",[203,1517,1518],{"class":646}," function*",[203,1520,1521],{"class":220}," events",[203,1523,653],{"class":209},[203,1525,1526],{"class":656},"res",[203,1528,665],{"class":209},[203,1530,1531],{"class":209}," {\n",[203,1533,1534,1537,1540,1543,1546,1548,1551,1553,1556,1559],{"class":205,"line":213},[203,1535,1536],{"class":1514},"  const",[203,1538,1539],{"class":646}," reader",[203,1541,1542],{"class":216}," =",[203,1544,1545],{"class":691}," res",[203,1547,454],{"class":216},[203,1549,981],{"class":1550},"su0L4",[203,1552,454],{"class":216},[203,1554,1555],{"class":220},"getReader",[203,1557,1558],{"class":209},"()",[203,1560,1561],{"class":216},";\n",[203,1563,1564,1566,1569,1571,1574,1577,1579],{"class":205,"line":242},[203,1565,1536],{"class":1514},[203,1567,1568],{"class":646}," decoder",[203,1570,1542],{"class":216},[203,1572,1573],{"class":216}," new",[203,1575,1576],{"class":220}," TextDecoder",[203,1578,1558],{"class":209},[203,1580,1561],{"class":216},[203,1582,1583,1586,1589,1591,1594],{"class":205,"line":263},[203,1584,1585],{"class":1514},"  let",[203,1587,1588],{"class":646}," buffer",[203,1590,1542],{"class":216},[203,1592,1593],{"class":216}," ''",[203,1595,1561],{"class":216},[203,1597,1598,1601,1604,1607],{"class":205,"line":280},[203,1599,1600],{"class":646},"  while",[203,1602,1603],{"class":209}," (",[203,1605,1606],{"class":919},"true",[203,1608,1609],{"class":209},") {\n",[203,1611,1612,1615,1617,1620,1622,1625,1628,1630,1634,1636,1638,1641,1643],{"class":205,"line":354},[203,1613,1614],{"class":1514},"    const",[203,1616,772],{"class":216},[203,1618,1619],{"class":646}," value",[203,1621,312],{"class":216},[203,1623,1624],{"class":646}," done",[203,1626,1627],{"class":216}," }",[203,1629,1542],{"class":216},[203,1631,1633],{"class":1632},"sEsAJ"," await",[203,1635,1539],{"class":691},[203,1637,454],{"class":216},[203,1639,1640],{"class":220},"read",[203,1642,1558],{"class":209},[203,1644,1561],{"class":216},[203,1646,1647,1650,1652,1655,1658,1661],{"class":205,"line":371},[203,1648,1649],{"class":646},"    if",[203,1651,1603],{"class":209},[203,1653,1654],{"class":691},"done",[203,1656,1657],{"class":209},") ",[203,1659,1660],{"class":1632},"return",[203,1662,1561],{"class":216},[203,1664,1665,1668,1671,1673,1675,1678,1680,1682,1684,1687,1691,1693,1696,1699,1701,1703,1705,1708,1711,1714,1716,1719,1721],{"class":205,"line":387},[203,1666,1667],{"class":691},"    buffer",[203,1669,1670],{"class":216}," +=",[203,1672,1568],{"class":691},[203,1674,454],{"class":216},[203,1676,1677],{"class":220},"decode",[203,1679,653],{"class":209},[203,1681,1235],{"class":691},[203,1683,312],{"class":216},[203,1685,1686],{"class":209}," { ",[203,1688,1690],{"class":1689},"syYvs","stream",[203,1692,227],{"class":216},[203,1694,1695],{"class":919}," true",[203,1697,1698],{"class":209}," })",[203,1700,454],{"class":216},[203,1702,1208],{"class":220},[203,1704,653],{"class":209},[203,1706,1707],{"class":216},"\u002F",[203,1709,1710],{"class":646},"\\r\\n",[203,1712,1713],{"class":216},"\u002Fg",[203,1715,312],{"class":216},[203,1717,1718],{"class":216}," '\\n'",[203,1720,665],{"class":209},[203,1722,1561],{"class":216},[203,1724,1725,1728,1731],{"class":205,"line":402},[203,1726,1727],{"class":1514},"    let",[203,1729,1730],{"class":646}," sep",[203,1732,1561],{"class":216},[203,1734,1735,1738,1741,1744,1746,1748,1750,1753,1755,1758,1761,1764,1767,1770],{"class":205,"line":1350},[203,1736,1737],{"class":646},"    while",[203,1739,1740],{"class":209}," ((",[203,1742,1743],{"class":691},"sep",[203,1745,1542],{"class":216},[203,1747,1588],{"class":691},[203,1749,454],{"class":216},[203,1751,1752],{"class":220},"indexOf",[203,1754,653],{"class":209},[203,1756,1757],{"class":216},"'\\n\\n'",[203,1759,1760],{"class":209},")) ",[203,1762,1763],{"class":646},"!==",[203,1765,1766],{"class":216}," -",[203,1768,1769],{"class":919},"1",[203,1771,1609],{"class":209},[203,1773,1774,1777,1780,1782,1784,1786,1789,1791,1794,1796,1798,1800],{"class":205,"line":1387},[203,1775,1776],{"class":1514},"      const",[203,1778,1779],{"class":646}," frame",[203,1781,1542],{"class":216},[203,1783,1588],{"class":691},[203,1785,454],{"class":216},[203,1787,1788],{"class":220},"slice",[203,1790,653],{"class":209},[203,1792,1793],{"class":919},"0",[203,1795,312],{"class":216},[203,1797,1730],{"class":691},[203,1799,665],{"class":209},[203,1801,1561],{"class":216},[203,1803,1804,1807,1809,1811,1813,1815,1817,1819,1822,1825,1827],{"class":205,"line":1454},[203,1805,1806],{"class":691},"      buffer",[203,1808,1542],{"class":216},[203,1810,1588],{"class":691},[203,1812,454],{"class":216},[203,1814,1788],{"class":220},[203,1816,653],{"class":209},[203,1818,1743],{"class":691},[203,1820,1821],{"class":216}," +",[203,1823,1824],{"class":919}," 2",[203,1826,665],{"class":209},[203,1828,1561],{"class":216},[203,1830,1832,1834,1837,1839],{"class":205,"line":1831},13,[203,1833,1776],{"class":1514},[203,1835,1836],{"class":646}," data",[203,1838,1542],{"class":216},[203,1840,1841],{"class":691}," frame\n",[203,1843,1845,1848,1850,1852,1855],{"class":205,"line":1844},14,[203,1846,1847],{"class":216},"        .",[203,1849,839],{"class":220},[203,1851,653],{"class":209},[203,1853,1854],{"class":216},"'\\n'",[203,1856,727],{"class":209},[203,1858,1860,1862,1865,1868,1871,1873,1876,1879,1881,1884,1886,1889,1891,1893],{"class":205,"line":1859},15,[203,1861,1847],{"class":216},[203,1863,1864],{"class":220},"filter",[203,1866,1867],{"class":209},"((",[203,1869,1870],{"class":656},"l",[203,1872,1657],{"class":209},[203,1874,1875],{"class":646},"=>",[203,1877,1878],{"class":691}," l",[203,1880,454],{"class":216},[203,1882,1883],{"class":220},"startsWith",[203,1885,653],{"class":209},[203,1887,1888],{"class":216},"'",[203,1890,411],{"class":233},[203,1892,1888],{"class":216},[203,1894,1895],{"class":209},"))\n",[203,1897,1899,1901,1904,1906,1908,1910,1912,1914,1916,1918,1920,1923,1925,1927,1930],{"class":205,"line":1898},16,[203,1900,1847],{"class":216},[203,1902,1903],{"class":220},"map",[203,1905,1867],{"class":209},[203,1907,1870],{"class":656},[203,1909,1657],{"class":209},[203,1911,1875],{"class":646},[203,1913,1878],{"class":691},[203,1915,454],{"class":216},[203,1917,1788],{"class":220},[203,1919,653],{"class":209},[203,1921,1922],{"class":919},"5",[203,1924,665],{"class":209},[203,1926,454],{"class":216},[203,1928,1929],{"class":220},"trimStart",[203,1931,1932],{"class":209},"())\n",[203,1934,1936,1938,1941,1943,1945,1947],{"class":205,"line":1935},17,[203,1937,1847],{"class":216},[203,1939,1940],{"class":220},"join",[203,1942,653],{"class":209},[203,1944,1854],{"class":216},[203,1946,665],{"class":209},[203,1948,1561],{"class":216},[203,1950,1952,1955,1957,1960,1962,1965,1968,1970,1973,1975,1977,1979],{"class":205,"line":1951},18,[203,1953,1954],{"class":646},"      if",[203,1956,1603],{"class":209},[203,1958,1959],{"class":691},"data",[203,1961,1657],{"class":209},[203,1963,1964],{"class":1632},"yield",[203,1966,1967],{"class":297}," JSON",[203,1969,454],{"class":216},[203,1971,1972],{"class":220},"parse",[203,1974,653],{"class":209},[203,1976,1959],{"class":691},[203,1978,665],{"class":209},[203,1980,1561],{"class":216},[203,1982,1984],{"class":205,"line":1983},19,[203,1985,1986],{"class":209},"    }\n",[203,1988,1990],{"class":205,"line":1989},20,[203,1991,1992],{"class":209},"  }\n",[203,1994,1996],{"class":205,"line":1995},21,[203,1997,405],{"class":209},[11,1999,2000,2001,2004,2005,2008],{},"Then the UI is a ",[189,2002,2003],{},"switch"," on ",[189,2006,2007],{},"event.type",". This is the part I like most: your frontend becomes a reducer over a stream.",[194,2010,2012],{"className":1505,"code":2011,"language":1507,"meta":199,"style":199},"for await (const e of events(res)) {\n  switch (e.type) {\n    case 'TEXT_MESSAGE_CONTENT':\n      bubble += e.delta;\n      break;\n    case 'TOOL_CALL_ARGS':\n      toolArgs[e.toolCallId] += e.delta;\n      break;\n    case 'STATE_SNAPSHOT':\n      state = e.snapshot;\n      break;\n    case 'STATE_DELTA':\n      state = applyPatch(state, e.delta);\n      break;\n    case 'RUN_ERROR':\n      showError(e.message);\n      break;\n  }\n}\n",[189,2013,2014,2043,2059,2073,2088,2095,2108,2136,2142,2154,2169,2175,2187,2212,2218,2230,2248,2254,2258],{"__ignoreMap":199},[203,2015,2016,2019,2021,2023,2026,2029,2032,2034,2036,2038,2041],{"class":205,"line":206},[203,2017,2018],{"class":646},"for",[203,2020,1633],{"class":646},[203,2022,1603],{"class":209},[203,2024,2025],{"class":1514},"const",[203,2027,2028],{"class":646}," e",[203,2030,2031],{"class":216}," of",[203,2033,1521],{"class":220},[203,2035,653],{"class":209},[203,2037,1526],{"class":691},[203,2039,2040],{"class":209},"))",[203,2042,1531],{"class":209},[203,2044,2045,2048,2050,2053,2055,2057],{"class":205,"line":213},[203,2046,2047],{"class":646},"  switch",[203,2049,1603],{"class":209},[203,2051,2052],{"class":691},"e",[203,2054,454],{"class":216},[203,2056,777],{"class":1550},[203,2058,1609],{"class":209},[203,2060,2061,2064,2067,2069,2071],{"class":205,"line":242},[203,2062,2063],{"class":646},"    case",[203,2065,2066],{"class":216}," '",[203,2068,869],{"class":233},[203,2070,1888],{"class":216},[203,2072,674],{"class":216},[203,2074,2075,2078,2080,2082,2084,2086],{"class":205,"line":263},[203,2076,2077],{"class":691},"      bubble",[203,2079,1670],{"class":216},[203,2081,2028],{"class":691},[203,2083,454],{"class":216},[203,2085,528],{"class":1550},[203,2087,1561],{"class":216},[203,2089,2090,2093],{"class":205,"line":280},[203,2091,2092],{"class":646},"      break",[203,2094,1561],{"class":216},[203,2096,2097,2099,2101,2104,2106],{"class":205,"line":354},[203,2098,2063],{"class":646},[203,2100,2066],{"class":216},[203,2102,2103],{"class":233},"TOOL_CALL_ARGS",[203,2105,1888],{"class":216},[203,2107,674],{"class":216},[203,2109,2110,2113,2115,2117,2119,2122,2125,2128,2130,2132,2134],{"class":205,"line":371},[203,2111,2112],{"class":691},"      toolArgs",[203,2114,1019],{"class":209},[203,2116,2052],{"class":691},[203,2118,454],{"class":216},[203,2120,2121],{"class":1550},"toolCallId",[203,2123,2124],{"class":209},"] ",[203,2126,2127],{"class":216},"+=",[203,2129,2028],{"class":691},[203,2131,454],{"class":216},[203,2133,528],{"class":1550},[203,2135,1561],{"class":216},[203,2137,2138,2140],{"class":205,"line":387},[203,2139,2092],{"class":646},[203,2141,1561],{"class":216},[203,2143,2144,2146,2148,2150,2152],{"class":205,"line":402},[203,2145,2063],{"class":646},[203,2147,2066],{"class":216},[203,2149,561],{"class":233},[203,2151,1888],{"class":216},[203,2153,674],{"class":216},[203,2155,2156,2159,2161,2163,2165,2167],{"class":205,"line":1350},[203,2157,2158],{"class":691},"      state",[203,2160,1542],{"class":216},[203,2162,2028],{"class":691},[203,2164,454],{"class":216},[203,2166,1079],{"class":1550},[203,2168,1561],{"class":216},[203,2170,2171,2173],{"class":205,"line":1387},[203,2172,2092],{"class":646},[203,2174,1561],{"class":216},[203,2176,2177,2179,2181,2183,2185],{"class":205,"line":1454},[203,2178,2063],{"class":646},[203,2180,2066],{"class":216},[203,2182,564],{"class":233},[203,2184,1888],{"class":216},[203,2186,674],{"class":216},[203,2188,2189,2191,2193,2196,2198,2200,2202,2204,2206,2208,2210],{"class":205,"line":1831},[203,2190,2158],{"class":691},[203,2192,1542],{"class":216},[203,2194,2195],{"class":220}," applyPatch",[203,2197,653],{"class":209},[203,2199,268],{"class":691},[203,2201,312],{"class":216},[203,2203,2028],{"class":691},[203,2205,454],{"class":216},[203,2207,528],{"class":1550},[203,2209,665],{"class":209},[203,2211,1561],{"class":216},[203,2213,2214,2216],{"class":205,"line":1844},[203,2215,2092],{"class":646},[203,2217,1561],{"class":216},[203,2219,2220,2222,2224,2226,2228],{"class":205,"line":1859},[203,2221,2063],{"class":646},[203,2223,2066],{"class":216},[203,2225,453],{"class":233},[203,2227,1888],{"class":216},[203,2229,674],{"class":216},[203,2231,2232,2235,2237,2239,2241,2244,2246],{"class":205,"line":1898},[203,2233,2234],{"class":220},"      showError",[203,2236,653],{"class":209},[203,2238,2052],{"class":691},[203,2240,454],{"class":216},[203,2242,2243],{"class":1550},"message",[203,2245,665],{"class":209},[203,2247,1561],{"class":216},[203,2249,2250,2252],{"class":205,"line":1935},[203,2251,2092],{"class":646},[203,2253,1561],{"class":216},[203,2255,2256],{"class":205,"line":1951},[203,2257,1992],{"class":209},[203,2259,2260],{"class":205,"line":1983},[203,2261,405],{"class":209},[11,2263,2264],{},"Running it against the server prints the state evolving as events arrive:",[194,2266,2269],{"className":2267,"code":2268,"language":418,"meta":199},[416],"[run run-1 started]\n  state = {\"status\":\"thinking\",\"steps\":[]}\n  step: plan\n  state -> {\"status\":\"writing\",\"steps\":[\"plan\"]}\n  assistant: You said: 'hello there'. This reply is streamed as AG-UI events.\n  state -> {\"status\":\"done\",\"steps\":[\"plan\"]}\n[run finished: success]\n",[189,2270,2268],{"__ignoreMap":199},[46,2272,2274],{"id":2273},"_7-human-in-the-loop-interrupts","7. Human in the loop: interrupts",[11,2276,2277],{},"This is the feature that made me take the protocol seriously. Agents that send emails, move money or delete things need a human to say yes first.",[11,2279,2280,2281,2284],{},"AG-UI models this without any special \"pause\" machinery. The run simply ",[15,2282,2283],{},"ends",", and the final event says why:",[194,2286,2288],{"className":196,"code":2287,"language":198,"meta":199,"style":199},"{\n  \"type\": \"RUN_FINISHED\",\n  \"threadId\": \"thread-1\",\n  \"runId\": \"run-1\",\n  \"outcome\": {\n    \"type\": \"interrupt\",\n    \"interrupts\": [\n      {\n        \"id\": \"int-1\",\n        \"reason\": \"tool_call\",\n        \"message\": \"Send email to a@b.com with subject 'Hi'?\",\n        \"toolCallId\": \"tc-email-1\",\n        \"responseSchema\": {\n          \"type\": \"object\",\n          \"properties\": { \"approved\": { \"type\": \"boolean\" } },\n          \"required\": [\"approved\"]\n        }\n      }\n    ]\n  }\n}\n",[189,2289,2290,2294,2312,2330,2348,2360,2380,2393,2398,2418,2438,2457,2476,2489,2509,2554,2577,2582,2587,2592,2596],{"__ignoreMap":199},[203,2291,2292],{"class":205,"line":206},[203,2293,210],{"class":209},[203,2295,2296,2298,2300,2302,2304,2306,2308,2310],{"class":205,"line":213},[203,2297,217],{"class":216},[203,2299,777],{"class":220},[203,2301,224],{"class":216},[203,2303,227],{"class":216},[203,2305,230],{"class":216},[203,2307,449],{"class":233},[203,2309,224],{"class":216},[203,2311,239],{"class":216},[203,2313,2314,2316,2318,2320,2322,2324,2326,2328],{"class":205,"line":242},[203,2315,217],{"class":216},[203,2317,221],{"class":220},[203,2319,224],{"class":216},[203,2321,227],{"class":216},[203,2323,230],{"class":216},[203,2325,234],{"class":233},[203,2327,224],{"class":216},[203,2329,239],{"class":216},[203,2331,2332,2334,2336,2338,2340,2342,2344,2346],{"class":205,"line":263},[203,2333,217],{"class":216},[203,2335,247],{"class":220},[203,2337,224],{"class":216},[203,2339,227],{"class":216},[203,2341,230],{"class":216},[203,2343,256],{"class":233},[203,2345,224],{"class":216},[203,2347,239],{"class":216},[203,2349,2350,2352,2354,2356,2358],{"class":205,"line":280},[203,2351,217],{"class":216},[203,2353,1460],{"class":220},[203,2355,224],{"class":216},[203,2357,227],{"class":216},[203,2359,1531],{"class":209},[203,2361,2362,2365,2367,2369,2371,2373,2376,2378],{"class":205,"line":354},[203,2363,2364],{"class":216},"    \"",[203,2366,777],{"class":297},[203,2368,224],{"class":216},[203,2370,227],{"class":216},[203,2372,230],{"class":216},[203,2374,2375],{"class":233},"interrupt",[203,2377,224],{"class":216},[203,2379,239],{"class":216},[203,2381,2382,2384,2387,2389,2391],{"class":205,"line":371},[203,2383,2364],{"class":216},[203,2385,2386],{"class":297},"interrupts",[203,2388,224],{"class":216},[203,2390,227],{"class":216},[203,2392,1189],{"class":209},[203,2394,2395],{"class":205,"line":387},[203,2396,2397],{"class":209},"      {\n",[203,2399,2400,2403,2405,2407,2409,2411,2414,2416],{"class":205,"line":402},[203,2401,2402],{"class":216},"        \"",[203,2404,298],{"class":1550},[203,2406,224],{"class":216},[203,2408,227],{"class":216},[203,2410,230],{"class":216},[203,2412,2413],{"class":233},"int-1",[203,2415,224],{"class":216},[203,2417,239],{"class":216},[203,2419,2420,2422,2425,2427,2429,2431,2434,2436],{"class":205,"line":1350},[203,2421,2402],{"class":216},[203,2423,2424],{"class":1550},"reason",[203,2426,224],{"class":216},[203,2428,227],{"class":216},[203,2430,230],{"class":216},[203,2432,2433],{"class":233},"tool_call",[203,2435,224],{"class":216},[203,2437,239],{"class":216},[203,2439,2440,2442,2444,2446,2448,2450,2453,2455],{"class":205,"line":1387},[203,2441,2402],{"class":216},[203,2443,2243],{"class":1550},[203,2445,224],{"class":216},[203,2447,227],{"class":216},[203,2449,230],{"class":216},[203,2451,2452],{"class":233},"Send email to a@b.com with subject 'Hi'?",[203,2454,224],{"class":216},[203,2456,239],{"class":216},[203,2458,2459,2461,2463,2465,2467,2469,2472,2474],{"class":205,"line":1454},[203,2460,2402],{"class":216},[203,2462,2121],{"class":1550},[203,2464,224],{"class":216},[203,2466,227],{"class":216},[203,2468,230],{"class":216},[203,2470,2471],{"class":233},"tc-email-1",[203,2473,224],{"class":216},[203,2475,239],{"class":216},[203,2477,2478,2480,2483,2485,2487],{"class":205,"line":1831},[203,2479,2402],{"class":216},[203,2481,2482],{"class":1550},"responseSchema",[203,2484,224],{"class":216},[203,2486,227],{"class":216},[203,2488,1531],{"class":209},[203,2490,2491,2494,2496,2498,2500,2502,2505,2507],{"class":205,"line":1844},[203,2492,2493],{"class":216},"          \"",[203,2495,777],{"class":646},[203,2497,224],{"class":216},[203,2499,227],{"class":216},[203,2501,230],{"class":216},[203,2503,2504],{"class":233},"object",[203,2506,224],{"class":216},[203,2508,239],{"class":216},[203,2510,2511,2513,2516,2518,2520,2522,2524,2527,2529,2531,2533,2535,2537,2539,2541,2543,2546,2548,2550,2552],{"class":205,"line":1859},[203,2512,2493],{"class":216},[203,2514,2515],{"class":646},"properties",[203,2517,224],{"class":216},[203,2519,227],{"class":216},[203,2521,772],{"class":209},[203,2523,230],{"class":216},[203,2525,2526],{"class":656},"approved",[203,2528,224],{"class":216},[203,2530,227],{"class":216},[203,2532,772],{"class":209},[203,2534,230],{"class":216},[203,2536,777],{"class":297},[203,2538,224],{"class":216},[203,2540,227],{"class":216},[203,2542,230],{"class":216},[203,2544,2545],{"class":233},"boolean",[203,2547,224],{"class":216},[203,2549,1627],{"class":209},[203,2551,1627],{"class":209},[203,2553,239],{"class":216},[203,2555,2556,2558,2561,2563,2565,2568,2570,2572,2574],{"class":205,"line":1898},[203,2557,2493],{"class":216},[203,2559,2560],{"class":646},"required",[203,2562,224],{"class":216},[203,2564,227],{"class":216},[203,2566,2567],{"class":209}," [",[203,2569,224],{"class":216},[203,2571,2526],{"class":233},[203,2573,224],{"class":216},[203,2575,2576],{"class":209},"]\n",[203,2578,2579],{"class":205,"line":1935},[203,2580,2581],{"class":209},"        }\n",[203,2583,2584],{"class":205,"line":1951},[203,2585,2586],{"class":209},"      }\n",[203,2588,2589],{"class":205,"line":1983},[203,2590,2591],{"class":209},"    ]\n",[203,2593,2594],{"class":205,"line":1989},[203,2595,1992],{"class":209},[203,2597,2598],{"class":205,"line":1995},[203,2599,405],{"class":209},[11,2601,2602,2603,2606],{},"The user decides, and the client starts a ",[15,2604,2605],{},"new run on the same thread"," that answers every open interrupt. This is the \"new request\" moment from the video: an SSE stream only flows from agent to app, so the answer cannot travel back up it.",[194,2608,2610],{"className":196,"code":2609,"language":198,"meta":199,"style":199},"{\n  \"threadId\": \"thread-1\",\n  \"runId\": \"run-2\",\n  \"resume\": [\n    {\n      \"interruptId\": \"int-1\",\n      \"status\": \"resolved\",\n      \"payload\": { \"approved\": true }\n    }\n  ]\n}\n",[189,2611,2612,2616,2634,2653,2666,2671,2691,2710,2736,2740,2745],{"__ignoreMap":199},[203,2613,2614],{"class":205,"line":206},[203,2615,210],{"class":209},[203,2617,2618,2620,2622,2624,2626,2628,2630,2632],{"class":205,"line":213},[203,2619,217],{"class":216},[203,2621,221],{"class":220},[203,2623,224],{"class":216},[203,2625,227],{"class":216},[203,2627,230],{"class":216},[203,2629,234],{"class":233},[203,2631,224],{"class":216},[203,2633,239],{"class":216},[203,2635,2636,2638,2640,2642,2644,2646,2649,2651],{"class":205,"line":242},[203,2637,217],{"class":216},[203,2639,247],{"class":220},[203,2641,224],{"class":216},[203,2643,227],{"class":216},[203,2645,230],{"class":216},[203,2647,2648],{"class":233},"run-2",[203,2650,224],{"class":216},[203,2652,239],{"class":216},[203,2654,2655,2657,2660,2662,2664],{"class":205,"line":263},[203,2656,217],{"class":216},[203,2658,2659],{"class":220},"resume",[203,2661,224],{"class":216},[203,2663,227],{"class":216},[203,2665,1189],{"class":209},[203,2667,2668],{"class":205,"line":280},[203,2669,2670],{"class":209},"    {\n",[203,2672,2673,2676,2679,2681,2683,2685,2687,2689],{"class":205,"line":354},[203,2674,2675],{"class":216},"      \"",[203,2677,2678],{"class":297},"interruptId",[203,2680,224],{"class":216},[203,2682,227],{"class":216},[203,2684,230],{"class":216},[203,2686,2413],{"class":233},[203,2688,224],{"class":216},[203,2690,239],{"class":216},[203,2692,2693,2695,2697,2699,2701,2703,2706,2708],{"class":205,"line":371},[203,2694,2675],{"class":216},[203,2696,1090],{"class":297},[203,2698,224],{"class":216},[203,2700,227],{"class":216},[203,2702,230],{"class":216},[203,2704,2705],{"class":233},"resolved",[203,2707,224],{"class":216},[203,2709,239],{"class":216},[203,2711,2712,2714,2717,2719,2721,2723,2725,2727,2729,2731,2733],{"class":205,"line":387},[203,2713,2675],{"class":216},[203,2715,2716],{"class":297},"payload",[203,2718,224],{"class":216},[203,2720,227],{"class":216},[203,2722,772],{"class":209},[203,2724,230],{"class":216},[203,2726,2526],{"class":1550},[203,2728,224],{"class":216},[203,2730,227],{"class":216},[203,2732,1695],{"class":919},[203,2734,2735],{"class":209}," }\n",[203,2737,2738],{"class":205,"line":402},[203,2739,1986],{"class":209},[203,2741,2742],{"class":205,"line":1350},[203,2743,2744],{"class":209},"  ]\n",[203,2746,2747],{"class":205,"line":1387},[203,2748,405],{"class":209},[11,2750,2751],{},[87,2752],{"alt":2753,"height":2754,"src":2755,"width":92},"Four stacked steps: run 1 ends with an interrupt outcome, the user approves or denies, a new RunAgentInput with a resume array is sent on the same thread, and run 2 emits the tool result and finishes.",1720,"https:\u002F\u002Fres.cloudinary.com\u002Fc552lpdm\u002Fimage\u002Fupload\u002Finterrupt-resume.webp",[11,2757,2758],{},"A few rules from the spec that are easy to miss:",[435,2760,2761,2768,2779,2786],{},[438,2762,2763,2764,2767],{},"The resume must address ",[15,2765,2766],{},"every"," open interrupt. Partial resumes are not supported.",[438,2769,2770,2771,2774,2775,2778],{},"Denial is expressed in the payload (",[189,2772,2773],{},"{ \"approved\": false }","), not as a separate status. ",[189,2776,2777],{},"cancelled"," is for \"the user walked away\".",[438,2780,2781,2782,2785],{},"The agent should emit any state it needs ",[15,2783,2784],{},"before"," the interrupt, so a resume can rebuild context.",[438,2787,2788,2789,2791,2792,2794],{},"The tool call is not repeated. Run 2 emits a ",[189,2790,548],{}," against the original ",[189,2793,2121],{},", so the audit trail is: proposal in run 1, decision in the resume, result in run 2.",[11,2796,2797],{},"My demo handles this end to end. Approving prints:",[194,2799,2802],{"className":2800,"code":2801,"language":418,"meta":199},[416],"[run run-1 started]\n  assistant: I drafted an email. I need your OK before sending it.\n  tool call: send_email\n  args: {\"to\": \"a@b.com\", \"subject\": \"Hi\", \"body\": \"Hello from AG-UI\"}\n  state = {\"status\":\"awaiting_approval\",\"steps\":[\"draft\"]}\n[run finished: interrupt]\n\n  ⏸  Send email to a@b.com with subject 'Hi'?\n  → user approves\n\n[run run-2 started]\n  tool result: {\"sent\": true, \"to\": \"a@b.com\"}\n  assistant: Done, the email was sent.\n[run finished: success]\n",[189,2803,2801],{"__ignoreMap":199},[11,2805,2806,2807,2810,2811,2814],{},"Denying gives ",[189,2808,2809],{},"{\"sent\": false, \"reason\": \"user declined\"}"," and \"Okay, I did not send anything.\" The protocol also supports approve-with-edits, where the resume payload carries an ",[189,2812,2813],{},"editedArgs"," object that fully replaces the tool arguments.",[11,2816,2817],{},"One caveat: interrupt support depends on the framework adapter. For example, the LangGraph adapter only emits the structured interrupt outcome if you opt in, so check your integration's docs.",[46,2819,2821],{"id":2820},"_8-using-the-real-sdks","8. Using the real SDKs",[11,2823,2824,2825,2828],{},"You do not have to hand-roll any of this. There are first-party SDKs for ",[15,2826,2827],{},"TypeScript, Python and .NET",", and integrations for LangGraph, Microsoft Agent Framework, Google ADK, AWS Strands, Mastra, Pydantic AI, CrewAI, LlamaIndex and Amazon Bedrock AgentCore, with CopilotKit as the main React client.",[11,2830,2831],{},"The two snippets below follow the official quickstarts, but I could not install the packages where I wrote this, so I have not run them. My hand-rolled server and client above are the parts I tested. Check the snippets against the SDK versions you install.",[11,2833,2834],{},"On the Python side, the SDK gives you typed events and an encoder. The code uses snake_case and serialises to the camelCase the wire expects:",[194,2836,2838],{"className":637,"code":2837,"language":639,"meta":199,"style":199},"from fastapi import FastAPI, Request\nfrom fastapi.responses import StreamingResponse\nfrom ag_ui.core import (\n    RunAgentInput, EventType, RunStartedEvent, RunFinishedEvent, TextMessageChunkEvent,\n)\nfrom ag_ui.encoder import EventEncoder\nimport uuid\n\napp = FastAPI()\n\n@app.post(\"\u002F\")\nasync def run(input_data: RunAgentInput, request: Request):\n    encoder = EventEncoder(accept=request.headers.get(\"accept\"))\n\n    async def gen():\n        yield encoder.encode(RunStartedEvent(\n            type=EventType.RUN_STARTED,\n            thread_id=input_data.thread_id, run_id=input_data.run_id))\n        yield encoder.encode(TextMessageChunkEvent(\n            type=EventType.TEXT_MESSAGE_CHUNK,\n            message_id=str(uuid.uuid4()), delta=\"Hello from the SDK\"))\n        yield encoder.encode(RunFinishedEvent(\n            type=EventType.RUN_FINISHED,\n            thread_id=input_data.thread_id, run_id=input_data.run_id))\n\n    return StreamingResponse(gen(), media_type=encoder.get_content_type())\n",[189,2839,2840,2859,2876,2893,2920,2924,2940,2947,2953,2966,2970,2993,3025,3065,3069,3082,3101,3117,3147,3164,3179,3214,3232,3247,3274,3279],{"__ignoreMap":199},[203,2841,2842,2845,2848,2851,2854,2856],{"class":205,"line":206},[203,2843,2844],{"class":1550},"from",[203,2846,2847],{"class":801}," fastapi ",[203,2849,2850],{"class":1550},"import",[203,2852,2853],{"class":801}," FastAPI",[203,2855,312],{"class":216},[203,2857,2858],{"class":801}," Request\n",[203,2860,2861,2863,2866,2868,2871,2873],{"class":205,"line":213},[203,2862,2844],{"class":1550},[203,2864,2865],{"class":801}," fastapi",[203,2867,454],{"class":216},[203,2869,2870],{"class":801},"responses ",[203,2872,2850],{"class":1550},[203,2874,2875],{"class":801}," StreamingResponse\n",[203,2877,2878,2880,2883,2885,2888,2890],{"class":205,"line":242},[203,2879,2844],{"class":1550},[203,2881,2882],{"class":801}," ag_ui",[203,2884,454],{"class":216},[203,2886,2887],{"class":801},"core ",[203,2889,2850],{"class":1550},[203,2891,2892],{"class":216}," (\n",[203,2894,2895,2898,2900,2903,2905,2908,2910,2913,2915,2918],{"class":205,"line":263},[203,2896,2897],{"class":801},"    RunAgentInput",[203,2899,312],{"class":216},[203,2901,2902],{"class":801}," EventType",[203,2904,312],{"class":216},[203,2906,2907],{"class":801}," RunStartedEvent",[203,2909,312],{"class":216},[203,2911,2912],{"class":801}," RunFinishedEvent",[203,2914,312],{"class":216},[203,2916,2917],{"class":801}," TextMessageChunkEvent",[203,2919,239],{"class":216},[203,2921,2922],{"class":205,"line":280},[203,2923,727],{"class":216},[203,2925,2926,2928,2930,2932,2935,2937],{"class":205,"line":354},[203,2927,2844],{"class":1550},[203,2929,2882],{"class":801},[203,2931,454],{"class":216},[203,2933,2934],{"class":801},"encoder ",[203,2936,2850],{"class":1550},[203,2938,2939],{"class":801}," EventEncoder\n",[203,2941,2942,2944],{"class":205,"line":371},[203,2943,2850],{"class":1550},[203,2945,2946],{"class":801}," uuid\n",[203,2948,2949],{"class":205,"line":387},[203,2950,2952],{"emptyLinePlaceholder":2951},true,"\n",[203,2954,2955,2958,2961,2963],{"class":205,"line":402},[203,2956,2957],{"class":801},"app ",[203,2959,2960],{"class":216},"=",[203,2962,2853],{"class":220},[203,2964,2965],{"class":209},"()\n",[203,2967,2968],{"class":205,"line":1350},[203,2969,2952],{"emptyLinePlaceholder":2951},[203,2971,2972,2975,2978,2980,2983,2985,2987,2989,2991],{"class":205,"line":1387},[203,2973,2974],{"class":216},"@",[203,2976,2977],{"class":220},"app",[203,2979,454],{"class":216},[203,2981,2982],{"class":220},"post",[203,2984,653],{"class":209},[203,2986,224],{"class":216},[203,2988,1707],{"class":233},[203,2990,224],{"class":216},[203,2992,727],{"class":209},[203,2994,2995,2997,3000,3003,3005,3008,3010,3013,3015,3018,3020,3023],{"class":205,"line":1454},[203,2996,1515],{"class":646},[203,2998,2999],{"class":646}," def",[203,3001,3002],{"class":220}," run",[203,3004,653],{"class":209},[203,3006,3007],{"class":656},"input_data",[203,3009,227],{"class":216},[203,3011,3012],{"class":801}," RunAgentInput",[203,3014,312],{"class":216},[203,3016,3017],{"class":656}," request",[203,3019,227],{"class":216},[203,3021,3022],{"class":801}," Request",[203,3024,764],{"class":209},[203,3026,3027,3030,3032,3035,3037,3040,3042,3045,3047,3050,3052,3055,3057,3059,3061,3063],{"class":205,"line":1831},[203,3028,3029],{"class":801},"    encoder ",[203,3031,2960],{"class":216},[203,3033,3034],{"class":220}," EventEncoder",[203,3036,653],{"class":209},[203,3038,3039],{"class":656},"accept",[203,3041,2960],{"class":216},[203,3043,3044],{"class":691},"request",[203,3046,454],{"class":216},[203,3048,3049],{"class":691},"headers",[203,3051,454],{"class":216},[203,3053,3054],{"class":220},"get",[203,3056,653],{"class":209},[203,3058,224],{"class":216},[203,3060,3039],{"class":233},[203,3062,224],{"class":216},[203,3064,1895],{"class":209},[203,3066,3067],{"class":205,"line":1844},[203,3068,2952],{"emptyLinePlaceholder":2951},[203,3070,3071,3074,3076,3079],{"class":205,"line":1859},[203,3072,3073],{"class":646},"    async",[203,3075,2999],{"class":646},[203,3077,3078],{"class":220}," gen",[203,3080,3081],{"class":209},"():\n",[203,3083,3084,3086,3089,3091,3093,3095,3098],{"class":205,"line":1898},[203,3085,854],{"class":646},[203,3087,3088],{"class":801}," encoder",[203,3090,454],{"class":216},[203,3092,715],{"class":220},[203,3094,653],{"class":209},[203,3096,3097],{"class":220},"RunStartedEvent",[203,3099,3100],{"class":209},"(\n",[203,3102,3103,3106,3108,3111,3113,3115],{"class":205,"line":1935},[203,3104,3105],{"class":656},"            type",[203,3107,2960],{"class":216},[203,3109,3110],{"class":691},"EventType",[203,3112,454],{"class":216},[203,3114,445],{"class":919},[203,3116,239],{"class":216},[203,3118,3119,3122,3124,3126,3128,3131,3133,3136,3138,3140,3142,3145],{"class":205,"line":1951},[203,3120,3121],{"class":656},"            thread_id",[203,3123,2960],{"class":216},[203,3125,3007],{"class":691},[203,3127,454],{"class":216},[203,3129,3130],{"class":691},"thread_id",[203,3132,312],{"class":216},[203,3134,3135],{"class":656}," run_id",[203,3137,2960],{"class":216},[203,3139,3007],{"class":691},[203,3141,454],{"class":216},[203,3143,3144],{"class":691},"run_id",[203,3146,1895],{"class":209},[203,3148,3149,3151,3153,3155,3157,3159,3162],{"class":205,"line":1983},[203,3150,854],{"class":646},[203,3152,3088],{"class":801},[203,3154,454],{"class":216},[203,3156,715],{"class":220},[203,3158,653],{"class":209},[203,3160,3161],{"class":220},"TextMessageChunkEvent",[203,3163,3100],{"class":209},[203,3165,3166,3168,3170,3172,3174,3177],{"class":205,"line":1989},[203,3167,3105],{"class":656},[203,3169,2960],{"class":216},[203,3171,3110],{"class":691},[203,3173,454],{"class":216},[203,3175,3176],{"class":919},"TEXT_MESSAGE_CHUNK",[203,3178,239],{"class":216},[203,3180,3181,3184,3186,3188,3190,3192,3194,3196,3198,3200,3203,3205,3207,3210,3212],{"class":205,"line":1995},[203,3182,3183],{"class":656},"            message_id",[203,3185,2960],{"class":216},[203,3187,1360],{"class":297},[203,3189,653],{"class":209},[203,3191,1365],{"class":691},[203,3193,454],{"class":216},[203,3195,1370],{"class":220},[203,3197,1373],{"class":209},[203,3199,312],{"class":216},[203,3201,3202],{"class":656}," delta",[203,3204,2960],{"class":216},[203,3206,224],{"class":216},[203,3208,3209],{"class":233},"Hello from the SDK",[203,3211,224],{"class":216},[203,3213,1895],{"class":209},[203,3215,3217,3219,3221,3223,3225,3227,3230],{"class":205,"line":3216},22,[203,3218,854],{"class":646},[203,3220,3088],{"class":801},[203,3222,454],{"class":216},[203,3224,715],{"class":220},[203,3226,653],{"class":209},[203,3228,3229],{"class":220},"RunFinishedEvent",[203,3231,3100],{"class":209},[203,3233,3235,3237,3239,3241,3243,3245],{"class":205,"line":3234},23,[203,3236,3105],{"class":656},[203,3238,2960],{"class":216},[203,3240,3110],{"class":691},[203,3242,454],{"class":216},[203,3244,449],{"class":919},[203,3246,239],{"class":216},[203,3248,3250,3252,3254,3256,3258,3260,3262,3264,3266,3268,3270,3272],{"class":205,"line":3249},24,[203,3251,3121],{"class":656},[203,3253,2960],{"class":216},[203,3255,3007],{"class":691},[203,3257,454],{"class":216},[203,3259,3130],{"class":691},[203,3261,312],{"class":216},[203,3263,3135],{"class":656},[203,3265,2960],{"class":216},[203,3267,3007],{"class":691},[203,3269,454],{"class":216},[203,3271,3144],{"class":691},[203,3273,1895],{"class":209},[203,3275,3277],{"class":205,"line":3276},25,[203,3278,2952],{"emptyLinePlaceholder":2951},[203,3280,3282,3284,3287,3289,3292,3294,3296,3299,3301,3304,3306,3309],{"class":205,"line":3281},26,[203,3283,679],{"class":646},[203,3285,3286],{"class":220}," StreamingResponse",[203,3288,653],{"class":209},[203,3290,3291],{"class":220},"gen",[203,3293,1558],{"class":209},[203,3295,312],{"class":216},[203,3297,3298],{"class":656}," media_type",[203,3300,2960],{"class":216},[203,3302,3303],{"class":691},"encoder",[203,3305,454],{"class":216},[203,3307,3308],{"class":220},"get_content_type",[203,3310,1932],{"class":209},[11,3312,3313,3314,3316,3317,3319,3320,3323],{},"Note the ",[189,3315,3161],{},". Chunk events are a convenience: you send ",[189,3318,3176],{}," and the client expands it into start, content and end for you. ",[189,3321,3322],{},"TOOL_CALL_CHUNK"," does the same for tool calls.",[11,3325,3326,3327,3330,3331,3334],{},"On the client, ",[189,3328,3329],{},"HttpAgent"," from ",[189,3332,3333],{},"@ag-ui\u002Fclient"," replaces my hand-written parser and gives you a callback per event type:",[194,3336,3340],{"className":3337,"code":3338,"language":3339,"meta":199,"style":199},"language-ts shiki shiki-themes tokyo-night","import { HttpAgent } from '@ag-ui\u002Fclient';\n\nconst agent = new HttpAgent({ url: 'http:\u002F\u002Flocalhost:8000' });\nagent.messages.push({\n  id: crypto.randomUUID(),\n  role: 'user',\n  content: 'hello',\n});\n\nawait agent.runAgent(\n  {},\n  {\n    onTextMessageContentEvent({ event }) {\n      process.stdout.write(event.delta);\n    },\n    onToolCallStartEvent({ event }) {\n      console.log('tool:', event.toolCallName);\n    },\n  },\n);\n","ts",[189,3341,3342,3363,3367,3400,3417,3436,3451,3467,3474,3478,3492,3499,3504,3522,3549,3556,3573,3605,3611,3618],{"__ignoreMap":199},[203,3343,3344,3346,3348,3350,3352,3355,3357,3359,3361],{"class":205,"line":206},[203,3345,2850],{"class":1550},[203,3347,1686],{"class":209},[203,3349,3329],{"class":297},[203,3351,1627],{"class":209},[203,3353,3354],{"class":1550}," from",[203,3356,2066],{"class":216},[203,3358,3333],{"class":233},[203,3360,1888],{"class":216},[203,3362,1561],{"class":216},[203,3364,3365],{"class":205,"line":213},[203,3366,2952],{"emptyLinePlaceholder":2951},[203,3368,3369,3371,3374,3376,3378,3381,3384,3387,3389,3391,3394,3396,3398],{"class":205,"line":242},[203,3370,2025],{"class":1514},[203,3372,3373],{"class":646}," agent",[203,3375,1542],{"class":216},[203,3377,1573],{"class":216},[203,3379,3380],{"class":220}," HttpAgent",[203,3382,3383],{"class":209},"({",[203,3385,3386],{"class":1689}," url",[203,3388,227],{"class":216},[203,3390,2066],{"class":216},[203,3392,3393],{"class":233},"http:\u002F\u002Flocalhost:8000",[203,3395,1888],{"class":216},[203,3397,1698],{"class":209},[203,3399,1561],{"class":216},[203,3401,3402,3405,3407,3409,3411,3414],{"class":205,"line":263},[203,3403,3404],{"class":691},"agent",[203,3406,454],{"class":216},[203,3408,285],{"class":1550},[203,3410,454],{"class":216},[203,3412,3413],{"class":220},"push",[203,3415,3416],{"class":209},"({\n",[203,3418,3419,3422,3424,3427,3429,3432,3434],{"class":205,"line":280},[203,3420,3421],{"class":1689},"  id",[203,3423,227],{"class":216},[203,3425,3426],{"class":691}," crypto",[203,3428,454],{"class":216},[203,3430,3431],{"class":220},"randomUUID",[203,3433,1558],{"class":209},[203,3435,239],{"class":216},[203,3437,3438,3441,3443,3445,3447,3449],{"class":205,"line":354},[203,3439,3440],{"class":1689},"  role",[203,3442,227],{"class":216},[203,3444,2066],{"class":216},[203,3446,326],{"class":233},[203,3448,1888],{"class":216},[203,3450,239],{"class":216},[203,3452,3453,3456,3458,3460,3463,3465],{"class":205,"line":371},[203,3454,3455],{"class":1689},"  content",[203,3457,227],{"class":216},[203,3459,2066],{"class":216},[203,3461,3462],{"class":233},"hello",[203,3464,1888],{"class":216},[203,3466,239],{"class":216},[203,3468,3469,3472],{"class":205,"line":387},[203,3470,3471],{"class":209},"})",[203,3473,1561],{"class":216},[203,3475,3476],{"class":205,"line":402},[203,3477,2952],{"emptyLinePlaceholder":2951},[203,3479,3480,3483,3485,3487,3490],{"class":205,"line":1350},[203,3481,3482],{"class":1632},"await",[203,3484,3373],{"class":691},[203,3486,454],{"class":216},[203,3488,3489],{"class":220},"runAgent",[203,3491,3100],{"class":209},[203,3493,3494,3497],{"class":205,"line":1387},[203,3495,3496],{"class":209},"  {}",[203,3498,239],{"class":216},[203,3500,3501],{"class":205,"line":1454},[203,3502,3503],{"class":209},"  {\n",[203,3505,3506,3509,3511,3513,3516,3518,3520],{"class":205,"line":1831},[203,3507,3508],{"class":220},"    onTextMessageContentEvent",[203,3510,653],{"class":209},[203,3512,688],{"class":216},[203,3514,3515],{"class":656}," event",[203,3517,1627],{"class":216},[203,3519,665],{"class":209},[203,3521,1531],{"class":209},[203,3523,3524,3527,3529,3532,3534,3537,3539,3541,3543,3545,3547],{"class":205,"line":1844},[203,3525,3526],{"class":691},"      process",[203,3528,454],{"class":216},[203,3530,3531],{"class":1550},"stdout",[203,3533,454],{"class":216},[203,3535,3536],{"class":220},"write",[203,3538,653],{"class":209},[203,3540,657],{"class":691},[203,3542,454],{"class":216},[203,3544,528],{"class":1550},[203,3546,665],{"class":209},[203,3548,1561],{"class":216},[203,3550,3551,3554],{"class":205,"line":1859},[203,3552,3553],{"class":209},"    }",[203,3555,239],{"class":216},[203,3557,3558,3561,3563,3565,3567,3569,3571],{"class":205,"line":1898},[203,3559,3560],{"class":220},"    onToolCallStartEvent",[203,3562,653],{"class":209},[203,3564,688],{"class":216},[203,3566,3515],{"class":656},[203,3568,1627],{"class":216},[203,3570,665],{"class":209},[203,3572,1531],{"class":209},[203,3574,3575,3578,3580,3583,3585,3587,3590,3592,3594,3596,3598,3601,3603],{"class":205,"line":1935},[203,3576,3577],{"class":691},"      console",[203,3579,454],{"class":216},[203,3581,3582],{"class":220},"log",[203,3584,653],{"class":209},[203,3586,1888],{"class":216},[203,3588,3589],{"class":233},"tool:",[203,3591,1888],{"class":216},[203,3593,312],{"class":216},[203,3595,3515],{"class":691},[203,3597,454],{"class":216},[203,3599,3600],{"class":1550},"toolCallName",[203,3602,665],{"class":209},[203,3604,1561],{"class":216},[203,3606,3607,3609],{"class":205,"line":1951},[203,3608,3553],{"class":209},[203,3610,239],{"class":216},[203,3612,3613,3616],{"class":205,"line":1983},[203,3614,3615],{"class":209},"  }",[203,3617,239],{"class":216},[203,3619,3620,3622],{"class":205,"line":1989},[203,3621,665],{"class":209},[203,3623,1561],{"class":216},[46,3625,3627],{"id":3626},"_9-gotchas-worth-knowing","9. Gotchas worth knowing",[435,3629,3630,3636,3642,3652],{},[438,3631,3632,3635],{},[15,3633,3634],{},"Model output is untrusted input."," The spec says so explicitly: tool arguments, tool results and streamed content cross a trust boundary, and apps must not render streamed content as executable markup. Same lesson as my chat: render text as text, never as HTML.",[438,3637,3638,3641],{},[15,3639,3640],{},"State round-trips."," State and messages go to the client and come back on the next run, so do not put secrets in them.",[438,3643,3644,3647,3648,3651],{},[15,3645,3646],{},"Check the version."," AG-UI is at 1.0, with a 1.1 draft in progress. Make sure every ",[189,3649,3650],{},"@ag-ui\u002F*"," package in your app is on the same major version, because two copies means two incompatible event types.",[438,3653,3654,3657],{},[15,3655,3656],{},"It is a communication layer, not a UI library."," AG-UI will not render anything for you. For a background batch job with nobody watching, it adds complexity for no benefit.",[46,3659,3661],{"id":3660},"whats-next","What's next",[11,3663,3664],{},"I like this protocol because it is small enough to hold in your head. A handful of event families, three repeating patterns, JSON over SSE. If you already know how to stream tokens to a chat bubble, you already know most of it.",[11,3666,3667],{},"So far the agent has only sent text, tool calls and state, and the UI decided how to show them. In Part 2 the agent starts choosing the interface too: a weather card, a form it invented, a whole widget. That is where the interesting design trade-offs are.",[11,3669,3670,3671,3674,3675,3678,3679,454],{},"The code for this part (",[189,3672,3673],{},"server.py"," and ",[189,3676,3677],{},"client.mjs",", no dependencies) is in ",[27,3680,3684],{"href":3681,"rel":3682},"https:\u002F\u002Fgist.github.com\u002FMondal10\u002F4ddc4f92f0c35fc84f3759c93abe1ab1",[3683],"nofollow","this GitHub Gist",[46,3686,3688],{"id":3687},"further-reading","Further reading",[435,3690,3691,3698,3705,3712,3719],{},[438,3692,3693],{},[27,3694,3697],{"href":3695,"rel":3696},"https:\u002F\u002Fdocs.ag-ui.com\u002Fconcepts\u002Fevents",[3683],"AG-UI docs: events",[438,3699,3700],{},[27,3701,3704],{"href":3702,"rel":3703},"https:\u002F\u002Fdocs.ag-ui.com\u002Fconcepts\u002Finterrupts",[3683],"AG-UI docs: interrupts",[438,3706,3707],{},[27,3708,3711],{"href":3709,"rel":3710},"https:\u002F\u002Fdocs.ag-ui.com\u002Fspec\u002Fdraft",[3683],"AG-UI specification (draft 1.1)",[438,3713,3714],{},[27,3715,3718],{"href":3716,"rel":3717},"https:\u002F\u002Fgithub.com\u002Fag-ui-protocol\u002Fag-ui",[3683],"AG-UI on GitHub",[438,3720,3721],{},[27,3722,3725],{"href":3723,"rel":3724},"https:\u002F\u002Fwww.copilotkit.ai\u002Flearning\u002Fmcp-vs-a2a-vs-ag-ui",[3683],"MCP vs. A2A vs. AG-UI (CopilotKit)",[3727,3728,3729],"style",{},"html pre.shiki code .sgJMe, html code.shiki .sgJMe{--shiki-default:#9ABDF5}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s3R4Z, html code.shiki .s3R4Z{--shiki-default:#7AA2F7}html pre.shiki code .sPY7s, html code.shiki .sPY7s{--shiki-default:#9ECE6A}html pre.shiki code .sySf4, html code.shiki .sySf4{--shiki-default:#0DB9D7}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sd1Qi, html code.shiki .sd1Qi{--shiki-default:#BB9AF7}html pre.shiki code .sT800, html code.shiki .sT800{--shiki-default:#E0AF68}html pre.shiki code .sE3pS, html code.shiki .sE3pS{--shiki-default:#C0CAF5}html pre.shiki code .sGX4V, html code.shiki .sGX4V{--shiki-default:#A9B1D6}html pre.shiki code .sOJ5S, html code.shiki .sOJ5S{--shiki-default:#FF9E64}html pre.shiki code .sbD-w, html code.shiki .sbD-w{--shiki-default:#51597D;--shiki-default-font-style:italic}html pre.shiki code .sN7LL, html code.shiki .sN7LL{--shiki-default:#9D7CD8;--shiki-default-font-style:italic}html pre.shiki code .su0L4, html code.shiki .su0L4{--shiki-default:#7DCFFF}html pre.shiki code .sEsAJ, html code.shiki .sEsAJ{--shiki-default:#BB9AF7;--shiki-default-font-style:italic}html pre.shiki code .syYvs, html code.shiki .syYvs{--shiki-default:#73DACA}",{"title":199,"searchDepth":213,"depth":213,"links":3731},[3732,3733,3734,3735,3736,3737,3738,3739,3740,3741,3742,3743],{"id":48,"depth":213,"text":49},{"id":66,"depth":213,"text":67},{"id":79,"depth":213,"text":80},{"id":183,"depth":213,"text":184},{"id":464,"depth":213,"text":465},{"id":627,"depth":213,"text":628},{"id":1494,"depth":213,"text":1495},{"id":2273,"depth":213,"text":2274},{"id":2820,"depth":213,"text":2821},{"id":3626,"depth":213,"text":3627},{"id":3660,"depth":213,"text":3661},{"id":3687,"depth":213,"text":3688},"#ffaa00","https:\u002F\u002Fres.cloudinary.com\u002Fc552lpdm\u002Fimage\u002Fupload\u002Fag-ui-cover-part-1.webp","Dark cover reading \"How agents talk to your frontend\" next to a vertical wire with typed AG-UI event capsules flowing from an agent down into an app.",null,"2026-10-23","AG-UI is the open protocol that streams an agent's text, tool calls and approvals to your UI. I build a server and client from scratch, no SDK or API key.",false,"md",{},"\u002Fblogs\u002Fag-ui-part-1-how-agents-talk-to-your-frontend",{"title":6,"description":3749},"blogs\u002Fag-ui-part-1-how-agents-talk-to-your-frontend",[17,3757,3758,3759],"AI Engineering","Frontend","Python",[3761,3762,3763],"AG-UI is an open protocol for agent-to-user apps: one request in, an ordered stream of typed events out.","It sits beside MCP (agent to tools) and A2A (agent to agent). AG-UI is agent to user, and it is plain JSON over SSE.","Human approval works by ending the run with an interrupt and resuming in a new run. I built it all in about 240 lines with no dependencies.","AT-CMGrgy1GLDUKvyGjO8wmndm7bP3h-mWhi1dwta0I",{"newer":3747,"older":3766},{"title":3767,"path":3768,"stem":3769,"draft":3750,"children":-1},"Building Amit.AI, Part 2: The Backend","\u002Fblogs\u002Fbuilding-amit-ai-part-2-the-backend","blogs\u002Fbuilding-amit-ai-part-2-the-backend",1792779529485]