Skip to content

Python: Python / AG-UI: Is stacking Thread Snapshot history with per-agent HistoryProvider intended? #8075

Open

Description

@likebean

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):

  1. messages: [{ "role": "user", "content": "Write a tagline for a budget-friendly eBike." }]
  2. messages: [{ "role": "user", "content": "Make it shorter." }]

Turn 2 should log writer stacked=True (or equivalent: final ≈ input + history).

Questions for maintainers

  1. Is stacking AG-UI Thread Snapshot history with each participant’s HistoryProvider intended for AG-UI workflows?
  2. For AG-UI Workflow + SnapshotStore + agents with HistoryProvider, what is the recommended way to manage conversation history?
  3. 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.

Activity

  1. added
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 4, 2026
  2. changed the title [-]Python / AG-UI: Is stacking Thread Snapshot history with per-agent HistoryProvider intended?[/-] [+]Python: Python / AG-UI: Is stacking Thread Snapshot history with per-agent HistoryProvider intended?[/+] on Sep 4, 2026
  3. moonbox3 commented on Sep 7, 2026

    @moonbox3
    Contributor

    Thanks for the detailed report. The current code appears consistent with your explanation: AG-UI replays the snapshot transcript while the cached workflow retains participant sessions, allowing overlapping history to reach the model.

    This looks like a history-composition gap related to #7756. The snapshot growth concern is also valid: max_snapshots bounds the number of stored threads, not the size of each thread’s transcript.

    This needs team triage to establish the intended history-ownership model and determine the appropriate fix and documentation.

  4. added
    ag-uiUsage: [Issues, PRs], Target: AG-UI protocol integration
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

ag-uiUsage: [Issues, PRs], Target: AG-UI protocol integrationpythonUsage: [Issues, PRs], Target: Python

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions