What happened
MemMachine.add_episodes (packages/server/src/memmachine_server/main/memmachine.py:735, as of 98f3cde speedkick) runs in two serial stages:
await episode_storage.add_episodes(...) (lines 754-759): one INSERT into the episode store, awaited alone.
- Only after that returns: the episodic write (
episodic_session.add_memory_episodes) and the semantic write (semantic_session_manager.add_message) run concurrently via asyncio.gather (lines 761-788).
Each add request therefore pays the episode-store round trip before any memory write starts. Episodic and semantic are already concurrent with each other; the episode store is not concurrent with them.
Why it cannot just be added to the gather
Both later stages consume the stored Episode, specifically its uid, and that uid is generated by the database. SqlAlchemyEpisodeStore.add_episodes inserts with insert(Episode).returning(Episode) and builds the returned models from the persisted rows (common/episode_store/episode_sqlalchemy_store.py:228-236). Neither the episodic write nor SemanticSessionManager._add_single_episode (which records episode.uid via add_message_to_sets, semantic_memory/semantic_session_manager.py:129) can start until the id exists.
Options
- Assign episode ids in the application (UUIDv7 or similar) when building the
Episode from the EpisodeEntry. The three writes can then run in one gather/TaskGroup. This changes Episode.id from an autoincrement integer (get_episode, get_episodes and delete_episodes all parse ids with int()) and needs a migration plus a decision about existing integer ids.
- Keep the order but decide what happens on partial failure. Today a failure in the second stage leaves the episode row committed: reproduced with
types: ["semantic"] while semantic memory is disabled, where the request returns 500 but the episode is stored. If the writes become concurrent, the same question applies in more combinations, so the failure contract should be settled together with the change.
No latency measured yet. The cost is one episode-store INSERT round trip per add request, on the critical path.
Related
Investigated and written by Claude (Claude Code), filed from the account of the user who commissioned the investigation.
What happened
MemMachine.add_episodes(packages/server/src/memmachine_server/main/memmachine.py:735, as of98f3cdespeedkick) runs in two serial stages:await episode_storage.add_episodes(...)(lines 754-759): one INSERT into the episode store, awaited alone.episodic_session.add_memory_episodes) and the semantic write (semantic_session_manager.add_message) run concurrently viaasyncio.gather(lines 761-788).Each add request therefore pays the episode-store round trip before any memory write starts. Episodic and semantic are already concurrent with each other; the episode store is not concurrent with them.
Why it cannot just be added to the gather
Both later stages consume the stored
Episode, specifically itsuid, and that uid is generated by the database.SqlAlchemyEpisodeStore.add_episodesinserts withinsert(Episode).returning(Episode)and builds the returned models from the persisted rows (common/episode_store/episode_sqlalchemy_store.py:228-236). Neither the episodic write norSemanticSessionManager._add_single_episode(which recordsepisode.uidviaadd_message_to_sets,semantic_memory/semantic_session_manager.py:129) can start until the id exists.Options
Episodefrom theEpisodeEntry. The three writes can then run in onegather/TaskGroup. This changesEpisode.idfrom an autoincrement integer (get_episode,get_episodesanddelete_episodesall parse ids withint()) and needs a migration plus a decision about existing integer ids.types: ["semantic"]while semantic memory is disabled, where the request returns 500 but the episode is stored. If the writes become concurrent, the same question applies in more combinations, so the failure contract should be settled together with the change.No latency measured yet. The cost is one episode-store INSERT round trip per add request, on the critical path.
Related
Investigated and written by Claude (Claude Code), filed from the account of the user who commissioned the investigation.