Filesystem & Caches¶
Every job runs in a container whose root filesystem is mounted read-only
(--read-only). This is a core security property: untrusted code cannot modify
any binary, library, or config baked into the image. Understanding which paths
are writable explains how caching libraries behave and how to load large
models without hitting "read-only filesystem" errors.
What is writable¶
| Path | Type | Writable? | Persists between runs? |
|---|---|---|---|
/ (root, incl. /home/sandbox) |
image layers | No (read-only) | n/a |
/tmp |
tmpfs (RAM-backed) | Yes | No wiped on exit |
/output |
bind mount | Yes | Collected as artifacts |
/input, /code |
bind mount | No (read-only) | n/a |
Two consequences matter for library behavior:
- The sandbox user's home,
/home/sandbox, lives on the read-only root. Anything that tries to write under$HOMEfails. /tmpis RAM-backed and size-capped (100 MB default, 300 MB while installing dependencies) and is wiped when the container exits. It is scratch space, not storage.
Today each container is single-use, so nothing written at runtime survives. Durable, per-agent workspaces are on the roadmap, see the project direction in the README.
Library caches ($HOME/.cache)¶
Many libraries cache reusable data under $HOME/.cache by the
XDG convention,
matplotlib caches a font list, fontconfig caches font metadata, ezdxf caches parsed
resources. On a read-only $HOME those writes fail, and you would see warnings like:
Tako VM avoids this by pointing the cache/config directories at the writable /tmp
tmpfs before running your code (in docker/entrypoint.sh):
export XDG_CACHE_HOME=/tmp/.cache # ezdxf, fontconfig, most XDG-aware libs
export MPLCONFIGDIR=/tmp/.cache/matplotlib # matplotlib uses its own variable
This is handled automatically, no action needed for these libraries. The cache lives
in /tmp, so it is recomputed on the next run (it is an optimization, not stored data).
Loading ML models (Hugging Face, PyTorch, NLTK, ...)¶
Caching libraries tolerate an unwritable cache, they warn and continue. Download
and cache libraries do not: when you call AutoModel.from_pretrained(...), the
library downloads weights and writes them under $HOME/.cache (or HF_HOME), then
loads them from that path. There is no in-memory fallback, so an unwritable cache is a
hard failure, not a warning.
Redirecting the cache to /tmp does not reliably fix this, /tmp is too small for
a multi-hundred-MB model, is RAM-backed (so it competes with your job's memory limit),
and is wiped every run (so the model re-downloads each time). Network is also disabled
by default (--network=none), so a runtime download often cannot happen at all.
The correct approach is to pre-stage the model so it is present and read-only at runtime. Hugging Face and friends are happy to load from a read-only cache, they only fail when they must write one. Set offline mode so the library reads the staged files instead of checking the network:
Option A, bake the model into the executor image (build time)¶
Download the model while building the image, where the filesystem is writable, and ship it inside the image. This is the same build-time pattern as Custom Libraries.
ENV HF_HOME=/opt/hf-cache
# Pin a revision so every run loads byte-identical weights.
RUN python -c "from huggingface_hub import snapshot_download; \
snapshot_download('org/model', revision='<commit-sha>')"
At runtime /opt/hf-cache is read-only, which is fine, the weights are already there
and the library only reads them.
- Pros: fully self-contained, reproducible, no runtime network.
- Cons: large images (weights can be GBs); one image per model set.
Option B, mount a read-only model volume (runtime)¶
Populate a volume once and mount it read-only, the same shape Tako VM already uses for
its read-only /code//input mounts and the UV_CACHE_VOLUME dependency cache.
- Pros: images stay small; swap models by pointing
HF_HOMEelsewhere; share across jobs. - Cons: the volume must be populated and managed out of band.
Determinism¶
Pin the model revision to a commit SHA. A baked, revision-pinned model loads identical
weights on every run, which is stronger than a runtime download that could silently pull
an updated or moved model.
Summary¶
| If you use... | Do this |
|---|---|
| matplotlib, ezdxf, fontconfig (cache-for-speed libs) | Nothing, caches are redirected to /tmp automatically |
| Hugging Face / torch / NLTK (download-and-cache libs) | Pre-stage the model (bake into image or read-only volume) and set HF_HOME + offline mode |
| Anything that must write at runtime | Write under /tmp (scratch, ephemeral) or /output (collected); never expect $HOME to be writable |