
Введение
Статья посвящена практической реализации агента с изолированной средой исполнения кода. Рассказываю как устроен агент, который пишет и исполняет код в Docker песочнице.
Это вторая часть серии про LLM Sandbox. В первой части мы разобрали риски исполнения кода от LLM, ограничения песочницы, способы изоляции (Docker, Wasm, gVisor, microVM) и минимальную архитектуру агент+песочница.
Код реализации агента, skills, полные логи и артефакты примера — в открытом GitHub-репозитории.
Пара слов обо мне
Меня зовут Евгений. Я разработчик и лид ML-команды. На работе и в свободное время занимаюсь проектами, связанными с агентами, LLM и обработкой естественного языка в целом. В своём тг-канале делюсь практическим опытом и рассказываю про техническую часть AI и ML. Ссылка на пост-навигатор канала.
Реализация агента с песочницей
В этом разделе посмотрим как агент реализован в репозитории: оркестратор, субагенты, skills и Docker Sandbox.

Архитектура агента
Если кратко, агент работает так:
Получает задачу от пользователя.
С помощью инструментов выполняет планирование и составляет набор подзадач.
Назначает их субагентам.
Субагент пишет код для решения подзадачи и исполняет его в песочнице.
После выполнения подзадачи обновляется список задач.
Артефакты, полученные в песочнице, сохраняются, а выходные файлы показываются в интерфейсе и доступны для скачивания.
На финальном этапе оркестратор агрегирует решение всех подзадач и артефакты.
Выдаёт пользователю ответ.
Основной паттерн работы агента - гибрид ReAct + Plan-Execute. Основной фреймворк - LangGraph.
Посмотрим подробнее на ключевые блоки.
Содержимое агента

Оркестратор — мозг агента. Обрабатывает запрос пользователя, планирует решение задачи, включая декомпозицию, выдаёт задачи субагентам и синтезирует финальный ответ.
Субагент — сессия с LLM и чистым контекстным окном. Оркестратор передаёт субагенту конкретную задачу и необходимый контекст. Так можно не перегружать основного агента, а выполнять подзадачи независимо.
Внутри оркестратора и субагента находится LLM с отдельными системными промптами.
Каталог навыков (skills) — набор подробных инструкций под конкретные задачи. Например, в
skills/presentationв репозитории полностью описан процесс написания презентаций.Что такое skills, я писал в канале (ссылка на пост). Оркестратор и субагенты всегда видят название и описание навыка, а при необходимости подгружают полную инструкцию через инструмент
read_skill.Инструменты для управления подзадачами.
create_todo_list— создаёт набор подзадач.complete_todo— закрывает подзадачу при успешном решении.start_next_todo— начинает работу над подзадачей.revise_todo_list— пересобирает список подзадач, если текущий план перестал подходить.block_todo— блокирует подзадачу, если решение пошло не по плану.
Инструменты управления файлами.
read_skill— читает навык из файла и добавляет его в контекст.list_input_files— список файлов, отправленных пользователем.list_output_files— список файлов, созданных в ходе задачи.read_file_content— читает файл.
Инструменты исполнения кода.
write_python_file— подготавливает полный Python-скрипт и сохраняет в/workspace/code.run_python_file— исполняет ранее подготовленный скрипт в песочнице.
Docker Sandbox — песочница для изолированного исполнения кода. В режиме субагента создаётся отдельный Docker контейнер на время выполнения подзадачи и серии sandbox-вызовов. Команда запуска описана в теоретической части, флаги:
--network=none,--read-only,--cap-drop=ALL,--security-opt=no-new-privileges, non-root user, лимиты CPU/RAM/PIDs, tmpfs для/tmp, read-only mount для/workspace/inputи/workspace/code.
Важно: Docker с такими флагами — практичная изоляция для локального MVP, для продуктивной среды может понадобиться более серьезная изоляция в несколько уровней. См. теоретическую часть.
Инструменты работы с Bash умышленно не предоставляются агенту для прозрачной и контролируемой работы с заданными инструментами.
Состояние запуска: что видит пользователь
В интерфейсе показывается весь путь агента: текущий план, закрытые подзадачи, вызовы инструментов и их результаты. Полные логи — в подключённом Langfuse, а локально — в agent/runs/<run_id>/events.jsonl, agent/traces/<run_id>.jsonl и agent/web-artifacts/<run_id>/.
Пример работы агента
Для наглядности посмотрим на конкретный запуск.
Задача (промпт)
Проанализируй набор данных и подготовь презентацию с ключевыми выводами EDA.
Набор данных
Датасет соревнования «Титаник».
P.S. Модели видели этот датасет множество раз при обучении, и написать код аналитики несложно. Цель примера — показать взаимодействие агента с песочницей, а не способность LLM писать код.
Модель
Qwen3.5-27B — модель, которую можно развернуть на доступном железе. В репозитории это значение используется как дефолт (Qwen/Qwen3.5-27B), но его можно заменить через LLM_MODEL на любую OpenAI-совместимую модель.
Шаги выполнения задачи
Первым шагом агент создаёт список подзадач:
eda-analysis— первичный анализ данных.create-presentation— создание презентации.
# Plan - [>] eda-analysis: Exploratory data analysis of basic_dataset.csv **(active)** - skills: eda, python-scripts - inputs: /workspace/input/basic_dataset.csv - success: EDA script runs successfully and prints key statistics, distributions, correlations, and missing value summaries to stdout. - outputs: /workspace/out/eda_summary.csv - [ ] create-presentation: Create PowerPoint presentation with key EDA findings - skills: presentation, python-scripts - inputs: /workspace/input/basic_dataset.csv, /workspace/out/eda_summary.csv - success: A .pptx file is generated with slides covering dataset overview, survival analysis, demographics, fare analysis, and key insights. - outputs: /workspace/out/titanic_eda_presentation.pptx
Для каждой подзадачи заданы требуемые навыки, входные/выходные файлы и критерии успеха.
Второй шаг — start_next_todo. Агент просматривает доступные входные файлы через list_input_files:
status: success summary: Listed 1 input file(s). - /workspace/input/basic_dataset.csv (61194 bytes)
Читает фрагмент CSV через read_file_content, затем пишет код через write_python_file:
import pandas as pd import numpy as np df = pd.read_csv('/workspace/input/basic_dataset.csv') # === SHAPE === print("=== SHAPE ===") print(df.shape) print() # === DTYPES === print("=== DTYPES ===") print(df.dtypes) print() ... <truncated>
Результат:
status: success summary: Staged `eda_analysis.py` (131 lines). Next: run_python_file({"script_path": "eda_analysis.py"}).
Срабатывает валидация: файл не может быть больше 120 строк — агент делит его на два скрипта.
Исполняет в песочнице через run_python_file:
status: success summary: success: Python execution succeeded (exit_code=0). exit_code: 0 timed_out: False succeeded: True STDOUT: === SHAPE === (891, 12) === DTYPES === PassengerId int64 Survived int64 Pclass int64 Name str Sex str Age float64 SibSp int64 Parch int64 Ticket str Fare float64 Cabin str Embarked str dtype: object ...<truncated>
Аналогично создаётся /workspace/out/eda_summary.csv. Подзадача закрывается через complete_todo:
{ "id": "eda-analysis", "notes": "EDA script ran successfully, printed key statistics/distributions/correlations/missing values to stdout, and saved /workspace/out/eda_summary.csv with 25 summary metrics." }
Следующая подзадача — презентация. Агент подгружает skills presentation и python-scripts. В процессе возможны ошибки, которые агент исправляет в следующих итерациях работы с кодом:
STDERR: Traceback (most recent call last): File "/workspace/code/deck_build_part1.py", line 11, in <module> add_bg_rect(slide, DARK) File "/workspace/code/deck_helpers.py", line 17, in add_bg_rect width = slide.slide_width ^^^^^^^^^^^^^^^^^ AttributeError: 'Slide' object has no attribute 'slide_width'. Did you mean: 'slide_id'?
Итоговый ответ агента (сокращённо):
## Результаты анализа и презентация ### 📊 Ключевые выводы EDA (Titanic, 891 пассажир) |Метрика|Значение| |---|---| |**Общий уровень выживших**|~38%| |**Пропущенные данные**|Age: 20%, Cabin: 77.1%, Embarked: 0.22%| ... ### 📁 Созданные файлы |Файл|Описание| |---|---| |`titanic_eda_presentation.pptx`|Презентация из 9 слайдов с графиками и выводами| |`eda_summary.csv`|Сводка из 25 метрик EDA| |`chart_survival.png`|Общая выживаемость| ...
Полные логи и артефакты — в репозитории.
Токен-экономика запуска
Пример выше — не только демонстрация песочницы, но и типичный агентский запуск с заметной «стоимостью» в токенах. Все LLM-вызовы, тайминги и usage отслеживались через Langfuse. Ниже — разбор run feec9b8b1e754c018d6de3f2f09d4f8c (Titanic + презентация, Qwen3.5-27B, OpenRouter/OpenAI-совместимый API).
Что считаем. На каждом шаге цикла модель получает промпт (input_tokens), генерирует ответ (output_tokens). Часть промпта может быть взята из KV-cache (cached_input_tokens). Песочница и файловые инструменты сами по себе токены не тратят, но результаты их работы возвращаются в контекст — и именно это раздувает входной запрос в модель на следующих шагах. В таблице ниже суммируются все вложенные LLM-вызовы оркестратора и субагентов из Langfuse.
Итого по запуску
Метрика | Значение |
|---|---|
Время работы | 182,6 с (~3 мин) |
LLM-вызовов | 29 (4 оркестратор + 8 субагент EDA + 17 субагент presentation) |
Input tokens | 359 913 |
Output tokens | 16 551 |
Всего (input + output) | 376 464 |
Запусков кода в песочнице | 7 (~5 с суммарно) |
Sandbox занял ~3% общего времени работы. Основная задержка — ожидание LLM и накопление контекста, а не исполнение Python.
Разбивка по фазам (данные Langfuse-трейса):
Шаг | Фаза | Действие | LLM-вызовов | Input | Output | Cached | Всего токенов | Время |
|---|---|---|---|---|---|---|---|---|
1 | Оркестратор |
| 1 | 3 429 | 409 | 0 | 3 838 | 5,5 с |
2 | Оркестратор |
| 1 | 3 895 | 77 | 3 168 | 3 972 | 1,6 с |
3 | Субагент EDA | файлы → код → sandbox → | 8 | 68 810 | 3 527 | 36 960 | 72 337 | 39,7 с |
4 | Оркестратор |
| 1 | 4 280 | 53 | 3 168 | 4 333 | 1,1 с |
5 | Субагент presentation | skills → код → sandbox → | 17 | 274 700 | 11 832 | 204 864 | 286 532 | 127,6 с |
6 | Оркестратор | финальный ответ пользователю | 1 | 4 799 | 653 | 4 224 | 5 452 | 7,0 с |
Итого | 29 | 359 913 | 16 551 | 252 384 | 376 464 | 182,5 с |
Что видно из цифр:
Оркестратор дешёвый — 17,6 тыс. токенов (4,7% от запуска). Он планирует, делегирует и синтезирует ответ, не таская в контекст логи.
Субагент presentation — главный потребитель — 286,5 тыс. токенов (76%) и 128 с (70% времени). Причина: много итераций
write_python_file/run_python_file, ошибки в коде (slide_width), повторные прогоны и растущий контекст.Контекст растёт по цепочке. Input на последнем LLM-вызове presentation-субагента — 24 706 токенов (против 4 445 на первом).
Самые дорогие вызовы — генерация кода, а не sandbox.
write_python_fileс output 1 777 токенов vsrun_python_fileс output 56 токенов.Cache помогает, но не спасает от роста контекста. Суммарно из cache обслужено 252 384 токенов input (~70% у presentation-субагента).
Практический вывод: песочница изолирует исполнение, но не стоимость. Чтобы удержать token budget, нужны те же приёмы, что и в инжиниринге контекста: субагенты с чистым окном, лимиты на размер tool output, сокращение stdout/stderr, skills по требованию (read_skill), а не в каждый промпт целиком.
Заключение
На практике песочница встраивается в агентскую обвязку как один из инструментов агентского цикла — рядом с планированием, skills и файловыми операциями. Из этого запуска видно три вещи:
Docker Sandbox с жёсткими флагами — рабочий вариант для локального MVP: отдельная сессия для подзадачи/сессии исполнения кода в песочнице, изоляция исполнения, понятный обмен артефактами через
/workspace/inputи/workspace/out.Оркестратор + субагенты помогают не смешивать планирование и «грязный» контекст исполнения кода.
Основное время тратится на работу LLM. 7 запусков кода заняли ~5 с; основная стоимость — в генерации и накоплении контекста.
При этом успех песочницы означает, что код исполнился и артефакты создались, а не то, что результат автоматически стал правильным. Для настоящих пользовательских отчётов всё равно нужны валидация содержания, проверка визуального качества и фактической информации.
Теорию про риски, ограничения и альтернативы (Wasm, gVisor, microVM) смотрите в первой части. Код, логи и все артефакты смотрите в репозитории на GitHub.
P.S. Спасибо за прочтение. Приглашаю в тг-канал «В погоне за NLP» (@chasing_nlp) — там про новости из мира AI/ML, анонсы новых статей и опыт разработки проектов с LLM и агентами.
Серия «LLM Sandbox»
Часть 1. Теория — риски, ограничения, способы изоляции.
Часть 2. Практика — эта статья.
Другие мои статьи
