Модуль 3 · Датчики и ограничения · урок 14 из 24
Изолированные окружения на каждую задачу
Git worktree решает только половину задачи параллельной работы: у каждого агента свои файлы. Но если все агенты стучатся в один и тот же контейнер на порту 8001, одну базу и один кэш, их проверки смешиваются, и зелёный прогон перестаёт что-либо значить. Этот урок — о полном окружении на задачу: что в него входит, как его поднять за минуту и как не забыть его убрать.
Что говорят источники
Все источники трека сходятся в одном: параллельную работу надо изолировать.
- В эксперименте OpenAI (Harness engineering) приложение поднимается на каждый git worktree, и к каждому привязан свой эфемерный стек наблюдаемости, который удаляется после задачи.
- В спецификации Symphony у каждой задачи из трекера свой workspace, и агент обязан работать только внутри него — это первый инвариант безопасности спецификации.
- Anthropic в схеме для длинных задач даёт агенту
init.sh, который поднимает dev-сервер, чтобы тот не тратил время на выяснение, как запустить приложение. - В лучших практиках Codex параллельные задачи на одних и тех же файлах без worktree названы антипаттерном.
Наш вывод. Изоляция — это не только про безопасность, но и про достоверность сигнала. Тест, который прошёл на чужой базе, ничего не доказывает.
Что именно изолировать
| Ресурс | Что будет без изоляции | Как развести |
|---|---|---|
| Файлы | правки агентов затирают друг друга | git worktree на задачу |
| Порты | второй стенд не стартует или агент смотрит на чужой | порт из хэша пути worktree |
| База данных | миграции одной ветки ломают другую | своя БД из фикстур на стенд |
| Кэши, очереди, сессии | «призрачные» баги из чужого состояния | отдельный префикс или свой Redis |
| Сборка фронта | артефакты одной ветки в другой | public/build внутри worktree |
| Браузерный профиль | куки и localStorage переносятся | новый контекст на каждый прогон |
| Секреты | лишние права у агента | только тестовые ключи, минимум |
| Логи и метрики | наблюдения смешиваются | файл логов внутри стенда |
Знакомая ситуация в агентстве: основной стенд проекта смонтирован на основную рабочую копию, и правки в worktree он просто не видит — агент проверяет старый код и честно докладывает, что всё работает. Лечится только своим стендом на worktree.
Что даёт Codex
Managed worktrees в ChatGPT desktop app. Codex создаёт worktree в $CODEX_HOME/worktrees от HEAD выбранной ветки в состоянии detached HEAD, поэтому может сделать несколько штук, не плодя веток. Игнорируемые git файлы, нужные для запуска (.env, локальные конфиги), перечисляются в .worktreeinclude в корне репозитория; игнорируемый AGENTS.override.md копируется автоматически. По умолчанию хранятся 15 последних worktree, перед удалением делается снимок. Подробности для повседневной работы — в уроке о worktree.
Local environments. Там же можно задать setup-скрипт, который выполняется при создании нового worktree (установка зависимостей, сборка), и действия — частые команды вроде запуска dev-сервера. Настройки хранятся в папке .codex проекта, её можно закоммитить.
CLI. Флаг --worktree и команда /worktree начинают или продолжают работу в managed worktree; у codex exec fork тоже есть --worktree. Актуальный набор флагов проверьте в codex --help.
Облако. В Codex Cloud окружение описывается install-скриптом и start-навыком, публикуется, и каждая задача получает изолированную копию подготовленной файловой системы. Секреты можно передать как network secrets: процесс видит заглушку, а реальное значение подставляет прокси только для разрешённых доменов. Это урок об облачных окружениях.
Чего Codex за вас не сделает: не поднимет вашу базу, не разведёт порты и не уберёт контейнеры. Это ваша часть обвязки.
Стенд на worktree за один скрипт
Минимальная схема для проекта на docker compose: имя проекта и порт вычисляются из пути worktree, поэтому каждый стенд получает свои контейнеры, тома и порт, а повторный запуск попадает в тот же стенд.
services:
app:
build: .
ports:
- "${APP_PORT:-8001}:80"
environment:
DB_HOST: db
DB_DATABASE: site
DB_USERNAME: root
DB_PASSWORD: local-only
volumes:
- ./src:/var/www/html
depends_on:
db:
condition: service_healthy
db:
image: mysql:8.4
environment:
MYSQL_DATABASE: site
MYSQL_ROOT_PASSWORD: local-only
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-plocal-only"]
interval: 3s
retries: 30
volumes:
- db:/var/lib/mysql
volumes:
db:#!/usr/bin/env bash
# Поднимает стенд для текущего worktree и печатает его адрес.
set -euo pipefail
root=$(git rev-parse --show-toplevel)
hash=$(printf '%s' "$root" | cksum | cut -d' ' -f1)
export COMPOSE_PROJECT_NAME="site-$(basename "$root" | tr -c 'a-z0-9\n' '-')"
export APP_PORT=$((8100 + hash % 800))
dc=(docker compose -f "$root/docker-compose.yml")
"${dc[@]}" up -d --build --wait
"${dc[@]}" exec -T app php artisan october:migrate
"${dc[@]}" exec -T app php artisan db:seed --class=AgentFixtureSeeder
echo "{\"project\": \"$COMPOSE_PROJECT_NAME\", \"url\": \"http://localhost:$APP_PORT\"}"#!/usr/bin/env bash
set -euo pipefail
root=$(git rev-parse --show-toplevel)
export COMPOSE_PROJECT_NAME="site-$(basename "$root" | tr -c 'a-z0-9\n' '-')"
docker compose -f "$root/docker-compose.yml" down --volumes --remove-orphansИмя сидера и команды миграции подставьте свои. Healthcheck у базы нужен, чтобы up --wait дождался её готовности: без него миграции на первом запуске падают. Обратите внимание на последнюю строку up.sh: скрипт печатает адрес в машиночитаемом виде, и агент сразу знает, куда идти браузером. Строка в AGENTS.md замыкает петлю: «перед проверкой в браузере запусти scripts/env/up.sh и используй адрес из его вывода; по окончании — scripts/env/down.sh».
Коллизии портов при таком хэше возможны; при десятке одновременных стендов проще хранить выданные порты в файле или спрашивать у системы свободный порт.
База: фикстуры, а не дамп
Соблазн поднять стенд из свежего дампа прода понятен — «там же реальные данные». Против — три довода: персональные данные клиентов в контексте агента, размер дампа и время развёртывания, недетерминированность проверок. Лучше набор фикстур, который покрывает типовые сущности и известные пограничные случаи, плюс сидер, который агент может расширять при работе над задачей. Это заодно и тестовые данные для e2e.
Жизненный цикл
У окружения четыре фазы, и Symphony задаёт для них хуки: after_create, before_run, after_run, before_remove, по умолчанию с таймаутом 60 секунд. Даже если вы не используете Symphony, полезно думать этими же фазами:
- Создание — worktree,
.worktreeinclude, установка зависимостей. - Перед запуском агента — стенд поднят, миграции и фикстуры применены, адрес известен.
- После прогона — собраны артефакты: скриншоты, отчёт тестов, логи.
- Перед удалением — контейнеры и тома остановлены и удалены.
Четвёртую фазу забывают чаще всего. Через неделю параллельной работы на машине разработчика оказываются десятки забытых стендов с базами.
Модель доверия
Спецификация Symphony не навязывает политику песочницы и одобрений, но требует, чтобы каждая реализация явно описала свою модель доверия. Это правильная планка и для своих схем. Ответьте письменно: какие команды агент выполняет без вопросов, есть ли у него сеть, какие секреты ему доступны, что он может сломать за пределами worktree.
Одна практическая тонкость: вложенная песочница Codex внутри Docker может не работать. Для такого случая документация советует изолировать агента контейнером (например, Dev Container) и внутри запускать с --sandbox danger-full-access — граница безопасности тогда проходит по контейнеру, и проектировать её нужно соответственно.
Мобильная разработка
Для мобайла роль стенда играет симулятор или эмулятор. Несколько агентов на одном симуляторе мешают друг другу так же, как на одной базе. Варианты: отдельное устройство на агента (xcrun simctl clone для iOS, отдельный AVD или запуск эмулятора с -read-only для Android), мок-сервер API на своём порту из того же хэша, сборка в собственный каталог derived data внутри worktree.
Попробуйте сами
10–15 мин на рабочем месте- Посчитайте по таблице из урока, какие ресурсы в вашем проекте общие для всех рабочих копий. Выберите самый опасный.
- Напишите
up.shиdown.shдля своего проекта по образцу выше, поднимите два стенда из двух worktree одновременно и убедитесь, что они не видят данных друг друга. - Создайте
.worktreeincludeс файлами, без которых проект не стартует, и добавьте в AGENTS.md две строки о том, как агенту поднимать и убирать стенд.
Коротко
- Worktree изолирует файлы; порты, база, кэши и логи требуют своей обвязки.
- Тест на общем окружении не доказывает ничего о вашей ветке.
- Codex даёт managed worktrees,
.worktreeinclude, setup-скрипты и облачные окружения; стенд и его уборку делаете вы. - База стенда — из фикстур, а не из продового дампа.
- Опишите модель доверия письменно и не забывайте фазу удаления.