Голосовые системы

Один turn: один результат, атомарная запись и безопасные реплики

Статус: предложение, не реализовано. Инвазивность: Средняя. Основа: webrtc-komaroff@772de4a, 16.09.2026.

Agent сохраняет runtime и историю двумя разными операциями. При ошибке записи общий retry снова вызывает workflow. Между этими вызовами модель и tools уже могли выполнить работу. Кроме того, обе Ruby реализации подписываются на run subject без queue group: при нескольких репликах один publish доходит до каждой.

Обработка turn и фиксация результата

Queue group решает выбор исполнителя для одного сообщения. Идемпотентность решает повторное поступление того же логического turn. Транзакция решает совместную фиксацию runtime и истории. Эти три свойства нужны одновременно и не заменяют друг друга.

Предлагаемая правка

Сначала добавить одинаковый queue group на run subject выбранного Ruby runtime. Во время rolling update старый plain subscriber продолжит получать копии, поэтому переход проверить отдельно и не допускать неконтролируемого смешанного режима.

Следом ввести реестр turn с уникальным ключом (session_id, turn_id, revision). Revision отличает повтор доставки от edit той же реплики. Запись хранит request fingerprint, статус и результат. Повтор завершённого turn возвращает сохранённый ответ; несовпадение payload при том же ключе — явная ошибка. Session ownership проверяется до обращения к состоянию.

Workflow не должен исполняться внутри долгой SQL-транзакции с удержанием row lock. Нужна короткая операция claim/lease, выполнение с fencing или session-serialization и короткая финальная транзакция. В ней вместе фиксируются runtime, обе строки истории, результат turn и события outbox.

Публикация outbox выполняется после commit. Она может повториться, поэтому событие получает стабильный event_id, а получатель дедуплицирует его. Для внешнего инструмента тоже нужен idempotency key; outbox сам по себе не отменяет уже совершённый внешний эффект.

Что проверять

Исходная точка: до 4 workflow executions при значении RUNTIME_CONFLICT_RETRIES=3 в одной попытке Agent; фактическая частота UNKNOWN. UI publisher подавляет повторы только внутри своего одного вызова, не между повторными запросами.

Цели для тестов: 100 повторов одного turn дают одну логическую фиксацию и один стабильный ответ; две реплики Ruby исполняют один запрос одним worker; crash после commit до publish не теряет событие; duplicate publish не добавляет вторую реплику в UI. Для external side-effect fixture число effects равно одному, если backend поддерживает ключ; неподдерживаемые backends требуют reconciliation, не обещания exactly-once.

Отдельно проверить два разных turn одной сессии, две сессии одного пользователя, edit/replay и потерю lease. Снять retry ratio, duplicate-effect count, p95 lock/claim wait, время от commit до доставки события.

Риски и внедрение

Это миграция схемы и протокола. Сначала добавить таблицы и совместимый reader, затем включить новый writer для тестовых сессий. Один turn имеет одного выбранного владельца; dual-write в обе архитектуры недопустим для tools с эффектами. Rollback возвращает маршрутизацию, сохраняя уже записанные новые данные и read compatibility.

Основания

Agent retry, отдельный UPDATE, history transaction, Ruby subscription. NATS queue groups и PostgreSQL transactions подтверждают свойства доставки и атомарности. Context7: /nats-io/nats.docs, /websites/postgresql_18, 16.09.2026; проект использует Postgres 16, поэтому для SQL baseline дана его версия. Outbox — предлагаемая композиция, не найденный компонент проекта.

Все предложения · Фактическая архитектура

Исходник

Скачать Markdown · К материалам исследования