Статья · Разработка
ai-worker, часть 2: архитектура и границы выполнения
Устройство ai-worker: общий планировщик, выбор провайдеров по порядку, защищённая веб-панель, изолированные гости QEMU, подтверждения для точных коммитов и очистка после доставки.
Читать на другом языке: ENai-worker — локальный контроллер на Python, связывающий задачи Forgejo с агентами для работы над кодом. Хост хранит решения о ходе работы и управляет публикацией. Гостевые машины QEMU запускают инструменты репозитория и штатные CLI провайдеров, возвращая код и структурированные подтверждения. Необязательный локальный сервер модели использует GPU хоста, а операции с инструментами по-прежнему выполняются в гостевой среде.
Первая часть описывает процесс и показывает экран
Overview. Здесь рассматривается
реализация в коммите 824c821, актуальная на 10 ноября 2026 года. Новые
последовательные планы, прежние параллельные функции и напрямую назначенные
задачи сохраняют разные правила авторизации и доставки.
Компоненты и полномочия
Контроллер обнаруживает работу, проверяет актуальные разрешения, закрепляет
задачу за слотом, выбирает провайдера, запускает фазу и публикует принятый результат.
Адаптер Forgejo выполняет аутентифицированные операции через tea. Отдельные
учётные записи планировщика, исполнителя и ревьюера без прав администратора
позволяют различать планирование и координацию слияний, реализацию и формальное
ревью.
SQLite хранит задачи, попытки, наблюдения о провайдерах и закрепления за слотами. Bare-кеш Git содержит объекты и отдельные ссылки слотов. Модули выполнения перемещают Git-бандлы, подготавливают гостей и фиксируют завершение. Другие модули отвечают за контракты подтверждений, исправления по ревью, изображения, метрики, доступ к репозиториям и очистку.
Веб-служба читает те же записи и вызывает ограниченный набор действий планировщика или systemd. Отдельного движка процессов она не добавляет. Опрос, расписание, запуск CI, публикация, слияния и очистка выполняются обычным Python-кодом. Модель используется только для планирования, реализации, ревью и предусмотренных процессом задач валидации.
Общий планировщик распределяет свободные слоты
Один пользовательский планировщик systemd поддерживает общую трёхминутную задержку между проверками работы. Службы слотов выполняют задания; отдельные десятиминутные таймеры слотов больше не управляют расписанием. Локальная блокировка обнаружения сериализует выбор и закрепление задачи и освобождается до выполнения выбранной фазы модели.
На локальных тактах планировщик перечитывает конфигурацию, проверяет доступность служб и слотов, записывает сведения о проверках и сроках следующего запуска. Start now запрашивает немедленную проверку через тот же механизм. Завершившееся задание, которое продвинуло работу, может запросить следующую проверку без обычной задержки.
Параметр shares разрешает один, два или три слота. Новые планы продвигают
по одному этапу родительской задачи, а разные родительские задачи могут занимать
несколько слотов реализации. Параллельно возможны планирование и ревью;
исправления уже проверенных PR имеют отдельное исключение внутри одной
родительской задачи. Ожидающий ревью или CI пул-реквест не должен бесконечно
занимать гостевую машину реализации.
WAL-режим SQLite, атомарные закрепления задач и условное обновление записей не дают устаревшей проверке перезаписать прогресс другого слота. Блокировки ядра показывают, какие закрепления живы, и освобождаются после смерти процесса. Блокировка обслуживания исключает подготовку инфраструктуры во время заданий; блокировки кеша Git и функции защищают короткие изменения. Это локальная координация одной установки, а не распределённые аренды ресурсов.
Провайдеры выбираются в заданном порядке
Список providers задаёт включение и приоритет провайдеров. Начальный порядок —
codex, claude, llama. Локальному варианту нужен настроенный сервер;
необязательным провайдерам — явное включение и собственные проверки допуска.
Для каждой фазы задаются модель и уровень рассуждений каждого провайдера, а также переключатель On/Off. Отключённые сочетания пропускаются до проверки квоты. Контроллер идёт по списку и берёт первого допустимого провайдера для фазы. Текущая политика не ранжирует их по запасу квоты, взвешенному временем сброса.
Если квоты измеряются окнами, каждое применимое окно после поправки на активные
задания должно оставаться строго выше резерва. По умолчанию каждое другое
задание того же провайдера уменьшает доступный остаток на пять процентных
пунктов. reserve_percent по умолчанию равен десяти; weekly_reserve_percent
отдельно защищает недельные ограничения, в том числе ограничения моделей.
Если недельный параметр не задан, он наследует обычный резерв. Равенство
порогов откладывает запуск.
Время сброса используется для проверки актуальности, кеширования и отображения квоты. Некорректные или заблокированные наблюдения делают провайдера недоступным; после ошибок действует задержка. Пустой список приостанавливает обращения к моделям, но сверка состояния продолжается. Изменения настроек действуют на последующие задания.
Смысл допуска различается у провайдеров
Общий контракт адаптера не делает все сигналы внешних систем равнозначными:
| Провайдер | Сигнал допуска и ограничение |
|---|---|
| Codex | Метаданные лимитов штатного CLI; отсрочка при доступных платных кредитах |
| Claude | Окна использования из OAuth API для чтения; отсрочка при включённых платных превышениях |
| Kimi Code | Проверка выбранной учётной записи и модели; API квот подписки для чтения нет |
| DeepSeek Harness | Явно включённый платный API-ключ и доступный баланс |
| OpenCode Go | Окна использования, проверенное ограничение расходов и привязанные результаты живой приёмки |
| Локальный llama.cpp | Настроенный сервер, допустимая простая работа, проверка ресурсов и ограниченная локальная попытка |
Метаданные Codex читаются без создания диалога или запроса к модели. Внутренний endpoint использования Claude изолирован адаптером, поскольку не является стабильным публичным контрактом. Условный запас Kimi означает успешную проверку входа; исчерпание квоты определяется по ошибкам лимитов CLI во время работы. Условные 100% DeepSeek означают наличие средств, а не нетронутую квоту подписки. Явное включение DeepSeek разрешает расходовать баланс API.
OpenCode Go включается отдельно. Ему нужны принадлежащая воркеру учётная запись Console, применимый бюджет All-spend в ноль долларов и результаты живой приёмки, привязанные к аккаунту, рабочему пространству, CLI, слою гостя, адаптеру и схеме результата. Непосредственно перед новой фазой адаптер перепроверяет бюджет и окна использования. Отсутствующее, нечитаемое или неподходящее ограничение делает провайдера недоступным. Это проверки допуска воркера, а не общие гарантии относительно биллинга провайдера.
Ошибки квоты сохраняют работу и возвращают потраченную попытку в лимит попыток. Следующая проверка может выбрать другого настроенного провайдера. Проценты квот и сигналы баланса помогают планировать работу, но не задают сопоставимую ёмкость в токенах.
Свежие сессии продолжают работу по явным артефактам
Каждая удалённая фаза начинает новую сессию CLI с ограниченным наследованием конфигурации и интеграций. Продолжение между провайдерами обеспечивают согласованная область работ, исходный код, прежние замечания, подтверждения проверок и сохранённая работа Git. Истории диалогов между провайдерами не переводятся.
Планирование, ревью и реализация имеют отдельные настройки. Задача валидации
использует настройки реализации. Для этапов с отметкой simple и подходящих
небольших исправлений по ревью simple_implementation может переопределять
часть настроек реализации. Записанная конфигурация задания сохраняется после
последующих изменений.
Маршрут llama.cpp допускает одну локальную попытку для снимка подходящей простой реализации или ограниченного исправления, с лимитом diff и переходом на другой вариант при неудаче. Планирование, ревью, валидация и исправление конфликтов его не используют. Отдельный ограниченный канал соединяет гостя с сервером модели на GPU хоста — это явное исключение из обычного выхода только к публичным сервисам.
Через границу выполнения проходят файлы и объекты Git
Хост фиксирует head и base в кеше Git; полученный head ревью должен совпасть с Forgejo. Бандлы, инструкции, контекст, настройки и схема результата передаются гостю без учётных данных сервера репозитория.
Изображения из задач пользователей с правом записи загружает хост с ограничением объёма и проверкой сигнатур. Метаданные допустимых файлов входят в отпечаток требований; отклонённые или недоступные вложения перечисляются в контексте. Гость получает файлы, а не учётные данные Forgejo.
Реализация работает в своей управляемой ветке; планирование и ревью начинают с переданного detached head. Нужная подготовка зависимостей выполняется обычным кодом до передачи секретов провайдера. Сохранённую работу можно обновить относительно зафиксированного base обычными коммитами слияния и хуками репозитория ещё до запуска модели.
Результаты, диагностика и бандлы реализации возвращаются на хост. Перед обычным push кеш импортирует результат и проверяет ожидаемую историю предков. После публикации проверяется head удалённой ветки, в том числе при потере ответа сервера. Перед публикацией или защищённым слиянием снова проверяются актуальные полномочия, область работ и снимки.
Валидация — структурированный контракт выполнения
Результат содержит состояние завершения, блокировки, вопросы, замечания, подтверждения критериев, попытки проверок и признаки изменения области работ или исчерпания квоты. Планирование добавляет описания ограниченных этапов. Хост проверяет схему и применяет правила приёмки, а не доверяет абзацу об успехе.
Требования к проверкам задают точные команды, стабильные идентификаторы, обязательные снимки, названия и область измерений, а также отдельные требования к наличию подтверждений и успешному исходу. Записи привязывают измерения базового или итогового состояния к полным хешам коммитов. Отсутствующие, устаревшие или несовпадающие подтверждения не разрешают публикацию. Даже рекомендательный порог требует полных измерений без ошибок тестовых утверждений или среды, если свидетельство выполнения обязательно.
Обычное независимое ревью исследует код и не запускает валидацию. Финальные PR
новых последовательных планов опираются на принятые подтверждения этапов
и применимый итоговый CI. Для прежних параллельных функций сохраняются
раздельные записи финального ревью и общей валидации. Workflow диспетчеризации
репозитория воркера публикует промежуточный и конечный статусы
CI / test (dispatch) с временным токеном задания, включая ошибки подготовки
среды. Учётные данные провайдеров в CI не используются.
Изоляция гостей включает жизненный цикл секретов
QEMU/KVM запускает гостя Ubuntu с дисковым слоем qcow2 поверх подготовленного образа. У гостя собственный Docker и контроллеры ресурсов. Каталоги исходного кода и Docker-сокет хоста не монтируются. Слои кеша браузера и зависимостей можно добавлять, не переписывая базовые образы сохранённых заданий.
Ограниченная сеть QEMU направляет обычный исходящий трафик гостя через контролируемый хостом HTTP-прокси. Он разрешает публичные адреса из списка на портах 80 и 443, проверяет результаты DNS, отклоняет частные адреса и туннелирует TLS без перехвата. Канал локальной модели ограничен отдельно. SSH гостя использует порт только на loopback и закреплённый ключ хоста SSH.
Учётные данные Forgejo остаются на хосте. Секрет выбранного провайдера передаётся во временный домашний каталог в tmpfs после подготовки и удаляется при завершении. Изоляция учитывает особенности провайдеров: выбранный комплект учётных данных Kimi и токен доступа OpenCode без секрета обновления. Обновлённые штатным CLI данные принимаются обратно только после проверки идентичности и неизменности исходных данных на хосте.
DeepSeek Harness запускается из закрытого каталога с явно заданным рабочим пространством репозитория. Файлы окружения репозитория не загружаются в процесс запуска Harness. Предварительная штатная проверка без обращения к модели проверяет службы разрешений; её неудача откладывает работу без расходования попытки.
Выбранный секрет доступен во время выполнения, поэтому установка предназначена для доверенных репозиториев проекта. Изоляция ограничивает доступ к инфраструктуре, но не делает безопасным запуск враждебного кода с учётными данными провайдера.
Веб-API предоставляет ограниченный набор операций
Встроенный веб-интерфейс использует Python-службу, слушающую только loopback, с версионированными endpoint для сессии, статуса, задач, журналов, настроек, возможностей и действий. Доступ из частной сети можно организовать через HTTPS-прокси с аутентификацией; это не публичная панель без контроля доступа.
Изменяющие запросы проверяют Host, разрешённый Origin при его наличии и CSRF-токен по схеме double-submit. Сохранение настроек проверяет типы, границы значений и версию, использует общую блокировку и атомарную замену закрытого файла. Элементы управления провайдерами строятся по метаданным возможностей. Неподдерживаемые поля установки и учётных записей доступны только для чтения.
Запуск, пауза, возобновление и подтверждённая остановка слота используют планировщик и systemd. Подготовка инфраструктуры, управление секретами, обновления и валидация гостей остаются вне веб-панели. Обновление статуса и задач не обращается к провайдерам или Forgejo. Сохранённые квоты и выборки ресурсов — наблюдения; устаревшие данные не разрешают запуск задания.
Операции доступа к репозиториям используют отдельную неблокирующую блокировку и перепроверяют версию настроек перед удалённым чтением. Check access читает права; Repair access выдаёт недостающий неадминистративный Write и проверяет каждую учётную запись бота. При сохранении новых репозиториев это выполняется только для добавленных записей. Ошибки не отменяют сохранённые настройки и возвращаются как очищенные от секретов предупреждения. Чтение Settings показывает кеш, привязанный к версии, либо Unknown, без удалённых обращений. Удаление репозитория не отзывает доступ; создание аккаунтов и смена ролей остаются операциями администратора.
Восстановление завершается очисткой после доставки
Пока работа не закончена, закрытые файлы заданий сохраняют инструкции, результаты, журналы, бандлы и дисковые слои. Записи о завершении привязаны к фазе, head, base и области работ и позволяют закончить публикацию без нового обращения к модели. При неизменных входных данных работа продолжается в госте; при изменении base прежний результат сохраняется, а экспортированные коммиты можно перенести вперёд.
Ограниченные циклы реализации и исправлений по ревью сохраняют лимиты между проверками и перезапусками. Для исправления только подтверждений можно оставить тот же коммит, но спорные замечания требуют свежего независимого ревью. Маркеры владения и сохранённое намерение создания помогают проверить неоднозначный удалённый запрос до повторного создания объекта.
После подтверждённой доставки возобновляемая очистка удаляет гостей, бандлы, журналы, скриншоты и старые задания функции. SQLite хранит результаты, сведения о слиянии и перечень очищенного; удалённые обсуждения и загруженные подтверждения остаются доступны. Общие кеши и образы, активные закрепления, чужие задания, незавершённая публикация и восстановление защищены от удаления. Ошибки очистки не запускают повторную реализацию уже доставленной функции.
О поведении задач, согласований, ревью и доставки читайте в первой части: от задачи до проверенного слияния.
