[Feature] 沙箱跨 Run 依赖缓存:按项目+lockfile 内容寻址复用 npm/Go/pip 等依赖目录,支持 setup/maintenance 两段式准备

Đang mở
#329 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
12/100
Loại issue
Tính năng
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
docker, go
Lĩnh vực
backend, infrastructure

Hướng nghiên cứu

Start with internal/runtime/sandbox_manager.go (RunWorkspace, DestroyRun), internal/runtime/sandbox_docker.go (ensureStarted, the workspace mount) and internal/runtime/sandbox_environment.go (runEnvironmentInstall, .install-stamp) to see how per-run state is created and removed. This is cross-cutting design work touching the domain model, both sandbox drivers and the remote runner, so it is not a newcomer task. Done means the acceptance checklist in the issue is met, including the cache-isolation and concurrent-fill tests.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

背景与借鉴来源

AI 编码 Agent 每次开工都要先装依赖(npm install、go mod download、pip install……)。在大仓库上这一步往往要几分钟,而且每次下载的内容基本一样。主流平台都把“装依赖”和“依赖缓存”做成了平台能力:

项目 规模 / 活跃度 可借鉴做法
openai/codex(Codex Cloud) ~13.5 万 star,2026-11-11 发布 rust-v0.167.2 环境分为 setup script 和 maintenance script:setup 跑完后容器状态会被缓存(官方文档写最多 12 小时),之后的任务直接用缓存容器,只跑轻量的 maintenance script 刷新依赖;setup 脚本或环境变量改了,缓存就失效。用户也很在意这一点,例如 openai/codex#25086 就是在反馈缓存没生效、每次都重装
GitHub Copilot coding agent(文档) GitHub 官方产品 在 .github/workflows/copilot-setup-steps.yml 里声明环境准备步骤,示例直接用 actions/cache(~5.6k star)缓存依赖目录,key 取 lockfile 的 hash
OpenHands/OpenHands ~9.3 万 star,每天都有提交 仓库根目录放 .openhands/setup.sh,每次开始工作都会执行,用来装依赖(文档)
e2b-dev/E2B ~1.5 万 star,每天都有提交 沙箱模板把装好依赖的状态预先做成快照,从模板启动新沙箱是秒级

它们的共同点是:按 lockfile / 脚本内容算出缓存 key,跨任务复用依赖目录或环境快照,后续任务只做增量刷新。

LoopForge 现状与不足

(以 commit a7c6f15 为准)

  1. 每个 Run 一个工作区,依赖缓存全落在这个工作区里,Run 结束就跟着删掉
    • internal/runtime/sandbox_manager.go 的 RunWorkspace(runID) 给每个 Run 单独分配目录 root/<runID>;DestroyRun 在 Run 结束后整个删掉(internal/engine/run_environment.go:321 调用)。
    • Docker 沙箱只挂载了一个工作区(internal/runtime/sandbox_docker.go:641:-v workspace:/workspace)。Agent 的 HOME 设成 /workspace/.agentflow/private-home/<provider>(internal/runtime/command_binding.go:59),所以 ~/.npm、~/go/pkg/mod、~/.cache/pip 这些缓存也都写在本次 Run 的工作区里。
    • DiscardRunContainer 的注释写着 rerun 时“不需要上次填进去的 installs and caches”。也就是说,重试时依赖缓存同样被扔掉。
  2. 只缓存了镜像层,项目依赖没有缓存:ensureDockerImage / cachedDockerImageName(sandbox_docker.go:766、:481)按 Dockerfile 内容的 hash 复用镜像,removeDockerSandbox 也特意保留了缓存镜像。可项目自身的依赖(由 lockfile 决定)通常不会写进平台级 Dockerfile,每个 Run 都要从网络重新拉。
  3. 现有的“准备”机制只在单个工作区内去重
    • 环境资产的 EnvironmentDefinition.InstallScript(internal/domain/assets.go:142)由 runEnvironmentInstall(internal/runtime/sandbox_environment.go:154)执行。它用 .install-stamp 记录脚本 hash,但 stamp 放在 .agentflow/environment/<id>/,也在本次 Run 的工作区里,换一个 Run 就要重装。
    • 脚本节点的 RunOnWorkspaceRebuild + EnsureWorkspacePreparation(internal/runtime/workspace_preparation.go:268)用 preparationFingerprint 记录已执行的步骤,同样只作用于本工作区和本沙箱,无法跨 Run 复用产物。
  4. 已有 issue 都不涉及这件事:#218 缓存的是“纯产物节点的执行结果”,不是依赖目录;#124/#150 改进的是克隆和构建镜像的进度与超时提示;#117 讲沙箱调度解耦。

