Dependency Cache Warming for AI Coding Agents Without Cold Install Thrash
21 Jun 2026 · 7 min read
- AI Coding Agents
- Build Systems
- CI Reliability
- Dependency Caching
- Developer Workflow
AI coding agents are surprisingly bad at one very ordinary thing, waiting through the same dependency bootstrap over and over.
If half of every run is pnpm install, uv sync, or rebuilding the same Docker layers, the model is not the bottleneck anymore. Your packaging and cache policy is.
What matters is not just having a cache somewhere. What matters is warming the right cache, keyed to the right evidence, before the agent starts editing.
Why this matters
A human developer tolerates one slow install and keeps working in the same shell for hours. Agent workflows do the opposite. They spin up fresh workspaces, bounce across repos, retry in CI, and often verify patches in isolation.
That changes the economics. A 90 second bootstrap penalty is annoying for a person. For an agent that runs 40 times a day, it quietly becomes the dominant cost in latency, CI minutes, and flaky failed runs.
This is especially true when you mix:
- ephemeral worktrees or sandboxes
- language-specific stores like
pnpm,uv,pip, orcargo - Docker-based verifiers
- cache-hostile keys such as branch names or timestamps
Architecture and workflow overview
Mermaid flow
flowchart LR
A[Agent task starts] --> B[Read lockfile + runtime versions]
B --> C[Compute cache fingerprint]
C --> D{Warm cache hit?}
D -->|yes| E[Hydrate workspace fast]
D -->|no| F[Prime shared store once]
F --> G[Persist cache artifacts]
G --> E
E --> H[Run focused tests or verifier]
H --> I[Publish timings and cache status]
The key design choice is that the cache belongs to the dependency graph, not to a branch or an agent session. If the lockfile and runtime match, the agent should inherit a warm store immediately.
Implementation details
1. Key caches from dependency evidence, not from branch names
For JavaScript repos, I like keying the warm store from:
pnpm-lock.yamlorpackage-lock.json- package manager version
- Node version
- OS or architecture when native modules are involved
A GitHub Actions example:
name: agent-verify
on:
workflow_dispatch:
pull_request:
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Enable pnpm
run: corepack enable
- name: Resolve pnpm store path
id: pnpm-store
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
- uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: pnpm-${{ runner.os }}-node22-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
pnpm-${{ runner.os }}-node22-
- run: pnpm install --frozen-lockfile
- run: pnpm test -- --runInBand
This is boring, which is exactly why it works. The agent gets a warm package store when dependency evidence matches, and a clean miss when it does not.
Reference docs:
2. Warm the shared store, not just node_modules
Caching node_modules looks tempting, but it is usually the wrong layer for agent systems. It is larger, more OS-sensitive, and more likely to break when path layouts or symlinks change.
A better pattern is warming the package-manager store and then doing a fast materialization step inside each fresh workspace.
For local or self-hosted agent runners, a bootstrap script can make this explicit:
#!/usr/bin/env bash
set -euo pipefail
LOCK_HASH=$(sha256sum pnpm-lock.yaml | awk '{print $1}')
STORE_ROOT="${HOME}/.cache/agent-pnpm"
MARKER="${STORE_ROOT}/${LOCK_HASH}.ready"
corepack enable
mkdir -p "$STORE_ROOT"
pnpm config set store-dir "$STORE_ROOT"
if [[ ! -f "$MARKER" ]]; then
echo "[warm] priming pnpm store for ${LOCK_HASH}"
pnpm fetch --frozen-lockfile
touch "$MARKER"
else
echo "[warm] store already primed for ${LOCK_HASH}"
fi
pnpm install --frozen-lockfile --prefer-offline
pnpm fetch is useful here because it populates the content-addressed store without pretending the workspace is already linked. The agent still gets a clean workspace, but the expensive network step is mostly gone.
Reference docs:
3. Give Python agent runs the same treatment with uv
Python workflows suffer from the same problem, especially when every verification job recreates a virtual environment from scratch.
A simple pattern is:
- key from
uv.lockplus Python version - cache the
uvdownload and wheel cache - rebuild the virtualenv from warm artifacts instead of hitting the network
Reference docs:
4. Warm Docker verifier layers with BuildKit cache mounts
If your agent verifies patches inside Docker, language package caches are only half the story. You also want BuildKit cache mounts so repeated builds reuse downloaded packages across runs.
# syntax=docker/dockerfile:1.7
FROM node:22-bookworm AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable
RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
pnpm fetch --frozen-lockfile
FROM node:22-bookworm AS test
WORKDIR /app
COPY --from=deps /root/.local/share/pnpm/store /root/.local/share/pnpm/store
COPY . .
RUN corepack enable
RUN pnpm install --frozen-lockfile --prefer-offline
RUN pnpm test
This keeps dependency downloads attached to the Docker cache lifecycle instead of redoing them for every agent patch.
Reference docs:
- https://docs.docker.com/build/cache/optimize/
- https://docs.docker.com/reference/dockerfile/#run---mounttypecache
Terminal before vs after
cold run
pnpm fetch 52s
pnpm install 31s
focused tests 18s
total 101s
warm run
cache restore 3s
pnpm install 8s
focused tests 18s
total 29s
That kind of delta changes whether agent verification feels usable or wasteful.
Comparison table
| Stack | What to warm | Best key material | Common failure mode |
|---|---|---|---|
| pnpm | content-addressed store | lockfile, Node version, OS | keying from branch names, causing low reuse |
| uv | wheel and package cache | uv.lock, Python version, OS |
caching venv only, then rebuilding anyway |
| Docker BuildKit | build cache mounts and layers | Dockerfile plus lockfiles | invalidating all layers with early COPY . . |
| pip | wheel/download cache | requirements lock plus Python version | cache poisoning from unpinned transitive deps |
What went wrong and tradeoffs
Failure mode 1: overly broad restore keys
Broad restore keys improve hit rate, but they can also hide drift. If native modules are involved, restoring a cache from the wrong Node minor or architecture creates slow, confusing failures.
What I would not do: use a single global key like node-cache-linux for all repos.
Failure mode 2: caching mutable environments instead of immutable inputs
Teams often cache whole virtualenvs or node_modules directories, then wonder why runs become flaky after a package manager upgrade. Those directories are downstream artifacts. The lockfile and runtime version are the real invariants.
Failure mode 3: success metrics that ignore bootstrap time
If you only measure test pass rate, cache regressions stay invisible. Agent systems need bootstrap telemetry too:
- cache hit or miss
- restore duration
- install duration
- bytes downloaded
- verifier duration after bootstrap
Security concern: caches are shared attack surface
A shared dependency cache is useful, but it is also a place where poisoned or stale artifacts can linger. At minimum, pair cache warming with:
- frozen lockfiles
- registry integrity checks
- scoped write permissions on self-hosted runners
- cache eviction when package manager versions change sharply
For public CI, I am wary of workflows that let untrusted forks write back into privileged cache namespaces.
Practical checklist
- Key caches from lockfiles plus runtime version
- Prefer warming the package store over caching full install directories
- Separate network fetch from workspace materialization when possible
- Add BuildKit cache mounts for Docker-based verifiers
- Record cache hit rate and bootstrap duration in job output
- Invalidate aggressively when native modules or package manager versions change
- Treat shared caches as mutable infrastructure that needs access control
Best-practices callout
If your agent workflow uses fresh workspaces, optimize for fast reconstruction, not for preserving one magical workspace forever. Warm stores plus deterministic installs scale better than trying to keep giant mutable environments alive.
Conclusion
When an AI coding agent feels slow, the model often gets blamed first. A lot of the time the real culprit is repetitive bootstrap work.
Warm the dependency graph once, key it carefully, and let every fresh agent run inherit that speedup. It is one of the least glamorous reliability fixes you can make, and one of the highest leverage ones.