temporality
Изменить страницу↗
Инженерные документы

Профили песочниц (Sandbox Profiles) [L1-360]

#1. Цель

Дать агентам разные среды исполнения команд: не один образ на всю инсталляцию, а каталог профилей (Go, Node, Python, браузерные E2E), которые администратор собирает и закрепляет, а владелец агента назначает агенту по типу задачи.

#2. Проблематика текущего состояния

#3. Основные понятия

#3.1. Профиль песочницы (Sandbox Profile)

Сущность workspace:

Поле Смысл
id стабильный слаг (node, browser-e2e)
name, description человекочитаемые название и назначение
image digest-pinned ссылка на образ (repo@sha256:…); пусто, пока не собран
toolchains теги тулчейнов (go, node, python, browser) — для UI и подсказок
memory, cpus, scratch_size, tmp_size ресурсные переопределения; пусто = значения инсталляции
cache_env KEY=VALUE-переменные кэшей, указывающие в /cache (NPM_CONFIG_CACHE=/cache/npm)
builtin встроенный профиль каталога; нельзя удалить, можно пересобрать и подстроить ресурсы

#3.2. Резолвинг на старте рана

Цепочка: профиль агента → пусто = профиль инсталляции (образ раннера, текущее поведение без изменений). Разрешение происходит при построении RunInput (все пути запуска: задачи, триггеры, чат, делегирование через run-config). Результат — SandboxSpec (образ + ресурсы + кэш-переменные), который едет с раном до границы исполнения.

#3.3. Граница исполнения

run_command собирает sandbox.Request из SandboxSpec: образ, ресурсы и кэш-переменные применяются на каждый вызов команды. Профиль не попадает в промпт и не виден модели как отдельная сущность — среда исполнения это инфраструктура, а не знание агента.

#4. Принципы

  1. Безопасность не зависит от профиля. Изоляция одна для всех: read-only rootfs, --cap-drop=ALL, no-new-privileges, pids-limit, noexec /tmp, uid 10001. Профиль меняет только тулчейн и ресурсы.
  2. Только digest. Любой образ — встроенной сборки или внешний — закреплён полным @sha256:… и запускается с --pull=never (docs/sandbox-security-matrix.md). Тег без digest не принимается.
  3. Молчаливый фолбэк запрещён. Если агенту назначен профиль, чей образ не собран/отсутствует, команда падает с явной ошибкой «профиль не собран», а не выполняется в чужом окружении: тихая подмена тулчейна хуже видимого сбоя.
  4. Профиль — не привилегия. «Привилегированных» профилей нет: мёртвая тройка restricted/standard/privileged исчезает. Уровни доступа остаются там, где они реально работают: network_access, read_only, орг-политики, квоты бюджета.
  5. Сборка — административное действие. Пересборка образа профиля меняет то, что могут исполнять команды агентов, поэтому попадает в аудит и журнал событий.
  6. Рецепт композируется на известных базах — кроме оператора. Кастомный профиль по умолчанию собирается платформа из известных баз (§5.1), базовые образы не выдумываются. Оператор (и только оператор, роль writer+) может задать полный рецепт — текст Dockerfile в поле dockerfile_override (§5.4), включая любой FROM и multi-stage сборки. Это осознанный обмен: mutable-теги внутри редактируемого FROM принимаются на себя оператором; компенсирующий контроль не снимается — результат сборки по-прежнему пинится @sha256:… и запускается с --pull=never. Модель такого рецепта не пишет: поле отсутствует в контракте черновика визарда (§5.2).

#5. Каталог встроенных профилей

id Основа Тулчейны Особенности
node node:22-alpine + git, curl, jq, bash, ripgrep node NPM_CONFIG_CACHE=/cache/npm
python python:3.12-alpine + git, curl, jq, bash, ripgrep python PIP_CACHE_DIR=/cache/pip, UV_CACHE_DIR=/cache/uv
browser-e2e mcr.microsoft.com/playwright:v1.49.0-jammy node, browser память 2g, scratch 1g, /cache/ms-playwright под браузеры

Профиль по умолчанию (пустое значение) — текущий образ инсталляции; отдельной строки каталога для него нет, UI показывает его как «По умолчанию». Dockerfile-ы встроенных профилей живут в коде ядра (kernel/sandbox), как и базовый EmbeddedDockerfile.

