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

Хранилище песочниц: инвентаризация, квоты и уборка (Sandbox Storage)

Дата: 2026-10-16. Отвечает на два наблюдения из эксплуатации:

  1. Агенты обмениваются данными через персистентную ФС проекта, и она превращается в неуправляемый объём («сотни гигабайт за недели»), в котором невозможно разобраться.
  2. Агент заявляет в отчёте «создан артефакт docs/…/runbook.md», имея в виду файл в воркспейсе, — реестр артефактов при этом пуст, потому что тулы artifact_create/… не вызваны.

#1. Цель

Сделать персистентное хранилище песочниц управляемым:

#2. Проблематика (на момент проектирования)

Фаза 1 (инвентаризация, снапшоты, файловый API) закрыла пункты ниже; уборка и квоты — следующие фазы.

Раскладка сегодня (kernel/sandbox/docker.go, kernel/agent/activities.go):

Область Хост-путь В контейнере Жизнь
Воркспейс проекта <root>/projects/<project>/ /workspace персистентная, общая на проект
Кэш тулчейнов <root>/cache/<project>/ /cache персистентная, без лимита
Загрузки пользователя <root>/projects/<p>/uploads/ /workspace/uploads персистентная
Входящие intake <root>/projects/<p>/inbox/ /workspace/inbox персистентная
Стейджинг каналов <root>/inbound/… — удаляется после обработки
/tmp tmpfs /tmp умирает с контейнером
/scratch <root>/scratch/<project>/<run>/ /scratch пер-рановая: файлы живут между вызовами run_command одного рана, свипаются по TTL после него

Проблемы:

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

#3.1. Область хранения (storage area)

Именованный корень под KERNEL_SANDBOX_ROOT: workspace (проекты), cache (кэши тулчейнов), inbound (стейджинг). Обход и квоты оперируют областями; вне корня харнес ничего не трогает.

#3.2. Снапшот (storage snapshot)

Результат обхода области: {path, size_bytes, files, dirs, mtime}, агрегированный до глубины N (по умолчанию 3) с топ-подкаталогами. Снапшот — кэшируемый артефакт вычисления, а не событие на каждый чих: пересчитывается по крону (раз в час) и по кнопке.

#3.3. Политика удержания (retention policy)

Декларативные правила, кому можно умереть:

Политики — настройки инсталляции (workspace_setting), не код.

#3.4. Квота (quota)

Мягкий лимит: фаза 1 — env KERNEL_STORAGE_DEFAULT_QUOTA_BYTES (по умолчанию 50 GiB на проект); per-project настройка (workspace_quota_per_project) и событие storage.quota_exceeded — фаза 2, не реализованы. Жёсткая грань (фаза 3) — отказ новых run_command проекта с явной ошибкой «превышена дисковая квота проекта» до уборки.

#4. Принципы

  1. Серверная агрегация. Обход ФС и подсчёт размеров — в ядре, с лимитом глубины и времени; фронтенд только рендерит (тот же вывод, что docs/system-dashboard.md §1).
  2. Ничего не удаляется молча. Каждое удаление — событие журнала storage.evicted с путём, размером, причиной и политикой; ручная уборка — только operator и только с аудитом.
  3. Уборка не конкурирует с работой. Sweeper проверяет активные раны проекта (журнал/Temporal) и пропускает занятые; обход идёт с nice-приоритетом и ограничением по времени.
  4. Всё в sandbox root. Никаких удалений и обходов вне KERNEL_SANDBOX_ROOT; защита от выходов за корень — та же, что у файлового API.
  5. Файл ≠ артефакт. Рабочий продукт для человека регистрируется тулами артефактов; хранилище — транспорт и кэш, а не система документов.

#5. API

#5.1. Обзор хранилища

GET /v1/workspace/storage (reader, видимость проектов как у списка ранов):

{
  "generated_at": "…",
  "root": "/srv/temporality/.sandbox",
  "disk": { "total_bytes": 1e12, "free_bytes": 2.3e11 },
  "projects": [{
    "project": "omni",
    "workspace_bytes": 41200000000, "workspace_files": 210000,
    "cache_bytes": 8100000000,
    "quota_bytes": 53687091200, "quota_used": 0.77,
    "top_paths": [{ "path": "node_modules", "bytes": 2.1e10 }],
    "last_activity_at": "…"
  }]
}

Источник — свежий снапшот (кэш в памяти ядра; ?refresh=1 — пересчёт).

#5.2. Детали проекта

GET /v1/workspace/storage/projects/{id}?depth=4 — дерево с размерами и mtimes до глубины N, топ файлов по размеру. Reader.

#5.3. Ручная уборка (фаза 2, не реализована)

POST /v1/workspace/storage/projects/{id}/sweep { "dry_run": true, "areas": ["cache", "workspace"] } (operator). Возвращает план и факт: что удалено/будет удалено, сколько байт освобождено. Аудит + событие. Реализованный файловый API (фаза 1): GET dir/file, POST files, DELETE file — на /v1/workspace/storage/....

#5.4. Здоровье сервера

GET /v1/system/health (reader):

{
  "disk": [{ "path": "/srv/temporality/.sandbox", "total_bytes": 1e12, "free_bytes": 2.3e11 }],
  "cpu": { "load1": 3.2, "load5": 2.8, "cores": 16 },
  "memory": { "total_bytes": 6.7e10, "available_bytes": 1.2e10 },
  "docker": { "images_bytes": 2.4e10, "containers_running": 12 }
}

Источник: statfs по корням, /proc/stat+/proc/loadavg, /proc/meminfo (в стандартном Docker это хостовые значения), docker system df — по существующему docker-бинарному пути ядра.

#5.5. События журнала

#6. Sweeper в ядре

Не внешний cron: ядро уже владеет docker-бинарем, journal и настройками.

#7. UI

#7.1. Дашборд (docs/system-dashboard.md, вкладка «Обзор»)

#7.2. Страница «Хранилище» (Настройки → Хранилище, operator)

Таблица проектов (объёмы, квота, последняя активность, статус последней уборки) + дерево/топ путей проекта + dry-run и запуск уборки, история storage.evicted рядом. Для reader — read-only.

#7.3. Карточка проекта

Строка «хранилище: 41 GiB / 50 GiB» со ссылкой на страницу хранилища.

#8. Дисциплина артефактов

Файл в воркспейсе агент создаёт законно (это транспорт); проблема — подмена понятий в отчёте. Решение из двух частей:

  1. Контракт в системном промпте/навыках: «рабочий продукт для человека — только через artifact_create/artifact_submit; файл в /workspace — черновик». Проверяется контуром качества (judge смотрит, зарегистрирован ли заявленный артефакт).
  2. Мост artifact_attach (фаза 3): новый тул — «сделай артефакт из файла воркспейса». Ядро читает файл из sandbox root, создаёт draft-версию артефакта с телом и origin_run; дальше обычный жизненный цикл (docs/artifact-lifecycle.md). Это закрывает кейс «агент дописал секцию в docs/…/runbook.md и назвал это артефактом» одним вызовом вместо пересылки содержимого через промпт.

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

#Фаза 1 — наблюдаемость (read-only)

#Фаза 2 — уборка и мягкие квоты

#Фаза 3 — жёсткие квоты и мост артефактов

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

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