ADR-006: Explicit FSM key strategy
- Status: Accepted
- Owners: maintainers
Context
Conversation state can reasonably be scoped by user, space, thread, or a combination. Google resource names are strings and events do not always contain all dimensions. A hidden key choice causes state leakage or surprising sharing.
Decision
FSM storage uses a structured StorageKey with user, space, and thread
dimensions (no app dimension — implemented as shipped). FSMStrategy
explicitly selects the required dimensions; the default is USER_IN_SPACE
(user + space, thread when present); applications opt into USER or SPACE.
Missing required dimensions yield no key: reads degrade to None/{} and
mutations raise FSMError.
Storage contracts are honest about concurrency: MemoryStorage serializes per
key with process-local asyncio locks; RedisStorage relies on per-command
atomicity only (update_data is NOT cross-process — no Lua). Expiration
support is not implemented. In-memory storage is for tests/development;
Redis support is the optional chattice[redis] extra.
Consequences
State isolation is visible and testable. Apps must choose deliberately when thread semantics matter. Migrations between strategies need an application plan; the framework cannot infer intent.
Alternatives considered
- Always key by user: leaks state across spaces.
- Always key by user/space/thread: fragments direct-message flows and fails when thread data is absent.
- Accept arbitrary strings as keys: flexible but unsafe and non-portable.
Sources
Google Chat Event schema, aiogram FSM strategy, aiogram storage.