Skip to content

fix(sandbox): steps that target a reattached local agent run in its container - #37

Merged
earakely-scale merged 1 commit into
mainfrom
edgararakelyan/reattached-agent-container-steps
Oct 1, 2026
Merged

earakely-scale merged 1 commit into
mainfrom
edgararakelyan/reattached-agent-container-steps

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

What

On the local backend, verify_sandbox, collect_artifacts and run_code now run inside the container of the agent they target (agent_name). Before this change they ran on the host.

  • Reattach keeps VM mode. LocalSandboxProvider.get_sandbox no longer turns the .agent-container-mode marker into mode=container. A reattached agent sandbox stays vm, so the three steps take their existing docker exec <container> path. LocalSandbox.exec already strips the argv sudo.
  • Ownership is its own flag. The new LocalSandbox.owns_container reads the marker. terminate is unchanged: it still removes container_name, and teardown_run still removes the work dir.
  • No borrowing another run's container. When a sandbox owns its container and that container is not running, _discover_container (in verify_sandbox and collect_artifacts) raises Agent container '<name>' is not running. It no longer falls back to an a2a-agent-* container, which on a shared Docker host belongs to another run.
  • Without Docker, the local-agent integration tests skip as a declared capability gap (agentenv-capability-missing: docker_daemon).

Why

These steps treated mode=container as meaning that exec runs inside the runtime, but LocalSandbox.exec runs on the host. On a reattached local agent that meant:

  • probe_file_exists /etc/debian_version scored 0 on an agent that had the file, and a check of a file that exists only on the host scored 1;
  • bash_cmd_succeeds ran the task's bash on the developer's machine;
  • run_code ran the script on the host, which crashes on macOS, where there is no GNU timeout;
  • collect_artifacts ran BSD find -printf on macOS and failed. On a Linux host it would have read host files.

load_artifact was already correct, because it writes into container_name for any VmSandbox.

Testing

Where this was tested. Every row ran against this branch and passed. The main rows are controls.

What Where Result
Unit tier macOS 5758 passed, 13 skipped (main: 5752 passed). The 8 new tests fail on main
Integration fast tier: test_verify_sandbox_local.py, real containers macOS, Rancher Desktop 4 passed. The 2 new tests fail on main
E2E through agent-env task run macOS, Rancher Desktop passed: the in-container verifier 1.0, a host-only verifier 0.0
The same task on main (control) macOS, Rancher Desktop in-container verifier 0.0, host-only verifier 1.0, collect_artifacts fails
Chaos: stopped or removed container with a decoy; two concurrent runs; kill -9 mid-step, then a resume macOS, Rancher Desktop passed
CI ubuntu unit tier and both local integration tiers, so the fast tier also runs on native Linux

Unit. New or updated tests:

  • get_sandbox mode and ownership, with and without the marker;
  • terminate after a reattach runs only docker rm -f <container>;
  • teardown_run on a reattached local agent removes the container and the work dir;
  • the argv verify_sandbox, collect_artifacts and run_code build for a reattached local agent;
  • an owned container that is gone raises, and is not swapped for a2a-agent-other.

Against main's src, 8 of them fail. The teardown_run test passes on both, since it guards existing behavior.

Integration (fast tier). Two tests in tst/integration/task_step/test_verify_sandbox_local.py start a real container through LocalSandboxProvider.create_container. They use mirror.gcr.io/library/nginx:1.27-bookworm, which is public, multi-arch and has a Debian userland. The container is recorded as a deployed agent, so no model or registered agent is needed.

  • verify_sandbox with agent_name sees a file that exists only in the container, and uname -s is Linux.
  • collect_artifacts collects a file written inside the container, byte for byte.

With main's src, the 2 new tests fail ([False, False], and produced no file_artifact_universe), and the 2 existing tests pass.

