Агент-инженер по репозиторию · Модуль 4 · Урок 4.1
Изоляция записи: worktree и ветка, никогда main
Зачем (какую проблему чиним)
Это версия, где агент впервые меняет файлы. С этого момента он способен нанести необратимый ущерб. В июле 2025 ИИ-агент Replit во время объявленного code freeze выполнил деструктивные команды и удалил продакшн-базу, затем исказил отчётность. Урок индустрии однозначен: прежде чем дать агенту писать, нужно физически изолировать пространство записи. Сначала строим клетку — потом впускаем в неё запись.
Решение и альтернативы
Решение: агент работает в отдельном git worktree на отдельной ветке agent/<task-id>, отведённой от свежего main, но физически в другой директории. Инвариант, проверяемый в коде перед любой записью: текущая ветка не main/master и мы внутри выделенного worktree. main для агента недостижим для записи в принципе.
Альтернативы и почему хуже:
- Писать прямо в рабочее дерево на текущей ветке — ровно та ошибка, что приводит к катастрофам: нет границы между экспериментом агента и рабочим состоянием человека.
- Просто
git checkout -bв той же директории — лучше, но агент и человек делят одно рабочее дерево; переключения ветки конфликтуют, легко случайно оказаться наmain. Worktree даёт отдельную директорию на ветку — изоляция физическая. - Форк/клон репозитория целиком — тяжело и медленно для каждой задачи; worktree разделяет объектную базу и дёшев.
DIFF
Добавляем менеджер изоляции: создать worktree на новой ветке, guard-проверку «не main», уборку worktree по завершении. Все будущие инструменты записи будут работать ТОЛЬКО внутри него.
⚠ Безопасность
Главный инвариант курса: агент никогда не пишет в main. Он закодирован, а не оставлен на добросовестность модели: assertWritable падает, если ветка — main/master или мы вне worktree. Worktree отведён от свежего main, изолирован в своей директории и удаляется после задачи. Это физическая граница: даже галлюцинация или инъекция не дотянется до основной ветки.
Проверка
Запуск задачи создаёт ../wt-<task-id> на ветке agent/<task-id>; git -C <wt> branch --show-current подтверждает изоляцию. Попытка инициализировать запись на main падает с явной ошибкой инварианта. После задачи worktree снят, ветка осталась для MR (версия v4).
Глубже
Изоляция исполнения, least privilege, sandbox — курс «Продакшн-разработка», Модуль 5 (безопасность, гл. 11) и Модуль 4 (гл. 8). Human-in-the-loop как защитный слой — Модуль 5 (гл. 10).
# Отвести изолированный worktree от свежего main на новую ветку:
git worktree add -b agent/task-123 ../wt-task-123 main
# Убедиться, что мы НЕ на main:
git -C ../wt-task-123 branch --show-current # agent/task-123
# По завершении задачи — снять worktree (ветка остаётся для MR):
git worktree remove ../wt-task-123+package main
+
+import (
+ "context"
+ "fmt"
+ "path/filepath"
+)
+
+// Workspace — изолированное пространство записи: отдельный worktree на ветке.
+type Workspace struct {
+ Dir string // директория worktree (НЕ основной репозиторий)
+ Branch string // agent/<task-id> — никогда main/master
+}
+
+var protectedBranches = map[string]bool{"main": true, "master": true}
+
+// NewWorkspace создаёт worktree на новой ветке от свежего main.
+func NewWorkspace(ctx context.Context, repoRoot, taskID string) (*Workspace, error) {
+ branch := "agent/" + taskID
+ dir := filepath.Join(filepath.Dir(repoRoot), "wt-"+taskID)
+ if _, err := runGitWrite(ctx, repoRoot, "worktree", "add", "-b", branch, dir, "main"); err != nil {
+ return nil, err
+ }
+ ws := &Workspace{Dir: dir, Branch: branch}
+ return ws, ws.assertWritable(ctx)
+}
+
+// assertWritable — ИНВАРИАНТ: запись разрешена только вне main и в worktree.
+// Любой инструмент записи вызывает это ПЕРЕД изменением файлов.
+func (w *Workspace) assertWritable(ctx context.Context) error {
+ cur, err := runGitWrite(ctx, w.Dir, "branch", "--show-current")
+ if err != nil {
+ return err
+ }
+ if protectedBranches[trim(cur)] {
+ return fmt.Errorf("ОТКАЗ: запись в защищённую ветку %q запрещена", trim(cur))
+ }
+ return nil
+}Anti-patterns
| Грабля | Почему плохо | Как правильно |
|---|---|---|
| Агент пишет прямо в рабочее дерево на текущей ветке | Нет границы между экспериментом агента и состоянием человека/прода — путь к необратимому ущербу | Отдельный worktree на ветке agent/<task-id>; основной репозиторий не трогается |
| Полагаться на системный промпт «не трогай main» | Модель может галлюцинировать или поддаться инъекции; обещание ≠ гарантия | Инвариант в коде (assertWritable): запись физически невозможна на main |
checkout -b в общей директории вместо worktree | Агент и человек делят рабочее дерево; переключения ветки конфликтуют, легко попасть на main | git worktree add — отдельная директория на ветку, физическая изоляция |
| Не удалять worktree после задачи | Накапливаются висячие worktree и ветки; беспорядок и риск перепутать | Снимать worktree по завершении (worktree remove); ветку оставлять для MR |
Практическое задание (RA-v3)
- Реализовать
Workspaceповерхgit worktree add -b agent/<task-id> ../wt-<task-id> main. - Закодировать инвариант
assertWritable(отказ на main/master и вне worktree); вызывать его перед любой записью. - Добавить уборку worktree по завершении. Закоммитить:
git commit -m "v3: isolated worktree, never main".
Проверка знаний
Команда решает, что достаточно написать в системном промпте «никогда не коммить в main», и на этом считать запись безопасной.
Почему этого недостаточно и что надёжнее?
Верный ответ: B
B. Безопасность записи нельзя строить на добросовестности модели — это и приводит к инцидентам уровня Replit. Физическая изоляция (отдельный worktree) плюс инвариант в коде делают запись в main невозможной независимо от того, что «решит» модель. Бэкапы (D) — про восстановление, а не про предотвращение.
Чем git worktree предпочтительнее, чем git checkout -b в той же рабочей директории, для изоляции агента?
Верный ответ: B
B. checkout -b переключает единственное рабочее дерево — агент и человек мешают друг другу, и легко очутиться на main. Worktree выделяет отдельную директорию на ветку (физическая изоляция), разделяя при этом объектную базу — поэтому он дёшев в сравнении с полным клоном.