Skip to content

Python: [Bug]: Purview cache undercounts bytes when growing an entry at capacity #8988

Closed

Description

@dhairyajangir

Description

Replacing a cache entry with a larger value can make InMemoryCacheProvider undercount its stored bytes and exceed max_size_bytes, even when each individual value is smaller than the limit.

set() subtracts the old entry's size but leaves that entry in _cache while calling _evict_if_needed(). If the old entry has the earliest expiry, eviction finds it and subtracts its size a second time. Other entries then remain cached even though they should have been evicted. Removing the remaining entries can leave _current_size_bytes negative.

In the example below, a 20-byte cache holds values estimated at 24 bytes but reports 16. Expected behavior is to retain the replacement, evict newer, and return to zero tracked bytes when emptied.

Code Sample

import asyncio
from unittest.mock import patch

from agent_framework_purview._cache import InMemoryCacheProvider


async def main():
    cache = InMemoryCacheProvider(max_size_bytes=20)
    with patch("agent_framework_purview._cache.time.time", return_value=1000.0):
        # JSON string quotes add two bytes to the estimated size.
        await cache.set("older", "a" * 6, ttl_seconds=10)   # 8 bytes
        await cache.set("newer", "b" * 6, ttl_seconds=20)   # 8 bytes
        await cache.set("older", "c" * 14, ttl_seconds=30) # 16 bytes

        print(await cache.get("newer"))
        print(cache._current_size_bytes)
        print(sum(entry[2] for entry in cache._cache.values()))
        await cache.remove("older")
        await cache.remove("newer")
        print(cache._current_size_bytes)


asyncio.run(main())

Error Messages / Stack Traces

No exception is raised. Actual output:


bbbbbb
16
24
-8


Expected output:


None
16
16
0

Package Versions

Editable source checkout at e9857bd75a21843803431a2e7dfa41ee9b017ac7: agent-framework-core 1.20.0 and agent-framework-purview 1.0.0b261002.

Python Version

Python 3.12.14 on Linux.

Additional Context

The default ScopedContentProcessor uses this cache implementation. I am working on a focused fix that removes the old mapping before eviction, so its heap entry is treated as stale, with a regression test for replacement at capacity. The existing 17 cache tests pass before the change but do not cover this interaction.

Investigated and tested with Codex assistance. The reproduction is entirely local.

Activity

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

    @github-actions
    Contributor

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

    Agent analysis

    The bug reproduces in python/packages/purview/agent_framework_purview/_cache.py::InMemoryCacheProvider.set when the earliest-expiring key is replaced by a larger value while near capacity, causing _evict_if_needed() to deduct the still-mapped old entry twice. Minimal repro: freeze time, place two 8-byte entries in a 20-byte cache, replace the earlier-expiring entry with a 16-byte value, and assert the other entry is evicted and tracked size returns to zero after removal.

    • Failing test: python/packages/purview/tests/purview/test_cache.py::TestInMemoryCacheProvider::test_growing_entry_at_capacity_maintains_size_and_evicts_other_entry
    • Files examined: python/packages/purview/agent_framework_purview/_cache.py, python/packages/purview/tests/purview/test_cache.py, python/packages/purview/agent_framework_purview/_processor.py, python/packages/purview/pyproject.toml
    • Tests run: existing test_cache.py suite (17 passed), test_growing_entry_at_capacity_maintains_size_and_evicts_other_entry (failed)
    • Reported version: agent-framework-purview 1.0.0b261002
    • Current version: agent-framework-purview 1.0.0b261002
  4. added
    securityUsage: [Issues, PRs], Target: security of the codebase
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Oct 2, 2026
  5. dhairyajangir commented on Oct 28, 2026

    @dhairyajangir
    Author

    Eduard van Valkenburg (@eavanvalkenburg) Following up on #8988: the fix and regression test are in this PR, and the automated review on head db8cc2e reported no findings. The Python test matrix and code-quality checks passed.

    The merge gate is still failing on team_check and command_check; the corresponding checks from the original review run were cancelled. Could you please check whether those prerequisites and the merge gate can be rerun, and review the PR when you have a chance? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

pythonUsage: [Issues, PRs], Target: Pythonready-for-implementationMaintainers agree the problem and direction are ready for implementation.reproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflowsecurityUsage: [Issues, PRs], Target: security of the codebase

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions