Summary
AgentFrameworkAgent (python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py) emits a terminal MessagesSnapshotEvent at the end of most runs whenever there is accumulated text, tool calls/results, or reasoning. That snapshot is built as request messages + this turn’s assistant/tool output — i.e. a full transcript replacement, not a delta.
For clients such as @assistant-ui/react-ag-ui, MESSAGES_SNAPSHOT triggers a full replace of the local message list (applyExternalMessages). That forces the AG-UI request payload (and thus the snapshot) to carry the entire UI history on every turn, even when the app already persists history server-side via Agent Framework HistoryProvider.
This is costly and easy to get wrong. Incremental AG-UI events (TEXT_MESSAGE_*, TOOL_CALL_*, …) already describe the turn; a mandatory full snapshot at the end is redundant for many apps and actively harmful for HistoryProvider-based designs.
Current behavior
Near end of run_agent_stream:
should_emit_snapshot = (
flow.pending_tool_calls or flow.tool_results or flow.accumulated_text or flow.reasoning_messages
)
...
snapshot_event = _build_messages_snapshot(flow, snapshot_messages) # snapshot_messages ≈ request messages
...
yield snapshot_event # unless A2UI / predictive suppressions
So for a normal chat turn with text, the client always receives a terminal full-list snapshot.
Notably, the code already suppresses the terminal snapshot for A2UI runs, with a comment that other AG-UI frameworks often emit no terminal snapshot and that the next turn’s history can be reconstructed from streamed events. That same reasoning applies to plain chat + HistoryProvider.
Why this is painful
-
UI wipe / truncation bugs
If the server trims the AG-UI messages field to “latest user only” (to avoid duplicating HistoryProvider context for the model), the terminal snapshot also becomes “latest user + this assistant reply”. assistant-ui then replaces the panel and older bubbles disappear, even though SQLite/history still has the full original transcript.
-
Bandwidth & coupling
Every successful turn re-sends the whole conversation in SSE, even though the client already has prior messages and already applied streaming deltas.
-
Split-brain with AF persistence
Apps that intentionally put durability in HistoryProvider / SessionStateProvider (no AG-UI Thread Snapshot store) still get CopilotKit-style full-list reconciliation forced on them.
Expected / proposed direction
Please consider one or more of:
-
Opt-out / opt-in for terminal MessagesSnapshotEvent on AgentFrameworkAgent / AgentConfig (e.g. emit_messages_snapshot: bool = True for backward compatibility, or default False for non-snapshot-store setups).
-
Document clearly that MESSAGES_SNAPSHOT is a full authoritative list, so request messages must be the full UI transcript if the client applies the event as a replace — and that HistoryProvider users should either keep the full client transcript in the AG-UI payload for snapshot purposes, or disable the terminal snapshot.
-
Longer-term: support an incremental end-of-turn signal (only new messages / ids for this run) instead of requiring a full list, for clients that already own history locally or via AF providers.
Comparison
Other AG-UI stacks (e.g. Agno’s AG-UI path) often rely on streaming deltas + a separate session/history API and do not emit a terminal full MESSAGES_SNAPSHOT for ordinary chat. MAF’s A2UI path already skips the terminal snapshot for ordering reasons; plain chat would benefit from the same flexibility.
Environment
- Package:
agent-framework-ag-ui (Python)
- Client:
@assistant-ui/react-ag-ui + HttpAgent (applies MESSAGES_SNAPSHOT as full replace)
- Persistence: AF
HistoryProvider (no AG-UI snapshot_store)
Questions for maintainers
- Is the terminal full snapshot considered required by AG-UI / CopilotKit compatibility, or is it a convenience that can be optional?
- Would you accept a PR adding
emit_messages_snapshot (or similar) on the AG-UI agent config, defaulting to current behavior?
Thanks!
Summary
AgentFrameworkAgent(python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py) emits a terminalMessagesSnapshotEventat the end of most runs whenever there is accumulated text, tool calls/results, or reasoning. That snapshot is built as requestmessages+ this turn’s assistant/tool output — i.e. a full transcript replacement, not a delta.For clients such as
@assistant-ui/react-ag-ui,MESSAGES_SNAPSHOTtriggers a full replace of the local message list (applyExternalMessages). That forces the AG-UI request payload (and thus the snapshot) to carry the entire UI history on every turn, even when the app already persists history server-side via Agent FrameworkHistoryProvider.This is costly and easy to get wrong. Incremental AG-UI events (
TEXT_MESSAGE_*,TOOL_CALL_*, …) already describe the turn; a mandatory full snapshot at the end is redundant for many apps and actively harmful for HistoryProvider-based designs.Current behavior
Near end of
run_agent_stream:So for a normal chat turn with text, the client always receives a terminal full-list snapshot.
Notably, the code already suppresses the terminal snapshot for A2UI runs, with a comment that other AG-UI frameworks often emit no terminal snapshot and that the next turn’s history can be reconstructed from streamed events. That same reasoning applies to plain chat +
HistoryProvider.Why this is painful
UI wipe / truncation bugs
If the server trims the AG-UI
messagesfield to “latest user only” (to avoid duplicatingHistoryProvidercontext for the model), the terminal snapshot also becomes “latest user + this assistant reply”. assistant-ui then replaces the panel and older bubbles disappear, even though SQLite/history still has the full original transcript.Bandwidth & coupling
Every successful turn re-sends the whole conversation in SSE, even though the client already has prior messages and already applied streaming deltas.
Split-brain with AF persistence
Apps that intentionally put durability in
HistoryProvider/SessionStateProvider(no AG-UI Thread Snapshot store) still get CopilotKit-style full-list reconciliation forced on them.Expected / proposed direction
Please consider one or more of:
Opt-out / opt-in for terminal
MessagesSnapshotEventonAgentFrameworkAgent/AgentConfig(e.g.emit_messages_snapshot: bool = Truefor backward compatibility, or defaultFalsefor non-snapshot-store setups).Document clearly that
MESSAGES_SNAPSHOTis a full authoritative list, so requestmessagesmust be the full UI transcript if the client applies the event as a replace — and that HistoryProvider users should either keep the full client transcript in the AG-UI payload for snapshot purposes, or disable the terminal snapshot.Longer-term: support an incremental end-of-turn signal (only new messages / ids for this run) instead of requiring a full list, for clients that already own history locally or via AF providers.
Comparison
Other AG-UI stacks (e.g. Agno’s AG-UI path) often rely on streaming deltas + a separate session/history API and do not emit a terminal full
MESSAGES_SNAPSHOTfor ordinary chat. MAF’s A2UI path already skips the terminal snapshot for ordering reasons; plain chat would benefit from the same flexibility.Environment
agent-framework-ag-ui(Python)@assistant-ui/react-ag-ui+HttpAgent(appliesMESSAGES_SNAPSHOTas full replace)HistoryProvider(no AG-UIsnapshot_store)Questions for maintainers
emit_messages_snapshot(or similar) on the AG-UI agent config, defaulting to current behavior?Thanks!