#5.1. Кастомные профили на рецепте (base + packages)

Кастомный профиль — это либо внешний digest-pinned образ, либо рецепт: известная база (base ∈ base, node, python, browser-e2e) плюс дополнительные OS-пакеты (packages). Рецепт собирается платформой: kernel/sandbox.CustomDefinition берёт Dockerfile базы и добавляет один слой установки пакетов (apk для alpine-баз, apt для browser-e2e); тег — temporality-sandbox-<profile-id>:latest, digest закрепляется в строке профиля после сборки.

Ограничения, общие для API и UI:

#5.2. Конструктор из описания (визард)

Тот же паттерн, что у визардов агентов и команд: оператор описывает задачи агента обычным языком, модель-супервайзер (POST …/builder) возвращает черновик — базу из закрытого списка, пакеты, тулчейны и лимиты по классу нагрузки (light 512m/0.5 → builds 2g/2 → e2e 4g/4, потолок 8g/8). Черновик не сохраняется сам: он открывается в обычной форме профиля, оператор правит и сохраняет, сборка — штатным build-эндпоинтом.

#5.3. Docker-сокет хоста (DooD, опция профиля)

Флаг docker_socket у кастомного профиля монтирует сокет хостового демона (/var/run/docker.sock) в каждую песочницу профиля вместе с дополнительной группой, равной GID сокета: пользователь песочницы получает ровно тот доступ к демону, который хост выдал этой группе — не больше. DOCKER_HOST закрепляется на смонтированный сокет, флаги изоляции (--cap-drop=ALL, no-new-privileges, read-only rootfs) сохраняются — профиль получает доступ к демону, но не привилегии контейнера.

Это осознанный размен изоляции: сокет демона эквивалентен root на хосте (-v /:/host, --privileged-сиблинги). Включать только для доверенной автоматизации, которой нужен сам docker (например, агенты, развивающие compose-стек платформы). Настоящий DinD (dockerd внутри песочницы) сознательно не поддерживается — ему нужен privileged- контейнер, уничтожающий модель изоляции.

Ограничения:

#5.4. Операторский рецепт (свой Dockerfile)

У кастомного профиля вместо base/packages может стоять dockerfile_override — полный текст Dockerfile, который оператор пишет сам («operator recipe», решение D1 задачи полного редактирования, docs/tasks/2026-10-21-sandbox-dockerfile-editing):

#6. API

Метод Путь Доступ Смысл
GET /v1/workspace/sandbox-profiles reader каталог с флагом available (образ есть в локальном Docker)
POST /v1/workspace/sandbox-profiles writer создать кастомный профиль: внешний digest-pinned образ или рецепт (база + доп. пакеты)
PUT /v1/workspace/sandbox-profiles/{id} writer изменить кастомный; для встроенного — только ресурсы (memory, cpus, scratch, tmp, cache_env), идентичность каталога и image не трогаются
DELETE /v1/workspace/sandbox-profiles/{id} writer удалить кастомный (встроенный — 409)
POST /v1/workspace/sandbox-profiles/{id}/build writer собрать образ: у встроенного — по каталог-рецепту, у кастомного с base — по составному рецепту; digest закрепляется в строке
POST /v1/workspace/sandbox-profiles/{id}/pull writer скачать внешний образ и закрепить его digest в профиле
POST /v1/workspace/sandbox-profiles/builder writer конструктор: текстовое описание задач агента → черновик профиля от модели-супервайзера (не сохраняет, только предлагает)

События журнала: sandbox.profile.built, sandbox.profile.created, sandbox.profile.updated, sandbox.profile.deleted; те же действия пишутся в аудит. Назначение профиля агенту — обычное изменение карточки агента.

#7. Наблюдаемость

#8. UI

#9. Фазы реализации

#Фаза 1 — реестр и назначение (реализовано)

#Фаза 2 — управление образами (частично)

#Фаза 3 — политики

#10. Критерии готовности (фаза 1)

#11. Открытые вопросы

#12. Архитектурные ограничения

#13. Деплой: воркспейс и оркестраторы

Контракт sibling-контейнеров (ядро стартует песочницу через host-демон и передаёт хостовый абсолютный путь воркспейса) задаёт модель деплоя:

Миграция существующих воркспейсов при переезде на k8s — копирование каталога песочниц в PVC (пути внутри /workspace не меняются).