# Агенты планирования и исполнения для более безопасных изменений в production

Агент-планировщик не должен иметь возможности превратить собственную рекомендацию в изменение в production. Дайте ему возможность изучать, сравнивать и обосновывать варианты. Отдельному агенту-исполнителю выдайте небольшой набор прав на действия, а затем потребуйте доказать, что запрошенная операция соответствует одобренному изменению.

Такое разделение кажется бюрократией, пока вы не увидите, как агент переносит правдоподобное, но ошибочное предположение через границу API. Большинство сбоев не похоже на карикатурные атаки. Агент читает устаревшую инструкцию, принимает staging-хост за production или следует недоверенному тексту в задаче. Если один и тот же процесс хранит учётные данные и обладает правом действовать, ошибка превращается в изменение раньше, чем кто-то успевает её заметить.

## План - это свидетельство, а не разрешение

Агентам планирования и исполнения нужны разные полномочия, потому что план описывает утверждение о мире, а исполнение меняет сам мир. Даже хороший план может опираться на устаревшие данные, неполный контекст репозитория или инструкции из ненадёжного источника. Если считать план разрешением, вы объединяете два решения, которые заслуживают отдельной проверки.

Планировщик должен собирать факты, объяснять неопределённость, предлагать варианты и формировать ограниченный запрос. Ему не нужен production-токен только потому, что в отчёте нужно упомянуть endpoint. Если ему требуются факты из защищённой системы, предоставьте специально созданную операцию чтения, которая возвращает только нужные сведения, или попросите человека передать соответствующий результат.

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

Это различие также исправляет привычку называть «человеком в контуре» ситуацию, когда человек просто просмотрел длинную переписку. По прозе нельзя надёжно восстановить все вызовы инструментов, которые мог сделать агент. Зато можно проверить короткий структурированный запрос с целью, операцией, входными данными, ожидаемым эффектом и способом отката.

Контроль AC-5 в NIST SP 800-53 требует разделять обязанности, чтобы уменьшить вероятность того, что один человек сможет незаметно злоупотребить системой. Формулировка относится к людям, но логика так же хорошо применима к агентам. Не переносите старую иерархию согласований в prompt. Разделяйте возможности в реальных учётных данных и интерфейсах инструментов.

## Исполнитель должен быть ограничен сильнее, чем план

Исполнителю нужно дать меньше свободы, чем планировщику, а не просто другой prompt. Отдельный агент, который может запускать произвольные команды shell с широкими облачными полномочиями, не снижает риск по-настоящему. Он всё ещё может переосмыслить расплывчатый запрос, найти другие ресурсы и внести несвязанные изменения.

Начните с каталога действий. Каждое действие должно описывать одну операцию и принимать небольшой набор параметров. Например, `deploy_service_revision` может принимать имя сервиса, неизменяемый идентификатор ревизии и целевую среду. Оно не должно принимать фрагмент shell-команды или произвольный URL.

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

Не путайте ограниченный инструмент с ограниченным результатом. API-токен, который может обновить `billing-api`, возможно, также меняет распределение трафика, переменные окружения и настройки автомасштабирования. Если API позволяет, разделите эти операции. Если нет, поставьте перед ним небольшой шлюз, принимающий только ту операцию, которую вы готовы автоматизировать.

У надёжного исполнителя обычно есть такие границы:

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

Команды часто сопротивляются этому, потому что универсальные инструменты быстрее подключить. Для первой демонстрации это действительно быстрее. Но инструмент `run_command` становится дорогим в тот момент, когда нужно объяснить, почему агент удалил не тот ресурс, прочитав скопированную команду в задаче.

## Для передачи нужен запрос на изменение, проверяемый машиной

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

Практический запрос может выглядеть так:

```json
{
  "request_id": "chg-2025-0417-redis-timeout",
  "environment": "production",
  "action": "deploy_service_revision",
  "target": {
    "service": "checkout-api",
    "revision": "sha256:8f31c2..."
  },
  "expected_effect": "Run the approved checkout-api revision",
  "rollback": {
    "action": "deploy_service_revision",
    "revision": "sha256:31aa09..."
  },
  "approval": {
    "approved_by": "release-manager",
    "approved_request_hash": "b2c4..."
  }
}
```

Хеш важен. Без него рецензент может одобрить увиденный запрос, а исполнитель получит изменённую версию. Запись согласования должна быть привязана к каноническому представлению точных полей, которыми воспользуется исполнитель. Если разные части системы по-разному сериализуют JSON, сначала определите каноникализацию. Простой хеш строки становится ловушкой, когда порядок полей или пропущенные значения по умолчанию меняют содержимое.

Исполнитель должен отклонять запрос с понятной причиной. Полезный формат ответа показывает, какая проверка не пройдена, но не раскрывает секрет:

```json
{
  "status": "denied",
  "request_id": "chg-2025-0417-redis-timeout",
  "reason": "revision must be an immutable digest",
  "executed": false
}
```

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

Не позволяйте планировщику выбирать определение действия исполнителя. Владелец платформы должен определить каталог, правила проверки и сопоставление с учётными данными. Планировщик выбирает из поддерживаемых действий. Он не может придумать `deploy_anything`, потому что конкретная задача кажется срочной.

## Раздельные процессы не дают случайно поделиться полномочиями

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

Процесс планировщика должен получать инструменты исследования и, возможно, узкие интерфейсы для отфильтрованного чтения. Он не должен видеть учётные данные исполнителя в окружении, конфигурационных файлах или описаниях инструментов. Модели не нужен доступ к токену в открытом виде, чтобы злоупотребить им. Если процесс может вызвать инструмент, владеющий токеном, именно граница инструмента является границей полномочий.

