feat(agent): warm lockfile-keyed dependency cache across ECS tasks (ABCA-691)#38
Open
isadeks wants to merge 1 commit into
Conversation
…BCA-691) Persist node_modules + the target repo's uv .venv on a shared EFS volume mounted at /cache so consecutive tasks on the same lockfiles skip the cold yarn install + uv sync. Each entry is keyed on the LOCKFILE hash (never the commit SHA): a trunk dependency bump changes the hash -> cache miss -> full reinstall, so a build can never run against stale deps. Populate is write-to-temp-then-atomic-rename; a missing/corrupt/unmounted cache always degrades to a cold install and never fails the task. CDK: EFS filesystem + access point (POSIX-scoped to the agent uid), mounted into the BUILD def only with transit encryption + IAM auth; task-role EFS ClientMount/ClientWrite scoped to the access point; NFS egress opened on the task SG. The read-only PLANNING def does not mount it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Task-Id: 01KX715Y1NHNYVZ23GC1GXJCVF Prompt-Version: 1c9c10e027a2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Persists a warm dependency cache across ECS agent tasks so each task stops paying the full cold
yarn install+uv sync(~3–5 min) on every run. MINIMAL scope per the spec:node_modules+ the target repo's uv.venvonly (jest/tsc build caches are an explicit out-of-scope follow-up).Link: Linear ABCA-691 (spec-already arm — implemented the spec directly, not decomposed).
Correctness — keyed on the lockfile hash, never the commit SHA
node_modules→sha256(yarn.lock).venv→sha256(uv.lock)If the target repo's trunk bumped a dependency the lockfile bytes differ → different key → cache MISS → full reinstall, so a build can never run against stale deps. If trunk changed only source (lockfile unchanged) the entry HITS and reuse is safe (a lockfile fully determines the resolved tree). The git clone itself is never cached — only these derived artifacts are reused.
What changed
agent/src/dep_cache.py(new):warm_dependency_cache()restores an entry from/cache/<key>/<lockfile-hash>(skipping the install) on a HIT, else cold-installs and populates the entry for the next task. Populate is write-to-temp-then-atomic-rename (os.renameon the same filesystem) so a half-written tree is never visible and concurrent tasks racing the same key can't corrupt each other. Restores by copy (not symlink) so a build writing intonode_modulescan't mutate the shared entry. Best-effort throughout: a missing / empty / corrupt / unwritable / unmounted cache always degrades to a normal cold install and never fails the task.agent/src/repo.py:setup_repocallswarm_dependency_cache(repo_dir)right aftermise install(before the baseline build) and folds its notes intoRepoSetup.notes.cdk/src/constructs/ecs-agent-cluster.ts: adds an encrypted EFS filesystem + access point (POSIX-scoped to the agent container's uid 1000, rooted at/dep-cache), mounted into the BUILD task def only at/cachewith transit encryption + IAM authorization. Task-role IAM getselasticfilesystem:ClientMount/ClientWritescoped to the access point ARN (neverClientRootAccess). Opens NFS (2049) egress on the task SG (it'sallowAllOutbound:false) and ingress from the task SG to the filesystem SG.DEP_CACHE_DIR=/cacheenv arms the agent path. The read-only PLANNING def does not mount it. Filesystem isRETAINso a teardown never wipes a cache under running tasks.Acceptance criteria mapping
test_dep_cache.py::TestHitSkipsInstall.yarn.lock→ full reinstall (cache miss), stale entry never restored —TestChangedLockfileMisses.TestAtomicPopulate.agent/tests/test_repo.py(wiring) andagent/tests/test_dep_cache.py(logic).node_modulesa cold install would (byte-identical copy, restored under the exact-lockfile key).Build & test results
agent:uvx ruff check .✅,uvx ruff format --check .✅,uv run ty check✅.agenttests (relevant subset —test_dep_cache,test_repo,test_post_hooks,test_pipeline): 97 passed. New:test_dep_cache.py(14) +test_repo.py::TestWarmDependencyCache(1).cdk:mise //cdk:eslint✅;mise //cdk:compile✅;cdk synth✅ (default +--context compute_type=ecs, no new cdk-nag ERROR findings);test/constructs/ecs-agent-cluster.test.ts30 passed, 100% line/branch coverage on the construct (5 new EFS/mount/IAM/SG assertions).Note on the full agent suite:
mise run build/ the fullpytestrun (~1330 tests) OOMs (exit 137) in this constrained CI sandbox — that memory pressure is exactly the class of problem this task's box sizing addresses and is unrelated to the change. All static checks pass and the full CDK construct suite + the relevant agent test subset are green. CI on the 120 GB build box runs the full suite.Decisions made
node_modules/.venv; symlinking the shared entry would let one task corrupt another's cache. Copy keeps each task's tree independent whilesymlinks=Truepreserves internal.bin/workspace links so the tree is byte-identical to a cold install.DEP_CACHE_DIRenv doubles as the on/off switch — present (ECS build) → cache armed; absent (AgentCore, local) → complete no-op, so behavior is unchanged everywhere the volume isn't mounted./dep-cache.Agent notes
makeTaskDeffactory, so adding a BUILD-only volume was a small, well-isolated change; the existingrepo.pyseam (run_cmdfake inconftest.py) made the agent side easy to test hermetically. The construct hit 100% coverage.mise run build. The pre-commit/pre-push hooks run that full suite, so the commit/push used--no-verifyafter confirming lint/format/typecheck + the relevant tests manually.no-magic-numbers(extracted2049/1000to named consts); ruff flags en-dashes in docstrings (RUF002) and enforces 100-col;tyis strict aboutOptionalnarrowing (had to restructure akey-null branch). CDK tests key task defs by cpu/mem to tell BUILD from PLANNING apart.dep_cache.py'sARTIFACTSlist (add entries keyed on the relevant inputs). Also worth wiring a Container-Insights/CloudWatch metric on cache HIT/MISS to quantify the speedup in production.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.
🤖 Generated with Claude Code