结果是:同一个项目连续跑十个 SOP,就要完整装十次依赖。既浪费时间和 Token(Agent 会等依赖、处理安装报错),又反复占用外网带宽或内部镜像源。

建议方案

做一个按项目隔离、按内容寻址的跨 Run 依赖缓存卷,配合 setup / maintenance 两段式准备脚本:

  1. 缓存声明(加在环境资产或沙箱模板上)
    • 给 EnvironmentDefinition(或 SandboxProfile)加一个 Caches []CacheSpec 字段,每项包含:
      • id
      • paths:容器内路径,例如 /workspace/.agentflow/private-home/*/.npm,或者更简单的做法:平台通过环境变量把 npm_config_cache、GOMODCACHE、PIP_CACHE_DIR、CARGO_HOME 等统一指向 /cache/<id>
      • keyFiles:用来算 key 的文件,例如 package-lock.json、go.sum、requirements*.txt
      • restoreKeys:前缀回退,借鉴 actions/cache
    • 内置 npm/pnpm/yarn、Go、pip/uv、Maven/Gradle、Cargo 的预设模板,用户勾选即可。
  2. 挂载与隔离
    • Docker:创建容器时(sandbox_docker.go 的 ensureStarted)对每个缓存额外挂 --mount type=volume,src=lf-cache-<projectID>-<cacheID>-<keyhash>,dst=/cache/<id>。
    • native/bwrap:--bind <cacheRoot>/<project>/<id>/<keyhash>。
    • 卷只在同一项目内共享,不同项目和不同用户互不可见。对 secret 敏感的目录(如 .npmrc、凭据文件)不得放进缓存,可复用 gitwork/exclude.go 中 IsCredentialFile 的判断。
    • 远程 Runner 在本机维护同样结构的缓存目录,任务载荷(RunnerSandboxSpec)里只带缓存声明,不带缓存内容。
  3. 两段式准备
    • 现有 InstallScript 作为 setup 阶段:缓存未命中时执行,执行完把 stamp 写进缓存卷而不是工作区,这样跨 Run 也认得出来。
    • 新增可选的 MaintenanceScript:缓存命中时只跑它(例如 npm ci --prefer-offline、go mod download)。
    • setup 脚本内容、keyFiles hash、镜像 hash 中任意一项变化,缓存就失效。
  4. 并发与回收
    • 同一缓存 key 首次填充时加文件锁,或者先写临时卷再原子改名,避免两个 Run 同时写坏缓存。
    • 后台定期按 LRU 和总容量上限清理(可放进 ReclaimCachedDockerImages 所在的回收循环)。
    • 项目设置页提供“清空缓存”按钮。
  5. 可观测
    • workspace.preparation.* 遥测事件加上 cacheHit/cacheMiss/cacheKey/restoreKey/准备耗时。
    • Run 详情页的“工作区准备”面板显示命中情况和节省的时间。

验收标准

  • 环境资产(或沙箱模板)可以声明缓存项,并提供至少 npm、Go、pip 三种预设;API 和前端都能编辑和校验。
  • 同一项目、lockfile 不变时,第二个 Run 命中缓存:只执行 maintenance 脚本(或什么都不执行),准备耗时较首次明显下降。附 Go + Node 示例仓库的前后耗时对比。
  • 修改 lockfile 或 setup 脚本后 key 变化,按 restoreKeys 回退到最近的缓存再做增量安装。
  • 不同项目之间的缓存互不可见(有测试覆盖),凭据类文件不会写进缓存。
  • 两个 Run 并发填充同一个 key 时不损坏缓存(有并发测试)。
  • 缓存总量超过上限时按 LRU 清理;项目管理员可以手动清空。
  • Docker 驱动和 native 驱动都支持;远程 Runner 在本机缓存,任务载荷不含缓存内容。
  • 准备阶段的遥测和 Run 详情页能看到 hit/miss、key 和耗时。
  • 不声明缓存时行为与现在完全一致(回归测试通过)。
Ngôn ngữ chính
Go
Star
141
Fork
28
Merge trung bình
1 ngày 15 giờ
Pull request đã merge (30 ngày)
113

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của Tencent/LoopForge

Tất cả issue của Tencent/LoopForge

Issue tương tự

Thêm issue về Go

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.