Skip to content

Cache agent setup in an image, behind --cache-agent-setup (experiment) - #42

Closed
rzonyx wants to merge 7 commits into
masterfrom
agent-setup-image-cache
Closed

rzonyx wants to merge 7 commits into
masterfrom
agent-setup-image-cache

Conversation

@rzonyx

@rzonyx rzonyx commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Opt-in (--cache-agent-setup): the first trial commits its main container after agent setup to hbench-setup-cache:<hash>; later trials start from it when the main image (a built image's name, else the pulled image's id), the environment dir, agent, model, options and hbench version match. Only for agents whose setup writes no credentials or host files (claude-code, codex, deepseek-harness, opencode), only when the container sets no env and has no mounts, egress control or own compose, and never with --force-build. Nothing deletes the images automatically; hbench cache clean removes them. deepseek-harness skips its install when its pin is installed. Measured with --install-only: opencode on hello-world, agent setup 185–245 s → 10–12 s; deepseek-harness on adaptive-rejection-sampler, 116–155 s → 1 s. An experiment for review.

Agent setup installs the agent in every trial's container: about two
minutes for deepseek-harness on a Terminal-Bench image (apt, nvm, Node,
npm), and up to the 360-second timeout when apt or npm is slow.

With --cache-agent-setup (environment.cache_agent_setup), the first
trial commits its main container, as setup left it and before the agent
runs, to hbench-setup-cache:<hash>, and later trials with the same main
image, agent, model, options, MCP servers, agent env names, setup user
and hbench version start from that image. Setup still runs on it, so an
agent that writes per-task config writes it again; an agent whose pinned
version is already installed skips its install. deepseek-harness now
checks dsh --version against its pin before installing, as claude-code
already did.

Nothing of the trial reaches the image's config: the cache is used only
when the main container sets no env (task env, --ee, egress proxy), has
no mounts and no task or extra compose file, so docker commit keeps the
base image's env. The container's com.docker.compose.* labels are
blanked, or compose's down --rmi local would take the image for the
trial project's and remove it. It is not used for oracle and nop, an
agent from import_path, skills, a task with MCP servers, a user agent,
or version=latest. hbench cache clean removes the images.

On adaptive-rejection-sampler (terminal-bench@2.0.1) with
deepseek-harness --install-only: agent setup 116-155 s without the
image, 1 s from it.
@rzonyx
rzonyx marked this pull request as ready for review October 23, 2026 06:45
@rzonyx

rzonyx commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

@greptile-apps

greptile-apps Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 3/5

[Medium impact] The change affects opted-in Docker trials that meet the setup-cache conditions.

Fix task-file carryover and stale image reuse before merging.

Findings

  1. P1 Tasks inherit earlier files ▶
  2. P1 Cached images override requested builds ▶

Summary

Adds opt-in agent setup images through --cache-agent-setup.

  • Matching trials can start from an image saved after agent setup.
  • DeepSeek Harness skips installing its pinned version when it is already installed.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Cache enabled] --> B{Container passes guards?}
  B -->|No| C[Start normal task image]
  B -->|Yes| D{Setup image exists?}
  D -->|Yes| E[Use setup image and skip build]
  D -->|No| C
  C --> F[Upload task files]
  E --> F
  F --> G[Run agent setup]
  G --> H{Cache miss and eligible?}
  H -->|Yes| I[Commit container to setup image]
  H -->|No| J[Run trial]
  I --> J
Loading

Reviews (1) · Last reviewed commit: "Merge branch 'master' into agent-setup-i..." · Reviewed by Greptile

Comment thread src/env.rs Outdated
Comment on lines +736 to +745
let plain = egress.is_none()
&& ec.env.is_empty()
&& spec.persistent_env.is_empty()
&& spec.mounts.is_empty()
&& spec.extra_compose.is_empty()
&& !task_compose.exists();
let mut build_image = build_image;
let setup_cache = match (&spec.setup_cache_key, main.get("image").and_then(Value::as_str)) {
(Some(key), Some(base)) if plain => {
let image = setup_cache_image(key, base);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Tasks inherit earlier files

Tasks sharing a prebuilt image and agent can reuse a cache containing another task’s files. start() uploads env_dir before the container is committed, but the cache key includes neither those files nor their destination. The next task’s upload overwrites matching paths without removing unrelated files, so those files remain and can change the benchmark result.

Disable caching for this upload path, or include the uploaded files and destination in the key.

Comment thread src/env.rs Outdated
Comment on lines +745 to +751
let image = setup_cache_image(key, base);
let hit = docker_output(&["image", "inspect", &image], QUICK_COMMAND_TIMEOUT).await?.status.success();
if hit {
log.line(&format!("agent setup cache: starting from {image}"));
main.insert("image".into(), json!(image));
main.remove("build");
build_image = None;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Cached images override requested builds

A cache hit replaces the main image without checking its current contents and clears build_image even when force_build is set. If a user updates a prebuilt image under the same tag, trials still use the old setup image. Likewise, --force-build cannot rebuild a Dockerfile-backed image when its setup cache exists.

Bypass the setup cache for forced builds and tie reuse to the resolved base image, not just its name.

…r host files

devin writes its API key into credentials.toml and gemini-cli copies OAuth
credentials during setup; several agents upload host files whose contents
are not in the cache key. A committed image would keep those. Agents now opt
in with Agent::setup_is_cacheable: claude-code, codex, deepseek-harness and
opencode, whose setups only install pinned packages.
…r and the scrubbed files

The key held the main image's name, so a pulled tag that now points at
another image would still start from the old setup. A pulled image is now
keyed by its local id: before start when it is there, else the id the
container ran on, read at commit time. An image hbench builds keeps its
name, which hashes the build context; the trial removes it, so its id
changes with every build. Tasks that share an image also share a cache only
when their environment dirs (uploaded into the workdir for prebuilt images)
and the verifier-only files removed before setup match.
…agged ones

Concurrent trials that all miss the cache each commit the same tag, and each
commit untags the one before it. A trial now skips the commit when the image
is already there, and hbench cache clean also removes the untagged setup
cache images a race can still leave.
A cache hit drops the main image's build, so --force-build would not rebuild
a Dockerfile-backed image once its setup cache exists.
@hacktron-app

hacktron-app Bot commented Nov 5, 2026

Copy link
Copy Markdown

Hacktron Security Check - Skipped

Reason: Code Review is not active for this organization

Start a free trial (no credit card required), subscribe, or add a payment method from organization billing settings

Go to: https://app.hacktron.ai/nodeq/billing

@rzonyx rzonyx closed this Nov 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant