Статус: реализовано (
1.13.0). Все AC зелёные:bash tests/run.sh→итого провалено: 0. Ниже — описание, зафиксированные решения и журнал изменений.
OpenCode с харнессом для TDD/PRD-разработки, запущенный внутри контейнера с Python-окружением. Окружение полностью воспроизводимо у всех участников команды, а команды агента изолированы от хоста: правило bash: *: allow действует строго внутри контейнера, а не на локальном ноутбуке.
git clone+./run.shна чистой машине с Docker даёт opencode с харнессом без установки opencode и python3 на хост.- Одинаковое окружение у всей команды: те же версии, тот же харнесс, те же инструменты.
- Агент физически не может выполнить опасную команду на хосте: всё, что он делает, происходит в контейнере, а запись вне смонтированных каталогов запрещена правами.
Не является: дистрибутивом харнесса (он только потребляется), docker-in-docker, GUI/десктоп-вариантом opencode, решением для multi-user/удалённого доступа.
Нужны только Docker (с Compose v2), bash и git. opencode, python3 и прочие инструменты на хост ставить не нужно — всё живёт в контейнере.
- bash —
run.sh,tests/run.sh. Проверка:bash --version. macOS и Linux — из коробки. - Docker Engine + Compose v2 — сборка и запуск бокса. Проверка:
docker --version && docker compose version, аdocker infoдолжен выводить версию сервера, а не ошибку подключения (демон запущен). Установка: macOS —brew install --cask docker(Docker Desktop), Linux — https://docs.docker.com/engine/install/. - git — клонирование и дефолтная git-идентичность для контейнера.
Проверка:
git --version. macOS —xcode-select --install, Linux —apt install git. - shellcheck (необязательно) — линтер шелл-скриптов. Проверка:
shellcheck --version; macOS —brew install shellcheck, Linux —apt install shellcheck. Прогон:shellcheck run.sh entrypoint.sh tests/run.sh(ноль замечаний).
./run.sh без аргументов запускает opencode. Аргументы уходят в контейнер
вместо него — так получаются шелл и разовые команды:
| Команда | Что происходит |
|---|---|
./run.sh |
сборка при первом запуске, затем opencode (TUI) |
./run.sh bash |
интерактивный шелл, opencode не запускается — для тестов и ручных проверок |
./run.sh bash -lc '...' |
команда в шелле, её вывод попадает в stdout |
./run.sh setup.sh |
меню направлений стека (см. «Состав образа») |
./run.sh pytest -q |
разовая команда: любая команда контейнера, например pytest, ruff, mypy |
bash tests/run.sh |
тесты самого бокса — выполняются на хосте, не в контейнере |
Под капотом это docker compose run --rm box <команда>: контейнер после
выхода не остаётся, состояние opencode живёт в volume, файлы проекта — в
workspace/.
Структурной связи нет — только потребление:
- Источник правды — https://github.com/teterkin/opencode-harness (публичный).
- На сборке образа Dockerfile клонирует репо с пином по коммиту
(
HARNESS_REF, сейчас —2795f16). Ни сабмодуля, ни форка, ни вендоринга. Локальная копия рядом с боксом — только для справки, в сборку не входит. - Единственный контракт —
install.shхарнесса. Если он сломается на новомmain, это покажет AC3 (тесты самого харнесса внутри контейнера), а не тихая поломка.
git clone+./run.sh(илиdocker compose up) на чистой машине с Docker поднимает opencode с харнессом без предустановки opencode и python3 на хосте.- Внутри контейнера
opencode debug configпоказываетdefault_agent: implementer,opencode debug skill— скиллыtdd-workflowиprd-authoring(харнесс подхватился). tests/run.shхарнесса проходит внутри контейнера (Linux — закрытое «не проверено» из README харнесса).- Рабочий каталог проекта монтируется в контейнер, изменения файлов видны на хосте.
- Git-идентификация (
user.name/user.email) и API-ключ провайдера передаются с хоста, ключ не попадает в слои образа. - Агент внутри контейнера не может писать вне смонтированных каталогов
(попытка записи в
/etcили в$HOMEвне mount'ов неудачна; выполняется не от root). docker compose downостанавливает среду, состояние opencode (история сессий) переживает перезапуск через volume.- Python-dev окружение:
python3,pip,venvработают внутри контейнера,venvв смонтированном каталоге переживает перезапуск. - Базовый тулчейн установлен в образ с версионными пинами
(
requirements-devtools.txt):pytest,ruff,mypy,ipythonдоступны из/opt/devtools, плагиныpytest --covиpytest -n autoработают,ruff checkиruff format --checkпроходят на файле в/workspace. Каждый пин из файла присутствует в контейнере ровно в зафиксированной версии. - Установка пакетов проекта —
setup.sh: без аргументов печатает текущее окружение и меню направлений (завершается по Enter),setup.sh mlставит numpy/pandas/scikit-learn,setup.sh web— fastapi/django/flask/ uvicorn/gunicorn, оба строго по пинам из скрипта;setup.sh gpuотдаёт инструкции по PyTorch/TensorFlow и ничего не устанавливает, неизвестное направление — отказ с ненулевым кодом; пунктremoveудаляет пакеты направлений и их зависимости по манифесту (пустой ответ на подтверждение — отмена), venv и вручную поставленные пакеты остаются; меню всегда показывает базовый тулчейн/opt/devtools(pytest/ruff/mypy/ipython), не зависящий от venv. Вне контейнера скрипт отказывает с подсказкой./run.sh setup.sh,--helpдоступен и на хосте.
- Docker-клиент: ни сокета, ни dind. docker в контейнер не ставится. Приоритет — изоляция (AC6); docker-сборки делаются на хосте. Возможный compose-profile с сокетом — вне этого проекта.
- Харнесс ставится при сборке образа, пин по коммиту (
HARNESS_REF, сейчас —2795f16). Свежий харнесс = пересборка с новым пином; что именно закреплено, проверяется тестом (AC2: вAGENTS.mdобраза есть правило про пины и напоминание проgit init). Клон остаётся в/opt/opencode-harness— оттуда гоняются его тесты (AC3). - Ключи и идентификация — env через
.env(в.gitignore).OPENCODE_API_KEYпередаётся только в рантайме, в слои образа не попадает (проверяется тестом). Git:GIT_USER_NAME/GIT_USER_EMAIL, дефолтыrun.shберёт изgit configхоста;$HOMEв контейнере не-writable (требование AC6), поэтому entrypoint пишет gitconfig в/tmpи выставляетGIT_CONFIG_GLOBAL. - База —
ubuntu:24.04(python3 3.12 из коробки) + основные инструменты Python-разработки:python3,python3-venv,python3-pip,python3-dev,build-essential,git,make,curl,ca-certificates,ripgrep,jq. Зависимости проекта — в venv в смонтированном/workspace/.venv(живёт на хосте); pip-кэш — tmpfs. Версия opencode пинится (OPENCODE_VERSION, на момент планирования —1.18.34), обновление — через--build-arg. - TUI — обычный
docker compose run -itчерезrun.sh. tmux и переподключение к отвалившейся сессии — вне scope: история сессий и так живёт в volume. - Рабочий каталог — только подпапка
workspace/, не корень репозитория:./workspace:/workspace. ПроектныйAGENTS.mdберётся оттуда же; конфиг харнесса — в образе, не в volume.workspace/*в.gitignore(в коммит идёт только.gitkeep), чтобы код проекта не смешивался с кодом бокса. - Базовый тулчейн — отдельный venv
/opt/devtools, состав зафиксирован. Служебные инструменты (pytest, ruff, mypy, ipython) не смешиваются ни с системным python3, ни с venv проекта: свой каталог, свои пины. Владелец — root, поэтому box туда ничего не доустановит: изменение состава = правкаrequirements-devtools.txt+ пересборка образа./opt/devtools/binстоит вPATHпоследним —python3иpipостаются системными, а инструменты доступны как обычные команды. Пины — полныйpip freeze, не только верхнеуровневые: свежий resolve транзитивных зависимостей в чужой сборке — это шанс получить несовместимые версии. Обновление — изолированной правкой пина с прогоном тестов. - Пакеты проекта — не в образ, а в
/workspace/.venvчерезsetup.sh. Иначе состав проекта привязался бы к пересборке образа и делал бы его общим для всех репозиториев. Направления (ml,gpu) закрывают базовые потребности, версии зафиксированы прямо в скрипте; GPU-пакеты скрипт не ставит — только инструкция, потому что CUDA-сборка привязана к драйверу хоста.setup.shпроброшен файловым mount'ом, а не скопирован в образ.
Dockerfile ubuntu:24.04 + apt-пакеты + venv /opt/devtools (пины)
+ opencode (пин) + clone харнесса (пин) + install.sh;
пользователь box (uid 1000), $HOME принадлежит root
и не-writable для box; volume-точка
~/.local/share/opencode, ~/.cache — tmpfs
entrypoint.sh пишет /tmp/gitconfig из GIT_USER_NAME/GIT_USER_EMAIL,
выставляет GIT_CONFIG_GLOBAL, exec "$@"
docker-compose.yml tty/stdin, bind-mount ./workspace -> /workspace,
named volume состояния, env-прокидка
run.sh сборка при первом запуске, дефолты git-идентичности
с хоста, docker compose run --rm
setup.sh направления стека для проекта, монтируется compose'ом
в /usr/local/bin/setup.sh (правки без пересборки)
tests/run.sh проверки AC1-AC10 в стиле тестов харнесса
(bash, check/FAIL)
requirements-devtools.txt полный pip freeze тулчейна /opt/devtools
.env.example OPENCODE_API_KEY, GIT_USER_NAME, GIT_USER_EMAIL
.gitignore .env, workspace/* (кроме .gitkeep)
workspace/ рабочая папка проекта — единственное, что смонтировано
Четыре бандла, всё с версионными пинами — окружение одинаковое у всей команды.
Основа — ubuntu:24.04, python3 3.12 из коробки.
| Пакет | Назначение |
|---|---|
python3 |
интерпретатор системного python |
python3-venv |
создание venv в /workspace/.venv (AC8) |
python3-pip |
pip для системного python (PEP 668: в систему не ставит, только в venv) |
python3-dev |
заголовочные файлы Python — для сборки C-расширений |
build-essential |
компилятор и линковщик — для пакетов с C/C++-компонентом |
git |
клон харнесса и git внутри бокса |
make |
запуск Makefile проекта |
curl |
скачивание установщика opencode |
ca-certificates |
корневые сертификаты для HTTPS |
ripgrep |
быстрый поиск по коду для агента |
jq |
работа с JSON в скриптах и проверках |
Отдельный venv, владелец root; /opt/devtools/bin в конце PATH.
| Инструмент | Назначение |
|---|---|
pytest==9.1.1 |
запуск тестов |
pytest-cov==7.1.0 |
pytest --cov — отчёт о покрытии |
pytest-xdist==3.8.0 |
pytest -n auto — распараллеливание тестов |
ruff==0.16.10 |
линт и формат: ruff check, ruff format --check |
mypy==2.4.0 |
проверка типов |
ipython==9.17.1 |
интерактивный REPL |
Остальные строки requirements-devtools.txt — транзитивные зависимости,
снятые pip list --format=freeze после сборки. Файл проверяется тестом
(AC9): каждый пин обязан присутствовать в контейнере.
OPENCODE_VERSION=1.18.34 — CLI агента, официальный установочный скрипт,
версия проверяется после установки. Обновление — --build-arg.
HARNESS_REF=2795f16 — агент implementer, скиллы tdd-workflow и
prd-authoring, правила TDD/PRD. install.sh кладёт конфиг в
~/.config/opencode; сам клон остаётся в /opt/opencode-harness (оттуда
идёт AC3).
В образ ничего не входит: пакеты проекта ставятся в venv на смонтированном
/workspace/.venv — живут на хосте, образ не пересобирается.
Скрипт запускается только внутри контейнера: ./run.sh setup.sh —
интерактивное меню (текущее окружение + выбор, Enter — выход; при выборе
направления печатает сообщение о начале установки пакетов; всегда показывает
и базовый тулчейн из образа — /opt/devtools: pytest, ruff, mypy, ipython),
./run.sh setup.sh ml|web|gpu — сразу по направлению. Пункт меню
4) remove удаляет пакеты направлений вместе с их зависимостями —
по манифесту $VENV_DIR/.setup-pkgs, который setup.sh ведёт при каждой
установке (разница pip freeze до и после установки). Пакеты, поставленные
в venv руками, remove не трогает, сам venv остаётся. Подтверждение
[y/N]: пустой ответ и любое «нет» — отмена. На хосте он отказывает
с подсказкой: пакеты должны попасть в venv смонтированного каталога, а не в
окружение хоста. setup.sh --help работает и на хосте. Скрипт проброшен в
контейнер как /usr/local/bin/setup.sh, поэтому его правки подхватываются без
пересборки образа; каталог окружения переопределяется VENV_DIR. Вывод
окрашивается на TTY, FORCE_COLOR=1 включает цвет принудительно, NO_COLOR
гасит; в пайпе (как в тестах) управляющих кодов нет. Обе переменные
FORCE_COLOR/NO_COLOR пробрасываются в контейнер через compose, поэтому
работают и через ./run.sh setup.sh.
| Пакет | Назначение |
|---|---|
numpy==2.5.3 |
многомерные массивы и численные вычисления — основа научного стека |
pandas==3.0.6 |
таблицы и работа с данными: чтение/запись, агрегации, JOIN |
scikit-learn==1.9.1 |
классические ML-алгоритмы: классификация, регрессия, кластеризация, пайплайны и метрики |
Веб-разработка и API: фреймворки плюс серверы, которыми их запускают.
| Пакет | Назначение |
|---|---|
fastapi==0.142.2 |
самый популярный современный фреймворк для быстрых API, автодокументация Swagger/OpenAPI из коробки |
django==6.1.2 |
мощный full-stack фреймворк «батарейки в комплекте» для крупных проектов: админка, ORM, авторизация |
flask==3.1.3 |
минималистичный фреймворк для небольших приложений и микросервисов |
uvicorn==0.54.0 |
ASGI-сервер — запуск FastAPI и других ASGI-приложений |
gunicorn==26.2.0 |
WSGI-сервер — продакшен-запуск Django и Flask |
| Что | Назначение |
|---|---|
| PyTorch, TensorFlow | ничего не ставится автоматически: CUDA-сборка привязана к версии драйверов хоста. Скрипт отдаёт ссылки на подбор версии и команды проверки установки |
Контейнеров не остаётся: каждая команда docker compose run --rm
самоудаляется по завершении. Состояние живёт в трёх местах — образ
opencode-python-harness-box, volume opencode-state (история и ключ API
opencode) и смонтированный workspace/ на хосте (venv, манифест, файлы
проекта). Варианты сброса — по возрастанию радикальности.
remove в меню чистит только то, что поставили направления; оставить
«совсем пустой» venv (останется голый pip):
rm -rf workspace/.venv workspace/.venv-ac10
bash tests/run.shrm -rf workspace/.venv
docker compose build --no-cache
bash tests/run.shПины всего стека зафиксированы — apt, /opt/devtools
(requirements-devtools.txt), opencode, харнесс — так что образ
предсказуем. Первый прогон тестов докачает пакеты направлений: pip-кэш
лежит в tmpfs и живёт до перезапуска контейнера.
docker compose down -v --rmi local
rm -rf workspace/.venv
docker compose build --no-cache
bash tests/run.sh-v стирает opencode-state (сессии агента; API-ключ подтянется из
~/.env заново), --rmi local удаляет собранный образ. Осторожно:
docker system prune -a убирает все неиспользуемые образы и кэш системы,
а не только этого проекта.
Версии и связанные коммиты. Каждый цикл TDD — один коммит и одна запись.
| Версия | Что | Коммит |
|---|---|---|
0.1.0 |
README (этот файл), скелет тестов AC — RED | da6a189 |
0.2.0 |
Контейнер: python-dev + opencode + харнесс (AC1–AC3) | 87ddc1d |
0.3.0 |
Изоляция: не-root, запись вне mount'ов запрещена (AC6) | 6575ff4 |
0.4.0 |
Bind-mount, env-ключи, git-идентичность, volume (AC4, AC5, AC7, AC8) | f05bedf, 9fb2dbe |
1.0.0 |
Все AC зелёные, финальный прогон | 351feaf |
1.1.0 |
Mount только workspace/, корень репо не виден в контейнере (TDD3) |
9e6db68 |
1.2.0 |
Тулчейн /opt/devtools c полным freeze, AC9, состав образа в README (TDD1) |
399fc6b |
1.3.0 |
setup.sh: меню направлений, ml по пинам, gpu — инструкция (AC10, TDD2) |
77355bf |
1.4.0 |
Пин харнесса 2795f16: правило 7 (пины) и напоминание про git init, проверка в AC2 |
5bf03f0 |
1.5.0 |
setup.sh отказывает вне контейнера с подсказкой |
e6356eb |
1.6.0 |
Цветной вывод setup.sh (TTY / FORCE_COLOR / NO_COLOR) |
3dd9cbc |
1.7.0 |
Направление web (FastAPI/Django/Flask/uvicorn/gunicorn), раздел README переписан |
cb55eb9 |
1.8.0 |
FORCE_COLOR/NO_COLOR пробрасываются в контейнер через compose |
7cee09e |
1.9.0 |
Сообщение об установке при выборе пункта меню | aacb411 |
1.10.0 |
Пункт меню remove — удаление установленных пакетов с подтверждением |
cfe4693 |
1.11.0 |
remove переделан: удаляет только пакеты направлений (пины ml и web), venv и остальное остаётся |
851156e |
1.12.0 |
Меню всегда показывает базовый тулчейн из образа (/opt/devtools), в т.ч. после remove |
32af586 |
1.13.0 |
remove чистит и зависимости направлений: манифест .setup-pkgs (разница pip freeze до/после установки), ручные пакеты не трогаются |
1cbce29 |
- SSH из контейнера — ключи хоста не монтируются (осознанная изоляция),
git pushпо ssh из бокса работать не будет; только https. Если понадобится иначе — mount~/.sshкак компромисс, отдельным решением. - CLI-путь
setup.sh— сообщение об установке и подтверждение удаления реализованы только в интерактивном меню;setup.sh ml|web|gpuзапускается молча, а пунктаsetup.sh removeв CLI нет. removeпротив ручных пакетов — если пакет поставлен руками и зависит от пакетов направлений (пример:requests→idna),removeснимет эту зависимость; лечение — повторныйpip install <имя-пакета>.- pip-кэш в tmpfs — каждый старт контейнера (в т.ч. прогон тестов) качает пакеты направлений заново; вариант — перенести кэш на постоянный volume.
Тесты бокса: bash tests/run.sh. Линтер: shellcheck run.sh entrypoint.sh tests/run.sh. Цикл: RED — один падающий AC-тест, показать вывод; GREEN —
минимальная правка, показать вывод; REFACTOR — при зелёных тестах.
Коммит — один на цикл.