You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add a run_mode field to RunAgentInput with a "sync" value that signals the backend to restore and emit session state (STATE_SNAPSHOT + MESSAGES_SNAPSHOT) without invoking the LLM. This enables session restoration on page load and explicit mid-session state refresh — both common operations that today have no first-class protocol support.
Problem
When a user returns to an existing session (page reload, new tab, device switch), the frontend has a threadId but no local state. To restore session state and message history, it currently has to trigger a full agent run. That is wasteful and incorrect: the LLM gets invoked on what is semantically a read operation, and users see a response they did not ask for.
We worked around this in our own deployment by monkey-patching ag-ui-adk to skip the LLM call when the last message is empty. That workaround is implicit, fragile, and invisible to anyone reading the call site. It should not be necessary.
We have also seen developers implement a dedicated out-of-band endpoint (e.g. GET /thread/{threadId}) as an alternative. That approach works but bypasses the existing protocol flow entirely — the client ends up with two separate integration points and the state has to be reconciled outside of the protocol. The protocol already has the right built-in mechanism for this: STATE_SNAPSHOT and MESSAGES_SNAPSHOT. What is missing is a way to invoke that mechanism without triggering an LLM call.
We haven't found a first-class way to achieve this in the current protocol — if we've missed something, we'd welcome a pointer. If there genuinely isn't one, we'd like to propose adding it.
The backend reads the existing session for threadId
Emits STATE_SNAPSHOT and MESSAGES_SNAPSHOT with the current session contents
Emits RUN_FINISHED — keeping the standard RUN_STARTED / RUN_FINISHED envelope so clients need no special transport handling
Does not invoke the LLM
If no session exists yet for threadId, responds with empty state (not an error)
Precedent
The resume field added for interrupt/human-in-the-loop flows is the closest analogue — it also represents a "run" where the protocol semantics differ from a fresh LLM turn. run_mode: "sync" follows the same pattern of extending RunAgentInput rather than adding a new message type.
CopilotKit implications (out of scope for the protocol itself, noting for discussion)
For the feature to be ergonomic end-to-end, CopilotKit would benefit from:
autoSync prop on <CopilotKit> (default true when threadId is provided) — fires a sync run on mount and whenever threadId changes
sync() function from a hook for explicit calls (e.g. a "refresh" button)
Raising here in case the maintainers want to coordinate. Happy to track this separately if preferred.
Open questions for maintainers
Is extending RunAgentInput the right place, or would a separate input event type be preferable?
Should the RUN_STARTED / RUN_FINISHED envelope still wrap a sync response, or would a lighter-weight response shape make more sense?
Is run_mode the right name, or does something like intent: "sync" | "run" fit the protocol vocabulary better?
Are there existing discussions or in-progress work in this direction we should be aware of?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Summary
Add a
run_modefield toRunAgentInputwith a"sync"value that signals the backend to restore and emit session state (STATE_SNAPSHOT+MESSAGES_SNAPSHOT) without invoking the LLM. This enables session restoration on page load and explicit mid-session state refresh — both common operations that today have no first-class protocol support.Problem
When a user returns to an existing session (page reload, new tab, device switch), the frontend has a
threadIdbut no local state. To restore session state and message history, it currently has to trigger a full agent run. That is wasteful and incorrect: the LLM gets invoked on what is semantically a read operation, and users see a response they did not ask for.We worked around this in our own deployment by monkey-patching
ag-ui-adkto skip the LLM call when the last message is empty. That workaround is implicit, fragile, and invisible to anyone reading the call site. It should not be necessary.We have also seen developers implement a dedicated out-of-band endpoint (e.g.
GET /thread/{threadId}) as an alternative. That approach works but bypasses the existing protocol flow entirely — the client ends up with two separate integration points and the state has to be reconciled outside of the protocol. The protocol already has the right built-in mechanism for this:STATE_SNAPSHOTandMESSAGES_SNAPSHOT. What is missing is a way to invoke that mechanism without triggering an LLM call.We haven't found a first-class way to achieve this in the current protocol — if we've missed something, we'd welcome a pointer. If there genuinely isn't one, we'd like to propose adding it.
Proposed change
Add an optional
run_modefield toRunAgentInput:When
run_modeis"sync":threadIdSTATE_SNAPSHOTandMESSAGES_SNAPSHOTwith the current session contentsRUN_FINISHED— keeping the standardRUN_STARTED/RUN_FINISHEDenvelope so clients need no special transport handlingthreadId, responds with empty state (not an error)Precedent
The
resumefield added for interrupt/human-in-the-loop flows is the closest analogue — it also represents a "run" where the protocol semantics differ from a fresh LLM turn.run_mode: "sync"follows the same pattern of extendingRunAgentInputrather than adding a new message type.CopilotKit implications (out of scope for the protocol itself, noting for discussion)
For the feature to be ergonomic end-to-end, CopilotKit would benefit from:
autoSyncprop on<CopilotKit>(defaulttruewhenthreadIdis provided) — fires a sync run on mount and wheneverthreadIdchangessync()function from a hook for explicit calls (e.g. a "refresh" button)Raising here in case the maintainers want to coordinate. Happy to track this separately if preferred.
Open questions for maintainers
RunAgentInputthe right place, or would a separate input event type be preferable?RUN_STARTED/RUN_FINISHEDenvelope still wrap a sync response, or would a lighter-weight response shape make more sense?run_modethe right name, or does something likeintent: "sync" | "run"fit the protocol vocabulary better?Beta Was this translation helpful? Give feedback.
All reactions