Я на Kotlin пишу либу, использую такую схему: Каждая команда это отдельный сценарий, в сценариях могут быть шаги (уникальные). При поступлении обновления идёт четыре стадии фильтров, которые в aiogram похожи на мидлвари: Logging, Cancel, Handlers, Unresolved. При этом стейты обрабатываются под капотом и разработчику не надо их контролировать.