Перейти к содержимому
Экосистема AI Vibe
← Назад к AI Vibe News

Редакция 30 июля 2026 г.

Разборы

Потерянное обновление: когда AI-агенты правят один файл, а вклад исчезает без ошибки

Потерянное обновление: когда AI-агенты правят один файл, а вклад исчезает без ошибки

Соло-разработчик, который гоняет два и более субагента на один артефакт — файл плана, scratchpad, общую заметку в репозитории — рискует потерять вклад даже при «успешных» ответах: оба агента получают ACK, а в финальном файле остаётся работа только одного. Это не глюк IDE и не «плохой промпт», а классический lost update в multi-agent workflow: каждый съеденный write уже стоит потраченных токенов, а система не подсвечивает потерю красной строкой. В разборе @alex_spinov показывает механизм на воспроизводимом симуляторе и предлагает pre-write compare-and-set gate с измерениями.

Почему ACK не гарантирует целостность при нескольких AI-агентах

В сценарии, который описывает автор, N агентов дописывают свои секции в один общий файл. Корректный исход — все N вкладов на месте. Цикл каждого агента простой: прочитать текущее состояние, поработать локально, записать результат.

Проблема в режиме last-writer-wins (write_lww): запись безусловно перезаписывает значение, версия инкрементируется, событие попадает в append-only WAL, а вызывающий получает "ACK" — даже если агент читал устаревшую версию. Потерянный вклад — это агент, чей write был подтверждён, но чья секция отсутствует в финальном value.

Для vibe-coding это болезненнее, чем в классической БД: исчезнувший write — не абстрактная транзакция, а уже оплаченная работа модели, которую оркестратор посчитал успешной.

Симулятор вместо продакшена — и почему выводы всё равно переносимы

Эксперимент с пятью агентами — не отчёт о коммерческих coding-агентах в бою. Автор собрал офлайн-симулятор lostwrite_sim.py на Python 3.13.5: только стандартная библиотека, без сети, ключей, потоков и реальных денег. «Конкурентность» задаётся явными расписаниями чередования (interleaving schedules), а не настоящим параллелизмом — результат воспроизводим. Код писался с помощью AI-ассистента; три локальных запуска дали байт-идентичный STDOUT с sha256 1df08c5e38894622314a2b052684303853b9b8fa9508eaabdfd877970c88983b.

Хранилище VersionedStore держит номер версии, frozenset вкладов и WAL. Модель намеренно упрощена, но бьёт в реальную боль соло-workflow: несколько субагентов на общий state — plan file, scratchpad, строка в БД, GitHub issue — без координации записи.

Compare-and-set перед write: как gate ловит гонку

Альтернатива LWW — write_cas(expected_version, agent_id, new_value, enforce=True). Коммит происходит только если self.version == expected_version; иначе возвращается "CAS_FAIL". При ACK обновляется value, растёт версия, событие пишется в WAL.

Логика retry в симуляторе:

res = store.write_cas(a.base, a.aid, a.newval, enforce=not disable_version_check)
if res == "ACK":
    a.acked = True
else:
    cas_failures += 1
    if a.retries < max_retries:
        a.retries += 1
        a.pc = "READ"      # re-read, re-work, re-write
    else:
        a.refused = True   # fail-closed

Автор называет это optimistic concurrency control; пессимистичная альтернатива — lease/lock, когда агент ждёт вместо повторной попытки. Фальсификатор F1 — тот же код с enforce=False на проверке версии — при worst-case N=5 снова даёт 4 lost updates, как без gate. Роль именно compare, а не обвязки, доказана.

Цифры: худший случай, честная выборка и цена retries

Автор разделяет детерминированный worst-case (все читают до любой записи) и Monte Carlo fair sample (равномерный случайный выбор готового агента на каждом тике, seed 20260728).

Worst-case при N=5:

Метрика Без gate (LWW) С gate (CAS)
writes acknowledged 5 5
contributions в финале 1 5
lost_updates (ACK, но вклад пропал) 4 0
integrity_ok False True
cas_failures 0 4
retries 0 4
total_runs (выполненная работа) 5 9

В fair sweep (trials = 40000 // N на каждое N) картина жёстче для LWW и ровная для gate:

N trials NO_GATE: runs с потерями mean lost GATE: потери
2 20000 75,0% 0,75 0
3 13333 97,3% 1,59 0
5 8000 100,0% 3,35 0
8 5000 100,0% 6,14 0

Суммарно 46 333 fair interleavings по всем N — gate даёт 0 потерь во всех выборках. При worst-case без gate потеря равна N−1; с gate — 0 lost, delta cost = N−1 retry.

Отдельный сценарий fail-closed: gate с max_retries=0, worst-case N=87 cas_failures, 7 refusals с видимыми id агентов, 0 silent lost_updates, integrity_ok=False (семь вкладов реально не попали). Это честнее, чем тихий overwrite. Константа COST_UNIT = 1 — учётная единица работы, не доллары и не измеренная цена токенов: gate увеличивает суммарную работу (retries), но не возвращает уже потраченные токены.

Что заложить в agent workflow и rules

Практический вывод для оркестрации субагентов на общем state:

  • Pre-write concurrency gate — проверка версии перед commit — уместен для sub-agent dispatch, swarm и retrying planner с workers на одном артефакте.
  • Append-only WAL фиксирует факт записи (version, agent_id) без значения: видно, что писали и сколько раз, но не восстанавливает потерянные вклады при чтении текущего value. Учёт — не контроль.
  • Один «зелёный» запуск не доказывает безопасность: при N=2 четверть interleavings сериализуется без потерь и даёт ложное спокойствие.
  • При исчерпании retry лучше fail-closed с явными refusals, чем silent overwrite.
  • Автор отделяет задачу от revert-guard (когда один агент возвращает откатанный код) и от double-charge одного агента на retry.

Для соло-разработчика, который собирает цепочку «планировщик + несколько workers на один файл», паттерн compare-and-set перед write и retry при CAS_FAIL — переносимый минимум. Конкретную интеграцию в IDE или оркестратор автор не описывает; зато показывает, почему без gate даже пять ACK не спасают четыре вклада.

Источники