Sunaba runs AI edits, lints, and test checks inside purpose-built Docker sandbox images. This document describes the images, pre-installed toolchains, detection rules, and version compatibility contracts.
Images are built from docker/Dockerfile.{base,python,go,js,full}. The toolchain installs themselves live in docker/install-python-tools.sh, docker/install-go.sh, and docker/install-js-tools.sh, which the variant Dockerfiles all source, so "what the Python/Go/JS toolchain is" is defined in exactly one place each.
| Tag | Base Layer | Included Backend Toolchains | Use Case |
|---|---|---|---|
sandbox:full |
Base | Python (3.12) + Go + JS (eslint/tsc/jest) + Node runtime. Every toolchain verify can dispatch to. |
The default. Used whenever image= is omitted. |
sandbox:base |
Neutral core | Node runtime + VCS + Search tools. No backend compilers/interpreters or JS dev tools. | FROM parent of the variants. Not a runtime default. |
sandbox:python |
Base | Python (3.12) toolchain + Node runtime (for Pyright). | Lean image; explicit image=python only. |
sandbox:go |
Base | Go compiler/toolchain. | Lean image; explicit image=go only. |
sandbox:js |
Base | eslint, typescript (tsc), jest (Issue #588). | Lean image; explicit image=js only. |
sandbox:minimal |
Minimal | Bare Git + Python + Pytest. | Lightweight or rapid testing. |
JS tools are the global fallback only, never the silent truth. A repository's own node_modules/.bin/{eslint,tsc,jest} -- when present -- always wins over the image-baked global, so a project pinned to a different major version is never linted or type-checked by the wrong one. edit_verify resolves this per invocation (test -x node_modules/.bin/<tool> relative to the verify working directory) and stamps the result: VerifyResult.detail always states whether local (repo node_modules/.bin) or global (image-baked) ran. This is why sandbox:js existing as a separate tag does not, by itself, fix version drift -- the resolution order does.
The default is deliberately the union of the toolchains rather than a guess at which one this project needs. See design_multilang_support.md §6.1 (Issue #584) for why: the host cannot reliably know a repository's language before the container starts, and the image is immutable once it does, so a wrong guess is unfixable — while the in-container detector that runs later is always right.
Each image's HEALTHCHECK asserts the tools it owes verify, and CI runs it with docker run after every build. A tool missing from an image therefore fails the build, not a user's first verify.
The default images come pre-installed with the following utilities, which the server uses to execute first-class verbs:
| Category | Utility | Used For |
|---|---|---|
| Text Search | ripgrep (rg) |
Lexical search in search_in_container |
| Structural Search | ast-grep (sg) |
Structural AST search in search_in_container |
| Text Replace | sd |
CLI-based search-and-replace edits |
| File Search | fd |
Finding file paths |
| Code Indexing | universal-ctags |
AST symbol navigation |
| Linting | ruff / eslint |
Python and JavaScript linting and autofix gates |
| Type Checking | pyright / tsc |
Python and TypeScript static type verification |
| Version Control | git, gh |
Cloning repositories, pushing changes, and managing issues |
| Package Install | uv / pip |
Fast dependency resolution in package_install |
| JSON Processing | jq |
Parsing and formatting command outputs |
Image selection does not involve detection. sandbox_initialize starts sandbox:full unless an explicit image= says otherwise (the aliases full / neutral / python / go / js resolve to pinned digests). The host used to probe the GitHub contents API to pick a variant; that was removed in #584 because the guess preceded an irreversible decision and a failed probe silently produced a container missing the toolchain the project needed.
Language detection still happens — inside the container, at verify time, where it reads the real files and can be re-run. It selects which toolchain to run, not which image to start (edit_verify.detect_languages):
- Manual Override:
language=onverify_in_container/lint_in_container/type_check_in_containerskips detection. - File Extension (single-file targets):
.py→ Python,.go→ Go,.js/.jsx→ JS,.ts/.tsx→ TS (scans upward fortsconfig.json). - Project Marker Files (directory targets):
go.mod→ Go;pyproject.toml/setup.py/requirements*.txt/Pipfile/tox.ini→ Python;package.json→ JS;tsconfig.json→ TS. - Polyglot: all detected languages run, each scoped to the sub-tree holding its marker. The default image carries every toolchain, so a polyglot repository is verified in full rather than falling back to an image that can run neither.
- Unknown: no markers found → verify asks for an explicit
language=instead of silently guessing.
The external contract of the Sunaba server is versioned using Semantic Versioning.
- Covered by Semver: MCP tool names, argument names/types, return shapes, and environment variables. Breaking modifications (e.g. removing a tool or argument) require a minor version bump while in
0.x(or major bump in1.x+). - NOT Covered by Semver: The sandbox image contents, internal module layout, and digests.
- Image Pins: Image digests are pinned inside
image_pins.json(updated viascripts/update_image_pins.py). Any server release is guaranteed to work with the digests bundled in its release package. If you supply a custom image viaimage=, ensure the expected tools (e.g.,ruff,pyright,pytest,eslint,jest,go test) are installed and behave compatibly with the server's edit/verify wrappers.