Lifecycle изолированных окружений AI-агентов: архитектура оркестрового фреймворка | AiManual
AiManual Logo Ai / Manual.
25 Июн 2026 Гайд

Управление изолированными окружениями агентов: архитектура lifecycle в оркестровом фреймворке

Глубокий разбор архитектуры lifecycle для workspace и runtime агентов. Как построить изоляцию, управлять конфигами и избежать катастроф в мультиагентных система

Реклама
partv2

Почему ваш агент — бомба замедленного действия

Сначала всё мило: вы написали агента, он парсит CSV, делает запросы к LLM и возвращает красивый JSON. Потом другой агент — идёт в продакшен, читает переменные окружения, пишет в ту же папку. Третий агент запускает shell-команды. И вот вы уже не знаете, какой процесс испортил базу данных. Знакомая картина? 90% команд, которые хвастаются "мультиагенткой", на самом деле запускают монолитную кучу с разделением ролей через промпты. И только когда система падает из-за конфликта зависимостей или утечки памяти, приходит осознание: изоляция окружений — не опция, а фундамент.

В этой статье я расскажу, как мы строили архитектуру lifecycle изолированных окружений для оркестрового фреймворка, который держит сотни агентов в production. Без тупого «заверни всё в Docker». С реальными компромиссами, граблями и неочевидными решениями.

Предупреждение: если вы считаете, что изоляция — это просто «разные процессы», эта статья вас отрезвит. Контекст: я пишу на основе опыта, когда агент случайно удалил /tmp на продакшене. Теперь у нас детерминированный kill-switch (подробно описан в статье про kill-switch).

Три слоя изоляции: workspace, runtime, конфиг

Когда мы проектировали наш фреймворк (назовём его OrchAgent, версия 3.2 на июнь 2026), то отталкивались от простой идеи: окружение агента должно быть воспроизводимо уничтожено и создано заново без побочных эффектов. Для этого выделили три сущности:

Компонент Описание Пример
Workspace Изолированная файловая система агента: код, данные, артефакты. /var/orch/agents/{agent_id}/workspace
Runtime Процесс или контейнер, в котором исполняется агент. Docker-контейнер с Python 3.12 + локальные пакеты
Конфиг Набор параметров: переменные среды, лимиты, политики доступа. YAML-файл с env, timeouts, allowed APIs

Workspace — его часто путают с volume в Docker. Но у нас workspace — логическая единица, которая может быть как каталогом на хосте, так и удалённым mount (NFS, S3 через FUSE). Главное — он привязан к жизненному циклу агента. Когда агент уничтожается, workspace может быть либо заархивирован, либо очищен. На старте мы всегда создаём чистый workspace из шаблона. Это как git clone для окружения.

Runtime — здесь классическая дилемма: sandbox vs no-sandbox. Мы перепробовали оба подхода. Вписывание всех runtime в один процесс — путь к катастрофе (один агент сломал всю очередь). Сейчас юзаем изоляцию на уровне контейнеров с ограничениями cgroups v2. Но не через Docker daemon — слишком медленно для сотен агентов. Используем runsc (gVisor) с собственным runtime-менеджером. Этот опыт мы уже описывали в статье про песочницы для deepagents.

Lifecycle: не просто start/stop

Обычный цикл: INIT -> READY -> RUNNING -> STOPPED -> DESTROYED. Но дьявол в деталях. Давайте разложим каждый переход на атомы.

1 Фаза INIT: подготовка workspace

На этом этапе фреймворк берёт манифест окружения — JSON/YAML, где описано:

  • базовый образ runtime (python:3.12-slim + специфические пакеты)
  • шаблон workspace (список директорий, seed-файлы, конфиги)
  • переменные окружения с защитой секретов (vault integration)
  • политики: разрешённые сетевые вызовы, лимиты CPU/RAM, timeout

Важно: INIT — единственная точка, где можно безопасно загрузить зависимости. Если агент начнёт качать pip-пакеты в рантайме — вы потеряете контроль над версиями. Мы всегда проверяем хеш всего workspace перед стартом. Это даёт детерминированность, которая спасает при дебаге ошибок.

