Why NextWhy Next
К списку
💻 Разработка10 мин чтения

Что случилось, когда я выпустил несколько ИИ-агентов в один репозиторий

Я думал, что параллельные сессии агентов удвоят производительность. На деле за одну неделю посыпались реальные аварии: угон ветки, коммиты поверх чужой ветки, осиротевшие коммиты, спасённые через reflog, и двойная реализация одной и той же фичи. Типология аварий, которые устраивают параллельные агенты на общем чекауте, и правила, выстроенные на этом опыте.

Главное

Настоящий враг параллельных сессий агентов не способности модели, а общий рабочий каталог. HEAD - это глобальная переменная для всех, поэтому переключение ветки в одной сессии сажает коммит другой сессии не туда. Четыре типа аварий за неделю и истории восстановления, плюс три линии обороны: изоляция через worktree, перепроверка ветки перед коммитом и сужение области проверочного шлюза.

Содержание

Работая с ИИ-агентами, естественным образом входишь во вкус. Пока одна сессия чинит баг, другая пусть рефакторит, а третья расследует иссью, разве нет? Моделей можно запустить сколько угодно, значит, и производительность вырастет во столько же раз.

Я тоже так начал. И через неделю понял: настоящий враг параллельных агентов не способности модели, а рабочий каталог, который они делят.

HEAD - это глобальная переменная

Причина укладывается в одно предложение. Когда один git-чекаут делят несколько сессий, состояние «текущая ветка» становится глобальной переменной для всех.

Представь двух людей, работающих одновременно за одним компьютером, и странность видна сразу; но, запуская агентов, я об этом не подумал. В каждой вкладке терминала висит по сессии, и выглядят они изолированными друг от друга. Но файловая система одна, и HEAD один. В момент, когда одна сессия делает git checkout, почва уходит из-под ног у всех остальных.

У аварий той недели были отчётливые типы.

Угон ветки. Пока сессия A работала в топик-ветке, сессия B переключила ветку под свою задачу. A, не зная об этом, закоммитила, и коммит лёг поверх ветки B. Было и в обратную сторону: в момент, когда A собиралась коммитить, ветка оказалась переключённой на develop, и аварию предотвратил только хук, блокирующий прямые коммиты в защищённые ветки. Не будь хука, коммит прошёл бы как есть.

Осиротевшие коммиты. Топик-ветку сессии A сессия B удалила в ходе уборки. Коммиты A стали сиротами, не принадлежащими ни одной ветке; я перерыл reflog, нашёл хеши коммитов и вернул их через cherry-pick. Повезло, что спас: истеки reflog или не найди я их, работа испарилась бы целиком.

Загрязнение стейджинга. В момент, когда сессия A создавала коммит, в области стейджинга уже лежало удаление файла, застейдженное сессией B. Закоммить она как есть, удаление от B вмешалось бы в коммит A. Я отсёк это, заметив незнакомое изменение при просмотре diff; не глянь агент diff перед коммитом, никто бы и не узнал.

Двойная реализация. Самый обидный тип. Две сессии, не зная о существовании друг друга, реализовали одну и ту же фичу каждая по-своему. Обе сделали добросовестно и хорошо, и одну пришлось выбросить целиком. Время, сэкономленное параллелизацией, вернулось обратно в полном объёме.

Проверочные шлюзы тоже топчут друг друга

Проблема была не только в файлах и ветках. В нашем харнессе есть шлюз, который перед завершением сессии прогоняет статический анализ и тесты по всему репозиторию и не даёт закончить работу в сломанном состоянии. Для одиночной сессии это отличный механизм.

В параллельных сессиях он превратился в капкан, которым сессии хватают друг друга за ноги. Сессия A трогала только документацию, но её шлюз падает из-за ошибки компиляции в файле, который прямо сейчас правит сессия B. A тратит ходы на доказательство «это не моё изменение», а в тяжёлый день полчаса ждала, пока B приберётся. Худший инцидент: одна сессия, чтобы пройти шлюз, взяла и починила код, над которым работала другая. Шлюз сам стал стимулом лезть в чужую работу.

Сама проверка не виновата. Виноватой стала её область «весь репозиторий» - в тот момент, когда репозиторий перестал принадлежать одному.

Три линии обороны

Пережив аварии по типам, я выстроил оборону в три слоя.

  1. Изоляция worktree по умолчанию. Дай каждой сессии независимый рабочий каталог через git worktree, и HEAD перестаёт быть общей переменной. Угон ветки, осиротевшие коммиты и загрязнение стейджинга исчезают в корне. Три из четырёх типов аварий выше снимаются одним этим ходом. Правда, он не бесплатный. В монорепо для каждого worktree придётся заново ставить зависимости и запускать кодогенерацию, а в репозитории с инструментами, плохо совместимыми с worktree, вроде git-crypt, изоляцию трудно сделать обязательной. У нас один репозиторий из-за этого ограничения так и живёт на общем чекауте, поэтому и нужны две оставшиеся линии.
  2. Перепроверка ветки прямо перед коммитом. В начале работы сессия записывает «я работаю в этой ветке», а перед самым коммитом сверяет запись с текущим HEAD. Не совпало - коммит останавливается, сначала разбираемся в ситуации. Правило до смешного простое, но все аварии с угоном ветки сводились к одному: «HEAD в момент коммита не тот HEAD, который я знаю». Человек заметил бы имя ветки в приглашении терминала, а агент не проверяет, пока не скажешь явно.
  3. Скоуп проверочного шлюза сужается до моих изменений. Область, которую проверяет шлюз завершения сессии, сужается с целого репозитория до файлов, которые эта сессия реально изменила. Исчезает и блокировка моего завершения чужим WIP, и стимул трогать чужой код ради прохождения шлюза. Здоровье репозитория в целом всё равно перепроверит CI по закоммиченному состоянию. Сессионному шлюзу смотреть на всё и не требовалось.

Сверху я добавил одно операционное правило. Перед запуском сессии просматриваю открытые ветки и PR: нет ли пересекающегося скоупа. Двойная реализация - это не та проблема, которую способен остановить git, это проблема диспетчеризации, и закрывать её пришлось не инструментом, а привычкой.

Проблемы конкурентности существуют не только в базах данных

Разложив всё по полочкам, узнаёшь знакомую картину: общий ресурс, конкурентный доступ без локов, гонки и уровни изоляции. Та самая проблема, которую мы десятилетиями изучали на базах данных и многопоточном коде, разыгралась заново поверх рабочего каталога.

С одним агентом этой проблемы не существует. Когда человек и агент работают по очереди, человек неявно играет роль координатора. Проблема начинается в тот момент, когда агентов становится много, а человек выпускает координацию из рук. С этой точки рабочий каталог - общий ресурс, которому нужен контроль конкурентности, и если гонять его без изоляции, данные будут теряться так же неизбежно, как терялись в базах данных.

Это не призыв отказаться от параллельных агентов. Я и сейчас каждый день запускаю по несколько сессий. Изменилось одно: я больше не сгоняю их в одну комнату.

Похожие статьи

Комментариев: 0