Исполнитель должен получать одобренный структурированный запрос и минимально необходимый набор инструментов. Ему не следует передавать исходный текст задачи, произвольное содержимое веб-страниц, инструмент поиска по всему репозиторию или переписку планировщика. В этих материалах могут быть prompt injection, опасные команды или случайные инструкции, которым исполнителю незачем следовать.

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

Разделение процессов помогает и при расследовании инцидентов. Можно отозвать сессию исполнителя, не потеряв исследовательскую запись планировщика. Можно проверить, предложил ли планировщик другую цель, чем та, которую одобрили. Если обе роли используют одну сессию и один идентификатор, восстановление событий превращается в догадки.

Sallyport поддерживает такую границу, когда агент с поддержкой MCP должен выполнять HTTP- или SSH-действия, не получая исходные API- или SSH-учётные данные. Приложение хранит секреты в зашифрованном хранилище и само выполняет вызов, поэтому планировщик не может извлечь учётные данные даже при ошибочных инструкциях.

## Согласование должно происходить там, где меняются последствия

Согласование человеком должно охватывать конкретное решение, а не давать расплывчатое разрешение на всё, что агент сделает дальше. Согласование сессии полезно, когда известному процессу агента разрешают использовать ограниченный набор действий с небольшим риском в рамках одного запуска. Это плохая замена проверке, если действие может изменить production-данные, идентификацию, сетевую доступность или деньги.

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

В согласовании должны отображаться сведения, которые рецензент действительно может оценить: имя действия, целевая среда, ресурс, неизменяемый входной параметр и запланированный откат. Диалог «Агент запрашивает доступ» является имитацией проверки. Он ничего не говорит о последствиях.

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

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

## Prompt injection сначала достигает планировщиков, а не исполнителей

Prompt injection часто появляется в обычной работе планировщика. В комментарии к репозиторию написано запустить команду. В задаче поддержки содержится поддельная инструкция извлечь конфигурацию. Веб-страница просит игнорировать предыдущие указания. Планировщики видят больше недоверенного текста, чем исполнители вообще должны видеть.

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

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

При разделённой архитектуре сбой останавливается в нескольких местах. В запросе на передачу production должна быть указана явно. Исполнитель принимает только действие развёртывания ревизии, а не изменение числа реплик. Человек видит настоящую цель и ожидаемый эффект. В журнале исполнителя остаётся запись об отказе, если запрос не подходит. Ни один из этих механизмов не требует, чтобы планировщик идеально распознавал отравленный или устаревший текст.

Считайте результат планировщика недоверенным входом для пути исполнения. Это должно влиять на обработку данных, а не оставаться предупреждением в интерфейсе. Не вставляйте текст планировщика в shell-команды. Не позволяйте ему заполнять пути HTTP, заголовки или параметры запроса без проверки типов и списков разрешений. JSON-схема полезна, но сама по себе не показывает, разрешена ли `production` в качестве цели.

## Доступ для чтения тоже может открыть путь к ущербу

Команды часто дают планировщикам широкий доступ для чтения, потому что «он ничего не может изменить». Эта фраза уже привела к множеству проблем, которых можно было избежать. Чтение может раскрыть данные клиентов, внутренние имена хостов, историю развёртываний, feature flags, шаблоны доступа и названия привилегированных систем. Оно также может предоставить ровно те сведения, которые нужны злоумышленнику для убедительного запроса на действие.

Классифицируйте операции чтения по чувствительности и по тому, что они позволяют сделать. Endpoint, возвращающий состояние сервиса, отличается от endpoint экспорта базы данных. Инвентаризация сервисов с публичными именами отличается от API менеджера учётных данных, который возвращает идентификаторы и метаданные секретов. Не помещайте всё это за один общий инструмент `read_only`.

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

Здесь команды часто перегибают и делают планирование бесполезным. Ответ не в том, чтобы сделать агента слепым. Определите, какие факты нужны задаче, и создайте интерфейс чтения именно для них. Если планировщику регулярно требуется дополнительное поле, добавьте его осознанно после проверки сценария. Не закрывайте каждый пробел доступом к production-консоли.

## Журналы должны позволять восстановить разногласие

Аудит должен отвечать не только на вопрос «произошёл ли вызов API». После спорного изменения нужно сравнить предложенный планировщиком артефакт, артефакт, одобренный человеком, проверенный исполнителем запрос, точный внешний вызов и ответ. Если чего-то одного не хватает, вместо доказательств остаётся история со слов.

Сохраняйте стабильный идентификатор запроса на всём пути. Планировщик назначает его или получает извне. Согласование связывается с ним и его хешем содержимого. Исполнитель записывает его вместе с идентификатором процесса и результатом действия. Внешний шлюз сохраняет его рядом с исходящим адресатом и статусом ответа. Не записывайте bearer-токены, пароли, закрытые ключи или полные чувствительные данные только ради удобной корреляции.

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

Sallyport строит журналы Sessions и Activity из зашифрованного аудита с цепочкой хешей, а `sp audit verify` проверяет цепочку офлайн по шифротексту без ключа хранилища. Это полезно при расследовании, потому что проверяющему не нужно выдавать права на выполнение действий.

Проведите тренировочное восстановление событий до того, как инцидент заставит вас делать это в спешке. Выберите одобренное изменение и попросите коллегу ответить по записям на пять вопросов: какой агент его предложил, кто одобрил точный запрос, какой исполнитель его запустил, какая внешняя операция произошла и какой результат вернулся. Если любой ответ зависит от памяти или переписки, улучшите записи.

## Первое автоматизированное действие должно быть скучным и обратимым

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

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

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

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