Skip to content

Python: [Bug]: CosmosHistoryProvider re-persists replayed transcript prefixes, causing duplicate history growth #8268

Closed

Description

Description

CosmosHistoryProvider.save_messages() persists every supplied message, including messages that are already stored for the same session and provider source. A caller that supplies a cumulative transcript can therefore silently add another copy of the persisted prefix.

For example:

  • Existing persisted history: [A, B]
  • Later cumulative input: [A, B, C]
  • Expected persisted history: [A, B, C]
  • Resulting logical persisted history under current write behavior: [A, B, A, B, C]

This logical result is inferred from directly observing that the second save submits three new Cosmos upserts (A, B, and C) instead of only one (C); the reproduction does not perform a live Cosmos readback. Each document receives a fresh UUID, so Cosmos upsert semantics do not replace the earlier logical messages. Replaying successively longer transcripts can consequently produce superlinear persistent-history growth.

The shared history helper filter_new_messages(existing, incoming) defines the established behavior for both append-only inputs and full-transcript replay while preserving legitimate repeated turns. The in-memory, file, and Redis history providers apply that behavior, but the Cosmos provider currently does not.

Code Sample

import asyncio
from collections.abc import AsyncIterator
from unittest.mock import AsyncMock, MagicMock

from agent_framework import Message
from agent_framework_azure_cosmos import CosmosHistoryProvider


async def items(messages: list[Message]) -> AsyncIterator[dict[str, object]]:
    for message in messages:
        yield {"message": message.to_dict()}


async def main() -> None:
    existing = [
        Message(role="user", contents=["A"]),
        Message(role="assistant", contents=["B"]),
    ]
    incoming = [
        Message(role="user", contents=["A"]),
        Message(role="assistant", contents=["B"]),
        Message(role="user", contents=["C"]),
    ]
    container = MagicMock()
    container.query_items.return_value = items(existing)
    container.execute_item_batch = AsyncMock(return_value=[])
    provider = CosmosHistoryProvider(source_id="mem", container_client=container)

    await provider.save_messages("s1", incoming)

    operations = container.execute_item_batch.await_args.kwargs["batch_operations"]
    assert len(operations) == 1  # Fails on current main: 3 == 1
    assert operations[0][1][0]["message"]["contents"][0]["text"] == "C"


asyncio.run(main())

Error Messages / Stack Traces

There is no runtime exception. The failure is silent duplicate persistence and history growth. The deterministic regression assertion on current main is:

E       AssertionError: assert 3 == 1

Package Versions

agent-framework: 1.18.0; agent-framework-core: 1.18.0; agent-framework-azure-cosmos: 1.0.0b260910; source: c37de51

Python Version

Python 3.12.12

Additional Context

This appears to be a remaining provider-specific instance of the history replay behavior reported in #7211 and addressed for the in-memory, file, and Redis providers in #7242. The shared helper introduced there explicitly handles append-only input and full-transcript replay while preserving legitimate duplicate turns.

A related follow-up, #8223, was fixed by #8224, which applied the same helper to the MessagePack branch of FileHistoryProvider; Cosmos remains a separate affected persistence backend.

At the direct provider level, supplying only message deltas avoids replaying the stored prefix. That does not protect call paths that validly pass an accumulated transcript.

Activity

  1. added
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 10, 2026
  2. added
    reproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
    on Sep 10, 2026
  3. github-actions commented on Sep 10, 2026

    @github-actions
    Contributor

    🤖 Automated triage reproduction notes (agent-authored — trust but verify)

    Agent analysis

    The bug reproduces in python/packages/azure-cosmos/agent_framework_azure_cosmos/_history_provider.py::CosmosHistoryProvider.save_messages at lines 175-207 when incoming messages contain an already-persisted transcript prefix. The method creates a fresh UUID-backed upsert for every supplied message without loading existing history. Minimal repro: mock existing history as [A, B], call save_messages("s1", [A, B, C]), and observe three batch operations instead of one.

    • Failing test: python/packages/azure-cosmos/tests/test_cosmos_history_provider.py::TestCosmosHistoryProviderSaveMessages::test_saves_only_new_messages_from_replayed_transcript
    • Files examined: python/packages/azure-cosmos/agent_framework_azure_cosmos/_history_provider.py, python/packages/azure-cosmos/tests/test_cosmos_history_provider.py, python/packages/core/agent_framework/_sessions.py, python/packages/azure-cosmos/pyproject.toml
    • Tests run: TestCosmosHistoryProviderSaveMessages.test_saves_messages, TestCosmosHistoryProviderSaveMessages.test_saves_only_new_messages_from_replayed_transcript
    • Reported version: 1.0.0b260910
    • Current version: 1.0.0b260910
  4. added
    memoryUsage: [Issues, PRs], Target: memory related features
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

memoryUsage: [Issues, PRs], Target: memory related featurespythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions