Skip to main content

How to Continue an AG-UI Conversation with threadId and parentRunId

· 9 min read
Jagdish Kumawat
Founder @ Dewiride

An AG-UI request carries three IDs: threadId, runId and parentRunId. When the server remembers conversations, a follow-up keeps the first, changes the second, points the third at the turn before — and carries only the new message. This guide shows each request and what the server sends back, from a real agent.

Threads, Runs and Messages​

AG-UI is an open protocol between a chat front end and an agent: one POST with a JSON body, answered by a stream of events. Its request body — RunAgentInput in the protocol's types — starts with three IDs and a list of messages:

FieldAG-UI's definitionIn plain words
threadIdID of the conversation threadWhich conversation this is
runIdID of the current runThis turn: one message in, one answer out
parentRunIdID of the run that spawned this run (optional)The turn this one follows
messagesArray of messages in the conversationWhat the agent should read

The protocol's events page calls parentRunId a lineage pointer: a reference to an earlier run in the same thread, so the runs form a chain — a git-like append-only log.

Notice the definition of messages: AG-UI lets a client send the whole conversation, which is what a stateless server needs. A server that keeps its own sessions is different. Microsoft's guide for its Agent Framework says it plainly: Send only the new messages in a continuation request (Getting Started with AG-UI). This guide follows that pattern.

Prerequisites​

Step 1: Start a Conversation​

The first request names the conversation and the run, and carries the first message:

The first turn
POST {{baseUrl}}/api/agents/service-desk
Content-Type: application/json
Accept: text/event-stream

{
"threadId": "thread-2",
"runId": "run-1",
"messages": [
{
"id": "message-1",
"role": "user",
"content": "My laptop won't connect to the office Wi-Fi. What should I do?"
}
]
}

The stream opens with RUN_STARTED, which repeats the IDs, and ends with RUN_FINISHED:

Response (first and last event)
data: {"type":"RUN_STARTED","threadId":"thread-2","runId":"run-1"}

data: {"type":"RUN_FINISHED","threadId":"thread-2","runId":"run-1","outcome":{"type":"success"},"usage":[{"model":"gpt-5.6-luna-2026-07-09","inputTokens":254,"outputTokens":60,"totalTokens":314,"reasoningTokens":41,"cachedInputTokens":0}]}

The text events in between spell out a question back: Which operating system is your laptop using: Windows or macOS?

Step 2: Send the Follow-Up​

Answer the question in the same conversation. Three things change and one stays:

The second turn
POST {{baseUrl}}/api/agents/service-desk
Content-Type: application/json
Accept: text/event-stream

{
"threadId": "thread-2",
"runId": "run-2",
"parentRunId": "run-1",
"messages": [
{
"id": "message-2",
"role": "user",
"content": "It's a Windows 11 laptop."
}
]
}

macOS-style code window showing the second-turn AG-UI request from an .http file: a POST to the service desk agent with threadId thread-2, runId run-2, parentRunId run-1 and a single new user message, It's a Windows 11 laptop

FieldFirst turnFollow-upWhy
threadIdthread-2thread-2The same conversation
runIdrun-1run-2A new turn gets a new ID
parentRunId—run-1The turn this one follows
messagesthe questiononly the answer to the agent's questionThe server already has the rest

RUN_STARTED confirms the chain, and the answer shows that the agent remembered:

Response (first event)
data: {"type":"RUN_STARTED","threadId":"thread-2","runId":"run-2","parentRunId":"run-1"}
The answer, put together
Try these steps on your Windows 11 laptop:

1. Make sure Wi-Fi is enabled:
- Select the network, sound, or battery icon on the taskbar.
- Confirm Wi-Fi is on and Airplane mode is off.

2. Select the office Wi-Fi network and choose Connect. Enter your normal work sign-in details if prompted. Do not share your password.

3. If it still fails, forget and reconnect:
- Settings > Network & internet > Wi-Fi > Manage known networks.
- Select the office network > Forget.
- Reconnect to it from the Wi-Fi list.

4. Restart the laptop and try again. If possible, check whether nearby colleagues can connect.

5. If you connect but have no internet, temporarily disconnect from any VPN and try opening a website.

6. If the problem continues, raise a ticket with the IT Service Desk. Include the Wi-Fi network name, the exact error message, and whether other devices can connect.

Step 3: Keep the Chain Going​

Every further turn moves the same two fields one step along:

The third turn's IDs
{
"threadId": "thread-2",
"runId": "run-3",
"parentRunId": "run-2",
"messages": [
{
"id": "message-3",
"role": "user",
"content": "I tried all of that, and it still won't connect."
}
]
}

The response starts with "runId":"run-3","parentRunId":"run-2", and the agent, knowing the basic fixes were done, moved straight on to the ticket and what to put in it. The chain now reads run-1 → run-2 → run-3.

Let the Server Choose the threadId​

You don't have to invent a threadId. Leave it out of the first request, and a Microsoft Agent Framework server creates one and sends it back in RUN_STARTED. I sent a first turn with only messages:

A first turn without IDs
{
"messages": [
{
"id": "message-1",
"role": "user",
"content": "My laptop won't connect to the office Wi-Fi. What should I do?"
}
]
}
Response (first event)
data: {"type":"RUN_STARTED","threadId":"1a69ef730fc744a8a2300de4cbd61238","runId":""}

The server chose the threadId. Sending the follow-up with that threadId continued the conversation — the agent answered with Windows 11 steps. But look at runId: it came back empty. The server doesn't make one up, so a follow-up has no previous runId to name in parentRunId. Always send your own runId, even when you let the server pick the threadId.

What the Server Does with Each Field​

This is how MapAGUIServer in Microsoft Agent Framework handles them — I read its source for version 1.26.0 and checked each point in the runs above:

FieldWhat the server does
threadIdLooks up the conversation's session in the session store, or creates a new session; generates an ID when it is missing
runIdRepeats it in RUN_STARTED and RUN_FINISHED
parentRunIdRepeats it in RUN_STARTED and records it in tracing; the session is found by threadId alone
messagesTurns every message into input for the agent

That last row is why a follow-up must carry only the new message. Resent messages reach the model a second time (Fixed: AG-UI Agent Receives the Conversation Twice).

Common Mistakes​

MistakeWhat happensDo this instead
A new threadId for the follow-upA new conversation starts, and the agent forgetsKeep the threadId
Resending earlier messagesThe agent reads them twiceSend only the new message
Leaving out runIdRUN_STARTED reports an empty runId, and the next turn can't name its parentSend a new runId with every request
Treating threadId as a secretAnyone who knows it may continue the conversation, unless the server isolates usersRely on sign-in and session isolation on the server, never on the ID

Troubleshooting​

SymptomLikely causeFix
The agent forgets the previous turnA different threadId, or the server keeps no sessionsCheck the threadId, and that the server has a session store
"runId":"" in RUN_STARTEDThe request had no runIdAdd one
Answers repeat themselves and token use jumpsEarlier messages were resentSend only the new message

FAQ​

Does parentRunId change what the agent remembers?​

Not in Microsoft Agent Framework 1.26.0: the session is chosen by threadId. parentRunId records the lineage, and Microsoft's guide asks for it on every continuation, so send it.

What should a chat page store?​

The threadId of each conversation and the runId of its last turn — the values for the next request. It can keep the messages too, for display, but it sends only the new one.

Is AG-UI wrong to put the whole conversation in messages?​

No. The field fits both kinds of server. A stateless server needs the history in every request; a server with sessions already has it, so you send only what's new.

Conclusion​

A follow-up keeps the threadId, takes a new runId, sets parentRunId to the previous run, and sends only the new message. If you let the server choose the threadId, read it from RUN_STARTED — and still send your own runId.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.