Source: FEAT-011 US3 checkpoint (tasks.md §162) — deferred, non-blocking.
The per-session IdempotencyStore (T011, FR-031a) is wired as a process-wide module-level dict _idempotency_stores: dict[app_session_id, IdempotencyStore] instead of being stored on the AppSession. AppSession is a frozen dataclass with no store attribute, so there is no true per-session ownership; cleanup relies on an explicit drop_idempotency_store call.
Location: src/agenttower/app_contract/mutations.py:552-577.
Fix: attach the IdempotencyStore to the session object (or the SessionRegistry) so its lifetime is bound to the session by construction.
Related but distinct: #23 (send_input idempotency TOCTOU).
Source: FEAT-011 US3 checkpoint (tasks.md §162) — deferred, non-blocking.
The per-session
IdempotencyStore(T011, FR-031a) is wired as a process-wide module-level dict_idempotency_stores: dict[app_session_id, IdempotencyStore]instead of being stored on theAppSession.AppSessionis a frozen dataclass with no store attribute, so there is no true per-session ownership; cleanup relies on an explicitdrop_idempotency_storecall.Location:
src/agenttower/app_contract/mutations.py:552-577.Fix: attach the
IdempotencyStoreto the session object (or theSessionRegistry) so its lifetime is bound to the session by construction.Related but distinct: #23 (send_input idempotency TOCTOU).