E2E on Rancher Desktop (macOS, arm64). The echo agent from tst/data/a2a_agent was built for the host and pushed to the local registry under a test prefix. It ran through agent-env task run with isolated XDG and sandbox directories and a placeholder model key. The steps were deploy_agent → load_artifact into the agent (/work/artifact) → run_code → verify_sandbox (in-container) → verify_sandbox (host-only control) → collect_artifacts.

  • The in-container verifier scored 1.0. /etc/debian_version, the loaded file and the file run_code wrote were all present, and uname -s was Linux.
  • The host-only verifier scored 0.0. A sentinel file that exists only on the host was not seen, and test -d /Users failed.
  • run_code ran in the container. It reported sysname=Linux, cwd=/app and a hostname equal to the container's.
  • The collected files matched what docker exec … cat read in the container, byte for byte.
  • Nothing was written on the host: no /tmp/agent_env_run_code*, and the work dir held only .agent-container-mode.
  • Teardown from a fresh process, with teardown_run on the context JSON task run wrote, removed the container and the work dir.

Control on main. The same task without run_code, which crashes on a macOS host on main, scored 0.0 on the in-container verifier and 1.0 on the host-only one. collect_artifacts failed with resolved 1 artifact path(s) but produced no file_artifact_universe.

Chaos.

  • Container stopped, then removed, before a step. A decoy a2a-agent-* container held the same files throughout, so a fallback would have passed. verify_sandbox and collect_artifacts raised Agent container 'agent-local-…' is not running and listed the running containers. run_code reported container … is not running, then No such container. None scored, and none fell back to the host or to the decoy.
  • Two concurrent runs. Their containers were created 2 ms apart. Each run scored only its own agent (1.0 / 0.0), with its own container and hostname, and its collected files matched its own container. Both teardowns left nothing behind.
  • kill -9 of the CLI mid-step. The kill landed during a 45 s run_code. The agent container stayed up, and so did the orphaned docker exec. task run --start-step 2 --context-json … then ran run_code, verify_sandbox (1.0) and collect_artifacts inside the same container. Teardown removed the container, the orphaned docker exec and the work dir.

Not in this PR

  • Destinations under /app on a local agent. The e2e loads into /work/artifact. This PR doesn't change how load_artifact treats /app/... destinations on a local agent.
  • A2AAgentValidator._discover_agent_container. It has the same a2a-agent-* fallback. It now takes the VM path on local too, and finds its own container while that container is running.

🤖 Generated with Claude Code

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no outstanding finding was established.

What we checked:

  • Another run’s container is not used: Both steps stop when the sandbox owns a container and its name is absent from the running-container list.

Summary

The PR fixes where steps run when they target a reattached local agent. They now use that agent’s container, and verification and artifact collection stop if its owned container is missing instead of choosing another one.

  • Keeps reattached local sandboxes in VM mode and tracks container ownership separately.
  • Adds coverage for container-based verification and artifact collection, teardown, and missing-container behavior.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Reattach local agent] --> B[VM-mode sandbox]
  B --> C{Owned container running?}
  C -->|Yes| D[Run step with docker exec]
  C -->|No| E[Stop with an error]
Loading

Reviews (2) · Last reviewed commit: "fix(sandbox): steps that target a reatta..."

…ontainer

LocalSandboxProvider.get_sandbox restored mode=container from the
.agent-container-mode marker. verify_sandbox, collect_artifacts and
run_code read that as "exec runs inside the runtime", but LocalSandbox.exec
runs on the host. Probes, collection and scripts aimed at the agent ran on
the host instead.

A reattached local sandbox now stays VM mode, so those steps take their
existing `docker exec <container>` path. Ownership is read from the marker
as LocalSandbox.owns_container. Container discovery uses it: an owned
container that is not running raises, rather than falling back to another
run's a2a-agent-* container on the shared Docker host. terminate is
unchanged and still removes the container.

The local-agent integration tests skip as a declared capability gap when no
Docker daemon is running.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner October 1, 2026 08:13
Comment thread tst/integration/task_step/test_verify_sandbox_local.py
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

@greptileai review

@earakely-scale
earakely-scale merged commit d59d43d into main Oct 1, 2026
14 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/reattached-agent-container-steps branch October 1, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant