
Введение
Статья посвящена практической реализации агента с изолированной средой исполнения кода. Рассказываю как устроен агент, который пишет и исполняет код в 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: successsummary: Listed 1 input file(s).- /workspace/input/basic_dataset.csv (61194 bytes)
Читает фрагмент CSV через read_file_content, затем пишет код через write_python_file:
import pandas as pdimport numpy as npdf = pd.read_csv('/workspace/input/basic_dataset.csv')# === SHAPE ===print("=== SHAPE ===")print(df.shape)print()# === DTYPES ===print("=== DTYPES ===")print(df.dtypes)print()... <truncated>
Результат:
status: successsummary: Staged `eda_analysis.py` (131 lines). Next: run_python_file({"script_path": "eda_analysis.py"}).
Срабатывает валидация: файл не может быть больше 120 строк — агент делит его на два скрипта.
Исполняет в песочнице через run_python_file:
status: successsummary: success: Python execution succeeded (exit_code=0).exit_code: 0timed_out: Falsesucceeded: TrueSTDOUT:=== SHAPE ===(891, 12)=== DTYPES ===PassengerId int64Survived int64Pclass int64Name strSex strAge float64SibSp int64Parch int64Ticket strFare float64Cabin strEmbarked strdtype: 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. Практика — эта статья.
Другие мои статьи
ссылка на оригинал статьи https://habr.com/ru/articles/1051892/