Ephemeral Mount Modes (`tmpfs`, `ephemeral`)
Status: Open — design proposal (Phase 1, Agent Orchestrator Research Program)
Problem
Section titled “Problem”jackin’s per-mount isolation
vocabulary is shared | worktree | clone. These modes describe git
semantics for the mounted path. They don’t
cover a different axis the operator also cares about: storage kind.
Specifically, two storage shapes have no declarative way to be expressed today:
- A path the agent can read and write, but whose contents should NOT
appear on the host filesystem and SHOULD reset between launches.
(Examples:
~/.gradle/daemon, an agent’s/var/tmp, an OpenCode session-state directory that should be per-instance.) - A path that lives in tmpfs (memory-backed) for performance or for
guaranteed teardown when the container exits. (Examples:
/tmp, a temporary~/.sshshadow during a rebase script.)
Today an operator can only achieve the first by writing a Docker volume
into a --mount arg and managing its lifecycle by hand, and the second
by passing --tmpfs ad-hoc. Neither lives in jackin.role.toml or the
workspace config.
multicode confirms this is a real, frequently-used distinction. Their
example config.toml declares 7 paths under isolated and 1 under
tmpfs — both essential to keeping concurrent agents from corrupting
each other’s caches and to keeping host state clean.
Why It Matters
Section titled “Why It Matters”- Parallel agents share host caches today. Two agents both running
gradle buildrace on~/.gradle/daemonbecause jackin’ has no way to declare “this directory is per-instance, not host-shared”. - Toolchain hygiene matters at scale. With the autonomous task queue in place, an agent fleet runs hundreds of builds a day. Each one should start with a clean per-instance scratch space; today, they don’t.
/tmpshould default to tmpfs. Many roles bake configuration assuming/tmpis fast and ephemeral. jackin’s default/tmpis the container’s own filesystem — fine for correctness, but larger and slower than necessary.- This is the only mount-mode gap found in the verification pass
comparing jackin’ against multicode’s
[isolation]block. Closing it brings the mount surface to parity.
Inspiration in multicode
Section titled “Inspiration in multicode”Sources:
- README — Isolation (mounting policy and Apple-container subsections describe per-workspace isolation rules)
- Config —
config.toml[isolation]block — theisolated = [...]andtmpfs = [...]lists in the example
[isolation]isolated = [ "~/.local/share/opencode", "/var/tmp", "~/.gradle/daemon", "~/.gradle/.tmp", "~/.gradle/kotlin-profile", "~/.local/share/kotlin", "~/.local/state/opencode",]tmpfs = [ "/tmp",]Semantics:
isolated: the path inside the workspace is mapped to a per-workspace dedicated mount (or tmpfs overlay). It’s writable while the workspace runs but not visible on the host and wiped per workspace start. Implementation:bwrap --tmpfs <path>overlays or a dedicated mount; the path doesn’t share with host.tmpfs: explicit tmpfs mount. Memory-backed, ephemeral, gone on exit. Implementation:bwrap --tmpfs <path>.
The two are subtly different: isolated may be backed by disk for
larger working sets (build artifacts), while tmpfs is always memory.
multicode lets the operator pick.
Recommended Shape
Section titled “Recommended Shape”Extend the per-mount isolation
enum vocabulary by two more modes, plus extend the workspace mount
schema so they don’t all need a host src.
Schema extension
Section titled “Schema extension”The existing per-mount isolation schema requires a host src:
[[workspaces.X.mounts]]src = "~/projects/x"dst = "/workspace/x"isolation = "worktree"Add two modes that don’t need src:
[[workspaces.X.mounts]]dst = "/tmp"isolation = "tmpfs"size = "512MiB" # optional; Docker default 64MiB is small
[[workspaces.X.mounts]]dst = "/home/agent/.gradle/daemon"isolation = "ephemeral"# 'ephemeral' = per-instance Docker volume, persists across container# restarts of the same instance, deleted on `purge`. Not bind-mounted# to host.V1 enum vocabulary becomes: shared | worktree | tmpfs | ephemeral
(plus clone when V1.1 ships). Validation:
tmpfsandephemeralrejectsrc(no host source).tmpfsandephemeralrejectreadonly(writability is their whole point; if you want read-only, you wantsharedwithreadonly = true).tmpfsaccepts an optionalsizefield with the same parsing rules as declarative resource limits.
Backend translation
Section titled “Backend translation”- Docker (today):
tmpfs→--tmpfs <dst>:size=<n>,mode=1777.ephemeral→--mount type=volume,source=jackin-<container>-<slug>,target=<dst>, with the volume created on firstloadand deleted bypurge(the per-instance cleanup helper already enumerates Docker volumes by label; add a label and reuse it). - Apple container (when selectable backends ships): use the equivalent allocation primitives.
- bwrap (if it ever lands):
--tmpfsfor both modes; the ephemeral case is bwrap’s natural shape.
Role declarations
Section titled “Role declarations”Roles can declare ephemeral defaults in jackin.role.toml:
[[runtime.mounts]]dst = "/home/agent/.gradle/daemon"isolation = "ephemeral"
[[runtime.mounts]]dst = "/tmp"isolation = "tmpfs"size = "1GiB"These are role-level defaults that workspaces inherit unless
overridden by a workspace mount on the same dst. Useful so a Java
role can declare its Gradle hygiene once and every workspace
gets the right behavior.
Scope (V1)
Section titled “Scope (V1)”- Extend the mount isolation enum to
shared | worktree | tmpfs | ephemeral. - TOML schema extension for
dst-only mounts (nosrc). - Optional
sizefield ontmpfs. - Docker translator for both new modes.
- Per-instance volume label scheme (
jackin.instance=<container>,jackin.dst=<slug>) so cleanup can enumerate. - Agent-class-level mount defaults via
[[runtime.mounts]]injackin.role.toml; workspace mounts override ondst. purgecleans up Docker volumes labeled with the instance name.- Docs update for mount isolation, the Mounts guide, and the architecture reference.
tmpfssize enforcement beyond Docker’s default behavior. Use Docker’s, document the limit.ephemeralmode with persistent behavior acrosspurge(operator-managed volume name). Out of V1; if needed, add a fifth modevolume = "<name>"that doesn’t auto-clean.- File ownership / mode flags on
tmpfs(mode = "0700"). Defer until a use case surfaces. - Cross-instance shared ephemeral volumes (“all my Java agents share
one Gradle daemon dir”). Out of V1; the use case is handled by
shared-cache mounts under
shared cache pattern (a
shared
~/.gradlemount overlaid on an isolated source tree).
Open Questions
Section titled “Open Questions”- Naming.
ephemeralis descriptive but not great. Alternatives:instance(it’s per-instance),volume(it’s a Docker volume),private(it’s not host-visible). Recommended:ephemeral— matches the operator’s mental model (“this is gone when I purge”). - Default
tmpfsfor/tmp. Should every role get/tmpas tmpfs by default, or stay opt-in? Recommended: opt-in for V1 — defaults can change downstream after seeing real usage. - Interaction with worktree
isolation. A workspace
with a worktree-isolated source mount and a sibling ephemeral
mount should compose cleanly (parent-mount-before-child rule).
Confirm the existing collapse/ordering logic handles a no-
srcchild mount correctly. - Is this its own leaf or an extension to mount isolation? This leaf treats it as a Phase 1 standalone item because it ships independently and the design doesn’t require any per-mount- isolation refactor. The Workspaces and Mounts guides get notes pointing here once this is implemented.
Related Files
Section titled “Related Files”src/workspace/mod.rs—MountConfig,MountIsolationenum extensionsrc/workspace/mounts.rs— parser for the new modes; rejectssrcfortmpfs/ephemeralsrc/runtime/launch.rs— Docker arg constructionsrc/runtime/cleanup.rs— volume cleanup onpurgesrc/manifest/mod.rs—[[runtime.mounts]]role-level defaultssrc/cli/workspace.rs—--mount-isolation tmpfs/ephemeralin workspace edit/create
See Also
Section titled “See Also”- Agent Orchestrator Research Program
- Per-mount isolation — the existing modes; this leaf extends the enum vocabulary
- Selectable sandbox backends — cross-backend translation home
- Declarative resource limits — shares the size-parsing helper