Summary
When an AG-UI workflow is backed by a Thread Snapshot store, follow-up turns that send messages without resume cause AgentFrameworkWorkflow to reconstruct the full prior thread transcript and pass it into workflow.run(message=...).
At the same time, each workflow participant still has its own agent session / InMemoryHistoryProvider (especially with require_per_service_call_history_persistence=True on local Chat Completions clients).
On turn 2+, the model call therefore sees roughly:
AG-UI snapshot replay (outer) + participant HistoryProvider (inner)
We are looking for maintainer guidance on whether this composition is intentional, and how conversation history is meant to be managed in this setup. We are not proposing a particular fix—we do not know what the intended practice is.
Related (similar dual-layer history, different surface): #7756 (SequentialBuilder + workflow.as_agent() + outer AgentSession). This issue is about the AG-UI Workflow + Thread Snapshot path.
What we observed
Setup:
AgentFrameworkWorkflow(workflow_factory=...) + AG-UI Thread Snapshot store
SequentialBuilder(participants=[writer, reviewer]) (easy to hit: every chat turn is messages without resume)
- Local OpenAI-compatible client (
store=False / no service-managed conversation)
- Participants with
require_per_service_call_history_persistence=True
| Layer |
Turn 1 |
Turn 2 (Make it shorter.) |
AG-UI reconstructed messages |
1 |
>1 (prior transcript + new user) |
| Same cached Workflow instance |
created |
reused |
Writer HistoryProvider |
0 |
>0 |
| Writer final messages to model |
≈ input only |
≈ input + history (stacked=True in our probe) |
Concrete probe line from a local run:
agent=writer workflow_input=2 history_provider=2 final_to_model=4 predicted_sum=4 stacked=True
Notes:
- On a Handoff AG-UI demo, mid-case turns are usually
resume, so this stacking is harder to notice; a post-complete messages-without-resume kickoff on the same instance shows the same pattern.
- On Sequential, turn 2 of normal chat is enough.
Snapshot growth (second concern)
Thread Snapshot history appears to grow without compaction / truncation. For long multi-turn threads, the reconstructed messages list alone can become very large before it is even combined with per-agent history. We would also appreciate guidance on how this is expected to be handled.
Minimal reproduction (backend)
from agent_framework import Agent, ChatContext, ChatMiddleware
from agent_framework.ag_ui import (
AgentFrameworkWorkflow,
InMemoryAGUIThreadSnapshotStore,
add_agent_framework_fastapi_endpoint,
)
from agent_framework.openai import OpenAIChatCompletionClient
from agent_framework.orchestrations import SequentialBuilder
from fastapi import FastAPI
class ContextProbe(ChatMiddleware):
def __init__(self, agent_id: str) -> None:
self._agent_id = agent_id
async def process(self, context: ChatContext, call_next):
before = list(context.messages)
history = []
if context.session is not None:
history = list(context.session.state.get("in_memory", {}).get("messages", []) or [])
await call_next()
final = list(context.messages)
print(
f"[probe] {self._agent_id}: input={len(before)} "
f"history={len(history)} final={len(final)} "
f"stacked={len(final) == len(before) + len(history) and len(history) > 0 and len(before) > 1}"
)
client = OpenAIChatCompletionClient(
model="...",
api_key="...",
base_url="...", # local Chat Completions; no service-managed history
)
writer = Agent(
id="writer",
name="writer",
instructions="You are a concise copywriter.",
client=client,
middleware=[ContextProbe("writer")],
require_per_service_call_history_persistence=True,
)
reviewer = Agent(
id="reviewer",
name="reviewer",
instructions="You are a short reviewer.",
client=client,
middleware=[ContextProbe("reviewer")],
require_per_service_call_history_persistence=True,
)
snapshot_store = InMemoryAGUIThreadSnapshotStore()
def workflow_factory(_thread_id: str):
return SequentialBuilder(participants=[writer, reviewer]).build()
app = FastAPI()
add_agent_framework_fastapi_endpoint(
app=app,
agent=AgentFrameworkWorkflow(
workflow_factory=workflow_factory,
snapshot_store=snapshot_store,
),
path="/sequential_demo",
snapshot_store=snapshot_store,
snapshot_scope_resolver=lambda _request: "demo",
)
Then POST twice on the same threadId (no resume):
messages: [{ "role": "user", "content": "Write a tagline for a budget-friendly eBike." }]
messages: [{ "role": "user", "content": "Make it shorter." }]
Turn 2 should log writer stacked=True (or equivalent: final ≈ input + history).
Questions for maintainers
- Is stacking AG-UI Thread Snapshot history with each participant’s HistoryProvider intended for AG-UI workflows?
- For AG-UI Workflow + SnapshotStore + agents with HistoryProvider, what is the recommended way to manage conversation history?
- Thread Snapshots appear to grow without compaction/truncation—what is the recommended way to think about long multi-turn threads in this model?
Environment
microsoft/agent-framework Python packages (workspace checkout)
agent-framework-ag-ui + agent-framework-orchestrations
- Python 3.12+
- OpenAI-compatible local Chat Completions endpoint (no service-side conversation store)
Happy to add more traces or adjust the repro if that helps.
Summary
When an AG-UI workflow is backed by a Thread Snapshot store, follow-up turns that send
messageswithoutresumecauseAgentFrameworkWorkflowto reconstruct the full prior thread transcript and pass it intoworkflow.run(message=...).At the same time, each workflow participant still has its own agent session /
InMemoryHistoryProvider(especially withrequire_per_service_call_history_persistence=Trueon local Chat Completions clients).On turn 2+, the model call therefore sees roughly:
AG-UI snapshot replay (outer) + participant HistoryProvider (inner)
We are looking for maintainer guidance on whether this composition is intentional, and how conversation history is meant to be managed in this setup. We are not proposing a particular fix—we do not know what the intended practice is.
Related (similar dual-layer history, different surface): #7756 (
SequentialBuilder+workflow.as_agent()+ outerAgentSession). This issue is about the AG-UI Workflow + Thread Snapshot path.What we observed
Setup:
AgentFrameworkWorkflow(workflow_factory=...)+ AG-UI Thread Snapshot storeSequentialBuilder(participants=[writer, reviewer])(easy to hit: every chat turn ismessageswithoutresume)store=False/ no service-managed conversation)require_per_service_call_history_persistence=TrueMake it shorter.)messagesHistoryProviderstacked=Truein our probe)Concrete probe line from a local run:
Notes:
resume, so this stacking is harder to notice; a post-completemessages-without-resumekickoff on the same instance shows the same pattern.Snapshot growth (second concern)
Thread Snapshot history appears to grow without compaction / truncation. For long multi-turn threads, the reconstructed
messageslist alone can become very large before it is even combined with per-agent history. We would also appreciate guidance on how this is expected to be handled.Minimal reproduction (backend)
Then POST twice on the same
threadId(noresume):messages: [{ "role": "user", "content": "Write a tagline for a budget-friendly eBike." }]messages: [{ "role": "user", "content": "Make it shorter." }]Turn 2 should log writer
stacked=True(or equivalent:final ≈ input + history).Questions for maintainers
Environment
microsoft/agent-frameworkPython packages (workspace checkout)agent-framework-ag-ui+agent-framework-orchestrationsHappy to add more traces or adjust the repro if that helps.