💡 Хорошая практика: сравнивать снимки workspace до и после выполнения агента. Это позволяет откатывать изменения, если агент сработал некорректно. У нас это встроено в lifecycle как опция snapshot_on_failure.

2 Фаза READY: прогрев runtime

Runtime не обязан стартовать сразу с агентом. Мы используем пул «тёплых» runtime (hot pool) — контейнеры с предзагруженным кодом, но без запущенного агента. Когда приходит задача, фреймворк назначает runtime на агента, копирует workspace (overlayfs, а не полный copy) и запускает процесс.

Это сокращает время старта с 5-10 секунд до 200-300 миллисекунд. Подход borrowed из архитектуры serverless, но адаптирован для долгоживущих агентов. Кстати, в статье про архитектуру без роутинга мы обсуждали похожие оптимизации для уменьшения latency.

3 Фаза RUNNING: здесь живут ошибки

Во время выполнения мониторинг должен ловить не только crash, но и дрейф конфигурации. Агент может изменить переменные среды, подменить файлы в workspace, запустить дочерние процессы. Мы фиксируем каждое изменение через auditd внутри runtime. Если что-то выходит за рамки политики (попытка записи в /etc, открытие неразрешённого порта) — runtime получает сигнал SIGTERM и переходит в QUARANTINED состояние.

Этот механизм похож на kill-switch, но более мягкий — агент не уничтожается, а изолируется. Потом мы можем либо откатить изменения, либо перезапустить с чистым workspace. Подробно про детерминированное завершение агентов я писал в руководстве по kill-switch.

4 STOPPED и DESTROYED: как не оставить мусор

Самая недооценённая часть lifecycle. Когда агент завершил работу, нужно:

  • stop runtime: SIGTERM -> timeout -> SIGKILL. Не забудьте про orphan processes!
  • snapshot workspace: если требуется сохранить артефакты, архивируем в object storage.
  • destroy workspace: удаление каталога, освобождение inode. Наш фреймворк проверяет, не осталось ли файлов вне workspace (например, через bind mount).
  • return runtime in pool: очистить overlayfs, сбросить state, вернуть в hot pool.

Мы используем двухфазное уничтожение: сначала агент переводится в SOFT_DELETED (архивация данных), а через TTL (default 24h) — в HARD_DELETED (полный cleanup). Это даёт окно для восстановления, если агент сделал что-то полезное, но потом понадобилось откатить.

Предостережение: никогда не полагайтесь на cleanup в runtime-процессе. Агент может зависнуть, и garbage collector не сработает. У нас был случай, когда агент породил 10 дочерних процессов, и после его убийства они висели как зомби. Теперь каждый runtime запускается внутри отдельного cgroup, и при уничтожении мы принудительно чистим всю иерархию процессов.

Как НЕ надо делать: типичные антипаттерны

Я видел реализации, где lifecycle агента выглядел так: start() -> run() -> stop(). Без workspace, без конфига, просто процесс. И это работало ровно до первого сбоя. Вот три главные ошибки:

  1. Один workspace на всех агентов. Кажется, что так проще — не надо копировать файлы. Но первый же агент, который случайно удалит shared файл, положит всю систему. Мы перешли на per-agent workspace после того, как один агент стёр общую папку с конфигами (ирония: он пытался почистить временные файлы).
  2. Игнорирование состояния runtime. Если runtime не умеет сообщать о своём состоянии (READY, HEALTHY, DEGRADED), вы не сможете корректно управлять lifecycle. У нас это привело к тому, что оркестратор пытался отправить задачу агенту, контейнер которого был в dead state, но это не было обнаружено.
  3. Ленивый cleanup. Когда вы не уничтожаете workspace после завершения агента, диск забивается мусором. В одном проекте мы нашли 500 ГБ stale workspace, которые никто не чистил. Пришлось писать отдельный cron-скрипт, хотя правильное место для cleanup — lifecycle.

Если вы хотите углубиться в управление конфигурациями в мультиагентных системах, рекомендую прочитать архитектуру on-prem AI стека — там обсуждаются похожие проблемы изоляции, но в контексте локальных LLM.

Конфиги: сердце lifecycle

Каждый lifecycle state должен читать конфиг. Но конфиг тоже живёт своей жизнью: он может обновляться, версионироваться, содержать ссылки на секреты. Мы сделали ConfigManager, который подписывается на изменения в etcd и автоматически применяет новую версию манифеста для агента без перезапуска runtime (для параметров окружения, которые не требуют restart).

Пример манифеста (упрощённый YAML):


apiVersion: orchagent.io/v3
kind: AgentEnvironment
metadata:
  name: data-processor-agent
spec:
  runtimeImage: agent-python:3.12.8
  workspaceTemplate: /templates/data-pipeline
  resources:
    cpu: 500m
    memory: 512Mi
    ephemeral: 1Gi
  policies:
    network: [allow-outbound-https]
    filesystem:
      readOnlyPaths: ["/etc", "/usr"]
      writablePaths: ["/workspace/output"]
  lifecycle:
    onStart: [validate-credentials]
    onStop: [upload-artifacts]
    onCrash: [snapshot-workspace, notify-admin]

Хитрость: onStart/onStop/crash — это хуки, которые выполняются внутри runtime, но не агентом, а init-процессом (мы используем tini с кастомным entrypoint). Это гарантирует, что даже если агент поломался, хуки отработают. Ведь потеря артефактов после краша — одна из самых болезненных проблем.

Интеграция с оркестром: как lifecycle вписывается в общую картину

Наш оркестровый фреймворк построен на event-driven архитектуре. Каждое изменение состояния агента (INIT -> READY -> RUNNING -> ...) публикует событие в Kafka. Другие сервисы (мониторинг, алертинг, планировщик) подписываются на эти события. Например, когда agent переходит в QUARANTINED, срабатывает триггер, который приостанавливает все задачи в очереди для этого агента.

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

Кстати, мы заметили, что lifecycle напрямую связан с BPMN-оркестрацией. Каждый state может быть представлен как task в BPMN-диаграмме. Если хотите увидеть, как старый стандарт помогает управлять агентами, почитайте статью про BPMN для AI-агентов.

Метрики и observability: как понять, что lifecycle не врёт

Без метрик вы не отличите вечно висящий в INIT агент от нормально работающего. Мы экспортируем следующие метрики для каждого этапа lifecycle:

  • duration_seconds — сколько времени агент провёл в каждом state
  • transitions_total — количество переходов между состояниями (с label причины)
  • workspace_size_bytes — размер workspace (если слишком быстро растёт — алерт)
  • runtime_cpu_usage + memory_usage — потребление ресурсов в RUNNING
  • lifecycle_errors_total — ошибки на этапах init, stop, destroy

Однажды мы заметили, что у 30% агентов transition из INIT в READY занимал больше 5 секунд. Оказалось, что шаблоны workspace лежали на медленном NFS. Перешли на локальный SSD с периодической синхронизацией — latency упало до 300 мс. Без метрик мы бы не нашли причину.

Совет: добавьте healthcheck в runtime, который проверяет, что lifecycle-обновления доходят до агента. У нас был баг: из-за race condition агент думал, что он в RUNNING, а оркестратор считал его INIT. Healthcheck на основе heartbeat решил проблему.

Что дальше: мульти-рантайм и динамическая изоляция

Текущая версия нашего фреймворка (3.2) поддерживает изоляцию на уровне контейнеров. Но мы уже работаем над следующей итерацией — динамический lifecycle, когда агент может сам запрашивать дополнительные ресурсы (например, GPU для инференса) в рамках своего окружения, и lifecycle-менеджер адаптирует runtime без остановки. Это потребует пересмотра модели конфигов и добавления подсостояний.

Вдохновение черпаем из опыта с тремя агентами вместо одного — разделение обязанностей позволило локализовать проблемы. Если у вас ещё нет чёткого lifecycle — начните с него. Потому что без изоляции вы рано или поздно будете тушить продакшен по ночам.

И последнее: не пытайтесь скопировать архитектуру 1:1. Каждый фреймворк имеет свои грабли. Начните с малого: workspace, simple runtime, три состояния. Добавляйте complexity по мере роста. И обязательно прочитайте историю про то, как ИИ-агент снёс продакшен — это лучшая мотивация не лениться с lifecycle.

Подписаться на канал