What happened
CountCachingEpisodeStorage (common/episode_store/count_caching_episode_storage.py) keeps _count_cache: dict[str, _CacheEntry] (:49) behind an asyncio.Lock (:50). It is filled on the first session-only count (:128-131), incremented only by this process's own add_episodes (:64-71), and cleared only by this process's own deletes (:148-153, :167-171). There is no TTL and no cross-process invalidation. Its own docstring (:39-44) says: "As an incoherent cache, changes to the underlying store are not reflected in the cache. Making this cache unsuited to concurrent deployment."
The wrapper is enabled by episode_store.with_count_cache, which defaults to true (common/configuration/__init__.py:89-92), and POST /projects/episode_count/get reads through it (MemMachine.episodes_count, main/memmachine.py:1130).
With two replicas: once replica A has served a count for a session, adds and deletes served by replica B never reach A's cache, and A keeps returning the stale number until A itself deletes something.
Expected
Either the cache is off by default and documented as a single-process optimization, or the count is coherent: a counter maintained in the same transaction as the insert, or a short TTL with the staleness stated. docs/open_source/configuration.mdx recommends the flag today with no caveat.
Notes
Code read at d6068cdbf (main), paths under packages/server/src/memmachine_server/. Not reproduced with two live replicas; the cache has no invalidation path except local writes, so the staleness follows from the code.
🤖 Written by Claude Code (Claude Fable 5.1) on behalf of @edwinyyyu.
What happened
CountCachingEpisodeStorage(common/episode_store/count_caching_episode_storage.py) keeps_count_cache: dict[str, _CacheEntry](:49) behind anasyncio.Lock(:50). It is filled on the first session-only count (:128-131), incremented only by this process's ownadd_episodes(:64-71), and cleared only by this process's own deletes (:148-153,:167-171). There is no TTL and no cross-process invalidation. Its own docstring (:39-44) says: "As an incoherent cache, changes to the underlying store are not reflected in the cache. Making this cache unsuited to concurrent deployment."The wrapper is enabled by
episode_store.with_count_cache, which defaults to true (common/configuration/__init__.py:89-92), andPOST /projects/episode_count/getreads through it (MemMachine.episodes_count,main/memmachine.py:1130).With two replicas: once replica A has served a count for a session, adds and deletes served by replica B never reach A's cache, and A keeps returning the stale number until A itself deletes something.
Expected
Either the cache is off by default and documented as a single-process optimization, or the count is coherent: a counter maintained in the same transaction as the insert, or a short TTL with the staleness stated.
docs/open_source/configuration.mdxrecommends the flag today with no caveat.Notes
Code read at
d6068cdbf(main), paths underpackages/server/src/memmachine_server/. Not reproduced with two live replicas; the cache has no invalidation path except local writes, so the staleness follows from the code.🤖 Written by Claude Code (Claude Fable 5.1) on behalf of @edwinyyyu.