Share host cache dirs into agent container, with periodic eviction #30

Open
opened 2026-06-23 05:14:26 +00:00 by erik · 0 comments
Owner

The fjx supervisor's dockerSpawnFn (src/supervise.ts:884) shares nothing writable with the host besides ~/.claude{,.json}, ~/.codex, and the project worktree. Every tick re-derives the Deno module cache, the trivy vuln DB (~600 MB), and any other tool caches from scratch in the container's overlay layer. This is slow (cold just validate does a lot of redundant download work) and wasteful of host Docker disk on each tick. A prior fix made /agent-home writable (images/fjx-base/Dockerfile); that unblocks ~/.cache writes but doesn't persist them.

Proposal

Bind-mount a per-project cache root from the host into /agent-home/.cache so Deno/trivy/etc. get cross-tick cache hits, and add an eviction job so it doesn't grow forever.

Mount

  • Host: ${project.path}/.git/fjx/cache/ (created lazily on first tick)
  • Container: /agent-home/.cache
  • Mode: read-write, owned by the host uid (already matches --user)

Per-project (not per-host) so different projects don't pollute each other, and so removing a project's repo removes its cache too. Strict-serial scheduling means no concurrent-write contention within a project.

Eviction

  • New verb: fjx cache prune [--max-age ] [--max-size ] [--dry-run]
  • Default policy: ??? (suggest: max-age 30d for non-DB files, max-size 5 GiB total, evict oldest-atime first).
  • Hook it in two places:
    • Manual: just cache-prune recipe wrapping the verb.
    • Automatic: supervisor invokes it once per N ticks, or on a wall-clock cadence (??? — once/day is probably enough). Eviction must not run while a tick is in flight; piggyback on the strict-serial scheduler.
  • Surface the last prune's freed-bytes in supervisor telemetry so operators can see it's working.

Acceptance criteria

  • Second consecutive agent-dev tick on the same project skips the trivy DB download (visible in container logs) and reuses the Deno module cache.
  • fjx cache prune --dry-run lists what would be removed without removing it.
  • fjx cache prune enforces the configured max-age and max-size and emits a structured event the supervisor records.
  • Automatic prune runs without interleaving with an in-flight tick.
  • Removing a project's repo removes its cache (no host state leaks beyond the worktree).
  • .git/fjx/cache/ is invisible to rg, fd, and the IDE indexers we care about (true today because .git/ is universally ignored — just verify).

Out of scope

  • Sharing caches across projects (no shared host cache root).
  • Cache warming / pre-population.
  • Telemetry beyond a single freed-bytes counter per prune.

Open questions

  • Default eviction policy values (max-age, max-size) — pick after one week of real usage data.
  • Should claude / codex model caches also be moved under /agent-home/.cache? They currently sit in the bind-mounted ~/.claude / ~/.codex and behave fine, so probably leave them alone.
  • Whether the prune verb belongs under fjx ops cache prune or top-level fjx cache prune (??? — follow whatever pattern the existing fjx ops surface uses).
The fjx supervisor's dockerSpawnFn (src/supervise.ts:884) shares nothing writable with the host besides ~/.claude{,.json}, ~/.codex, and the project worktree. Every tick re-derives the Deno module cache, the trivy vuln DB (~600 MB), and any other tool caches from scratch in the container's overlay layer. This is slow (cold just validate does a lot of redundant download work) and wasteful of host Docker disk on each tick. A prior fix made /agent-home writable (images/fjx-base/Dockerfile); that unblocks ~/.cache writes but doesn't persist them. ### Proposal Bind-mount a per-project cache root from the host into /agent-home/.cache so Deno/trivy/etc. get cross-tick cache hits, and add an eviction job so it doesn't grow forever. ### Mount - Host: ${project.path}/.git/fjx/cache/ (created lazily on first tick) - Container: /agent-home/.cache - Mode: read-write, owned by the host uid (already matches --user) Per-project (not per-host) so different projects don't pollute each other, and so removing a project's repo removes its cache too. Strict-serial scheduling means no concurrent-write contention within a project. ### Eviction - New verb: fjx cache prune [--max-age <duration>] [--max-size <bytes>] [--dry-run] - Default policy: ??? (suggest: max-age 30d for non-DB files, max-size 5 GiB total, evict oldest-atime first). - Hook it in two places: - Manual: just cache-prune recipe wrapping the verb. - Automatic: supervisor invokes it once per N ticks, or on a wall-clock cadence (??? — once/day is probably enough). Eviction must not run while a tick is in flight; piggyback on the strict-serial scheduler. - Surface the last prune's freed-bytes in supervisor telemetry so operators can see it's working. ### Acceptance criteria - Second consecutive agent-dev tick on the same project skips the trivy DB download (visible in container logs) and reuses the Deno module cache. - fjx cache prune --dry-run lists what would be removed without removing it. - fjx cache prune enforces the configured max-age and max-size and emits a structured event the supervisor records. - Automatic prune runs without interleaving with an in-flight tick. - Removing a project's repo removes its cache (no host state leaks beyond the worktree). - .git/fjx/cache/ is invisible to rg, fd, and the IDE indexers we care about (true today because .git/ is universally ignored — just verify). ### Out of scope - Sharing caches across projects (no shared host cache root). - Cache warming / pre-population. - Telemetry beyond a single freed-bytes counter per prune. ### Open questions - Default eviction policy values (max-age, max-size) — pick after one week of real usage data. - Should claude / codex model caches also be moved under /agent-home/.cache? They currently sit in the bind-mounted ~/.claude / ~/.codex and behave fine, so probably leave them alone. - Whether the prune verb belongs under fjx ops cache prune or top-level fjx cache prune (??? — follow whatever pattern the existing fjx ops surface uses).
Sign in to join this conversation.
No description provided.