Skip to content

feat(evaluator-sdk): sandboxed containerized Fabric runner (AALGO-321)#604

Open
SandyChapman wants to merge 1 commit into
mainfrom
schapman/aalgo-321-containerized-fabric-runner
Open

feat(evaluator-sdk): sandboxed containerized Fabric runner (AALGO-321)#604
SandyChapman wants to merge 1 commit into
mainfrom
schapman/aalgo-321-containerized-fabric-runner

Conversation

@SandyChapman

@SandyChapman SandyChapman commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

What

Adds FabricContainerRuntime (AALGO-321): runs NeMo Fabric + a coding agent inside a per-task Docker sandbox and maps the result into the existing CandidateEvidence contract, so the workspace-file, run_verifier, and trajectory metrics score container trials with no metric changes.

Highlights

  • Provider-neutral sandbox seam (SandboxSpec/SandboxProvider/AsyncSandbox) with a Docker provider that shells out to the docker CLI. Context/artifacts cross the boundary by file transfer (docker cp), so a Kubernetes/agent-sandbox provider can slot in behind the same seam later. AsyncSandbox tears down only its own sandbox; the shared provider's lifetime is the batch owner's (run_tasks disposes it once).
  • Opaque build-if-missing image — content-addressed tag, no Dockerfile for callers to write. A remote registry is the intended cache behind the same ensure_fabric_image seam.
  • nemo-relay is now a hard dependency: the trajectory profile is built from Relay's own typed config (via the shared fabric/_common.py), so Relay owns its schema instead of a hand-maintained dict that silently drifts. Verified cp311-abi3 wheels exist for every platform the workspace locks.
  • Host/container dedup: the shared helpers (failed-trial builder, output extraction, evidence-subdir naming, trajectory telemetry) live in fabric/_common.py so the two runtimes cannot drift apart.

Scope

Exercises the runtime directly (hermes-sdk harness, validated live end-to-end); the plugin _resolve_target wiring is a follow-up. See examples/fabric_container/ for a runnable end-to-end.

Verification

Full agent_eval suite green (143 passed, 1 skipped — the gated live Docker test); ruff + pyright clean.

Summary by CodeRabbit

  • New Features
    • Added an end-to-end Fabric evaluation example that runs inside a Docker-based sandbox.
    • Introduced a provider-neutral sandbox interface for command execution and file transfer.
    • Added Docker-backed sandbox runtime and automatic, deterministic Fabric image provisioning.
    • Standardized evaluation trial outputs with consistent evidence and trace/telemetry artifacts.
  • Bug Fixes
    • Improved handling of failed/malformed results, non-success run states, and sandbox/exec timeouts.
  • Documentation
    • Added documentation for the sandbox workflow used by evaluator runtimes.
  • Tests
    • Added/extended unit and live smoke coverage for sandbox and Fabric runtimes.

@SandyChapman SandyChapman requested review from a team as code owners July 8, 2026 14:41
@github-actions github-actions Bot added the feat label Jul 8, 2026
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f1295220-dc3b-496f-8a89-9c4f194aac05

📥 Commits

Reviewing files that changed from the base of the PR and between 5382c71 and 0945799.

⛔ Files ignored due to path filters (9)
  • sdk/python/nemo-platform/pyproject.toml is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/fabric/_common.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/fabric/container_runtime.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/fabric/image.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/fabric/runtime.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/sandbox/api.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/sandbox/base.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/sandbox/providers/docker.py is excluded by !sdk/**
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • packages/nemo_evaluator_sdk/examples/fabric_container/run_e2e.py
  • packages/nemo_evaluator_sdk/pyproject.toml
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/_common.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/image.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/sandbox.Dockerfile
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/README.md
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/api.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/base.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/providers/docker.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_container_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_image.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_integration.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_api.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider_live.py
  • packages/nemo_platform/pyproject.toml
  • third_party/licenses.jsonl
  • third_party/osv-licenses.json
  • third_party/requirements-main.txt
  • tools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/license/overrides.yaml
✅ Files skipped from review due to trivial changes (3)
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/README.md
  • tools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/license/overrides.yaml
  • third_party/requirements-main.txt
🚧 Files skipped from review as they are similar to previous changes (19)
  • packages/nemo_evaluator_sdk/pyproject.toml
  • third_party/licenses.jsonl
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/sandbox.Dockerfile
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/_common.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_api.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_image.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/base.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/api.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider_live.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_integration.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/providers/docker.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py
  • packages/nemo_evaluator_sdk/examples/fabric_container/run_e2e.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/image.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_container_runtime.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py
  • third_party/osv-licenses.json

📝 Walkthrough

Walkthrough

Adds sandbox abstractions and a Docker provider, shared Fabric runtime helpers, Fabric image provisioning, a containerized Fabric runtime with an end-to-end example, and relay dependency/license metadata updates.

Changes

Fabric Container Runtime

Layer / File(s) Summary
Sandbox contract and facade
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/base.py, packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/api.py, packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/README.md, packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_api.py
Defines the sandbox types and async facade, with lifecycle and seeding tests.
Docker sandbox provider
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/providers/docker.py, packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider*.py
Implements Docker-backed sandbox create/exec/transfer/status/close behavior, with unit and live smoke tests.
Shared Fabric helpers and runtime refactor
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/_common.py, packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py, packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py, packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_integration.py
Adds shared output/failure/telemetry helpers and updates the existing Fabric runtime and tests to use them.
Fabric image provisioning
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/image.py, packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/sandbox.Dockerfile, packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_image.py
Implements content-addressed image builds and validates the sandbox image build path.
FabricContainerRuntime orchestration
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py, packages/nemo_evaluator_sdk/examples/fabric_container/run_e2e.py, packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_container_runtime.py
Runs Fabric tasks in sandboxed containers, seeds inputs, parses results, and reports trial output and evidence.
Relay dependency and metadata
packages/nemo_evaluator_sdk/pyproject.toml, packages/nemo_platform/pyproject.toml, third_party/licenses.jsonl, third_party/osv-licenses.json, third_party/requirements-main.txt, tools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/license/overrides.yaml
Adds nemo-relay to runtime and dependency metadata, plus matching license and advisory records.

Possibly related PRs

  • NVIDIA-NeMo/nemo-platform#564: Refactors Fabric trial mapping and trajectory capture into shared helpers that this PR extends into the container runtime.

Suggested labels: feat

Suggested reviewers: arpitsardhana

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly matches the main change: a sandboxed, containerized Fabric runner for the evaluator SDK.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch schapman/aalgo-321-containerized-fabric-runner

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/api.py (1)

120-127: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move import tempfile to module top-level.

Local import inside _write_file is unconventional; hoist to the top of the file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/api.py`
around lines 120 - 127, The `_write_file` helper currently does a local `import
tempfile`, which should be hoisted to the module top level for consistency. Move
the import alongside the other top-level imports in this sandbox API module and
remove the inline import from `_write_file`, keeping the rest of the function
behavior unchanged.
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/providers/docker.py (2)

92-121: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No handling for a missing docker binary.

_run only catches timeouts; if docker_bin isn't found, asyncio.create_subprocess_exec raises a raw FileNotFoundError that propagates uncaught through every operation (create, exec, status, close) with no actionable message.

🛡️ Proposed fix
-        proc = await asyncio.create_subprocess_exec(
-            *argv,
-            stdin=asyncio.subprocess.PIPE if stdin is not None else None,
-            stdout=asyncio.subprocess.PIPE,
-            stderr=asyncio.subprocess.PIPE,
-            start_new_session=True,
-        )
+        try:
+            proc = await asyncio.create_subprocess_exec(
+                *argv,
+                stdin=asyncio.subprocess.PIPE if stdin is not None else None,
+                stdout=asyncio.subprocess.PIPE,
+                stderr=asyncio.subprocess.PIPE,
+                start_new_session=True,
+            )
+        except FileNotFoundError as exc:
+            raise RuntimeError(f"docker binary not found: {argv[0]!r}") from exc
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/providers/docker.py`
around lines 92 - 121, The _run chokepoint in DockerProvider only handles
timeouts, so a missing docker binary still bubbles up as a raw FileNotFoundError
through create, exec, status, and close. Update _run to catch the
create_subprocess_exec failure for the docker executable and re-raise a clearer,
actionable error that names the missing binary and the argv/context, while
preserving the existing timeout handling and return semantics for successful
runs.

45-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused _KEEP_ALIVE_COMMAND constant duplicates create()'s hardcoded argv.

create() hardcodes ["sh", "-c", "exec sleep infinity"] directly instead of using this constant, so they can silently drift.

♻️ Consolidate
-argv += [spec.image, "sh", "-c", "exec sleep infinity"]
+argv += [spec.image, *shlex.split(_KEEP_ALIVE_COMMAND)]

Also applies to: 137-139

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/providers/docker.py`
at line 45, The keep-alive command is duplicated between the _KEEP_ALIVE_COMMAND
constant and Docker provider create(), which can drift over time. Update the
create() path in the Docker provider to use _KEEP_ALIVE_COMMAND instead of
hardcoding the argv, and keep all keep-alive command construction centralized so
the constant is the single source of truth.
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/README.md (1)

1-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Mixes explanation and reference content on one page.

Rationale ("Why an owned seam") and reference material (contract/file listing, test listing) sit on the same page. Per coding guidelines, each doc page should fit one Diataxis quadrant with cross-links used instead of mixing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/README.md`
around lines 1 - 45, The Sandbox README currently mixes explanatory rationale
with reference material, so split it to fit a single Diataxis quadrant. Refactor
the content in the Sandbox seam README so the overview and “Why an owned seam”
narrative stay separate from the contract, isolation note, roadmap, and tests;
move the reference-style file listings and test listings to a dedicated
reference page or appendix, and add cross-links from this page to the new
target. Keep the main README focused on the conceptual explanation of the
sandbox seam and use the existing section titles like “The contract” and “Tests”
to relocate or trim the reference details.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/image.py`:
- Around line 82-96: Add bounded timeouts to the Docker subprocess invocations
so they cannot hang indefinitely. In image_exists, wrap the subprocess.run call
with a short timeout and convert timeout expiry into FabricImageError with a
clear daemon-unreachable message; also apply a timeout to the docker build path
in the same fabric image runtime code so both the quick inspect check and build
step fail fast instead of blocking forever.
- Around line 72-158: The cached Fabric image tag only depends on the Dockerfile
and extras, so changes in the staged NeMo-Fabric source can be missed and a
stale image reused. Update fabric_image_tag() so its digest also incorporates
the relevant source contents from the staged fabric_repo (or the same build
inputs used by _stage_source()), and make ensure_fabric_image() compute the tag
after resolving the repo so local source edits produce a new tag and trigger a
rebuild without requiring force_build.

In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/sandbox.Dockerfile`:
- Around line 29-42: The runtime stage in sandbox.Dockerfile is still executing
as root because it never switches away from the default user. Update the runtime
setup after the existing COPY/RUN steps to create and use a dedicated non-root
user, and add a USER instruction so the final image runs sandboxed instead of as
root. Keep the change in the runtime stage near the existing ENV, WORKDIR, and
fabric version check so it is easy to locate alongside the image bootstrap
logic.

---

Nitpick comments:
In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/api.py`:
- Around line 120-127: The `_write_file` helper currently does a local `import
tempfile`, which should be hoisted to the module top level for consistency. Move
the import alongside the other top-level imports in this sandbox API module and
remove the inline import from `_write_file`, keeping the rest of the function
behavior unchanged.

In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/providers/docker.py`:
- Around line 92-121: The _run chokepoint in DockerProvider only handles
timeouts, so a missing docker binary still bubbles up as a raw FileNotFoundError
through create, exec, status, and close. Update _run to catch the
create_subprocess_exec failure for the docker executable and re-raise a clearer,
actionable error that names the missing binary and the argv/context, while
preserving the existing timeout handling and return semantics for successful
runs.
- Line 45: The keep-alive command is duplicated between the _KEEP_ALIVE_COMMAND
constant and Docker provider create(), which can drift over time. Update the
create() path in the Docker provider to use _KEEP_ALIVE_COMMAND instead of
hardcoding the argv, and keep all keep-alive command construction centralized so
the constant is the single source of truth.

In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/README.md`:
- Around line 1-45: The Sandbox README currently mixes explanatory rationale
with reference material, so split it to fit a single Diataxis quadrant. Refactor
the content in the Sandbox seam README so the overview and “Why an owned seam”
narrative stay separate from the contract, isolation note, roadmap, and tests;
move the reference-style file listings and test listings to a dedicated
reference page or appendix, and add cross-links from this page to the new
target. Keep the main README focused on the conceptual explanation of the
sandbox seam and use the existing section titles like “The contract” and “Tests”
to relocate or trim the reference details.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4923bf8f-c0ef-495e-8637-c7626bafd2fe

📥 Commits

Reviewing files that changed from the base of the PR and between f60de77 and 4ac0cf4.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • packages/nemo_evaluator_sdk/examples/fabric_container/run_e2e.py
  • packages/nemo_evaluator_sdk/pyproject.toml
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/_common.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/image.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/sandbox.Dockerfile
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/README.md
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/api.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/base.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/providers/docker.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_container_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_image.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_integration.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_api.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider_live.py

Comment on lines +72 to +158
def fabric_image_tag(*, repo: str = DEFAULT_FABRIC_IMAGE_REPO) -> str:
"""Content-addressed tag for the harness-agnostic Fabric image: ``<repo>:<digest(recipe + extras)>``.

Not keyed by harness: one Fabric install + the bundled adapters runs any built-in harness, so the
image is the same regardless of which harness a task's config selects.
"""
recipe = _DOCKERFILE.read_bytes() + _extras_arg().encode("utf-8")
return f"{repo}:{hashlib.sha256(recipe).hexdigest()[:12]}"


def image_exists(tag: str, *, docker_bin: str = "docker") -> bool:
"""Whether an image tag is present in the local Docker image store.

Raises :class:`FabricImageError` when the Docker daemon is unreachable, so a stopped/misconfigured
daemon surfaces as a clear error instead of masquerading as "image absent" and triggering a build
that then also fails confusingly.
"""
result = subprocess.run([docker_bin, "image", "inspect", tag], capture_output=True, check=False)
if result.returncode == 0:
return True
stderr = result.stderr.decode("utf-8", errors="replace")
if "cannot connect to the docker daemon" in stderr.lower():
raise FabricImageError(f"cannot reach the Docker daemon (is it running?): {stderr.strip()}")
logger.debug("Fabric image %s not present in local store", tag)
return False


def _resolve_fabric_repo(fabric_repo: str | Path | None) -> Path:
default = Path(os.environ.get(FABRIC_REPO_ENV, _DEFAULT_FABRIC_REPO))
repo = (Path(fabric_repo) if fabric_repo is not None else default).expanduser()
if not (repo / "pyproject.toml").is_file():
raise FabricImageError(
f"NeMo-Fabric source not found at {repo}. The Fabric image is built from source "
f"(no public wheel); set {FABRIC_REPO_ENV} or pass fabric_repo to point at a checkout."
)
return repo


def _stage_source(repo: Path, dest: Path) -> None:
"""Copy only the maturin build inputs from ``repo`` into ``dest`` (not the whole checkout)."""
dest.mkdir(parents=True, exist_ok=True)
for name in _BUILD_SOURCE_PATHS:
src = repo / name
if src.is_dir():
shutil.copytree(src, dest / name, ignore=shutil.ignore_patterns("target", "__pycache__", "*.whl"))
elif src.is_file():
shutil.copy2(src, dest / name)
else:
raise FabricImageError(f"expected Fabric build input {name!r} not found under {repo}")


def ensure_fabric_image(
*,
fabric_repo: str | Path | None = None,
docker_bin: str = "docker",
force_build: bool = False,
) -> str:
"""Return a usable Fabric image tag, building it only if not already present.

One harness-agnostic image serves every built-in harness. Idempotent and content-addressed: an
unchanged recipe reuses the cached image; a changed recipe yields a new tag. Builds from a staged
copy of the local NeMo-Fabric source (build inputs only).
"""
tag = fabric_image_tag()
if not force_build and image_exists(tag, docker_bin=docker_bin):
logger.debug("Fabric image %s already present; skipping build.", tag)
return tag

repo = _resolve_fabric_repo(fabric_repo)
logger.info(
"Building Fabric image (first build compiles nemo-fabric; this can take minutes)...",
extra=dict(tag=tag, repo=repo),
)
with tempfile.TemporaryDirectory(prefix="nemo-fabric-image-") as ctx_dir:
ctx = Path(ctx_dir)
_stage_source(repo, ctx / "nemo-fabric")
shutil.copy2(_DOCKERFILE, ctx / "Dockerfile")
try:
subprocess.run(
[docker_bin, "build", "--build-arg", f"EXTRAS={_extras_arg()}", "-t", tag, str(ctx)],
check=True,
env={**os.environ, "DOCKER_BUILDKIT": "1"},
)
except subprocess.CalledProcessError as exc:
raise FabricImageError(f"docker build failed for {tag}: {exc}") from exc
logger.info("Built Fabric image %s.", tag, extra=dict(tag=tag))
return tag

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Tag doesn't bust cache when Fabric source changes.

fabric_image_tag() hashes only the Dockerfile bytes + extras (Line 78-79). ensure_fabric_image reuses any image matching that tag (Line 136-138) without hashing the staged fabric_repo contents (Line 147). A developer who edits their local NeMo-Fabric checkout without touching sandbox.Dockerfile will silently get a stale cached image on the next run — no rebuild happens unless force_build=True is passed explicitly.

♻️ Include the staged source in the tag digest
-def fabric_image_tag(*, repo: str = DEFAULT_FABRIC_IMAGE_REPO) -> str:
+def fabric_image_tag(*, repo: str = DEFAULT_FABRIC_IMAGE_REPO, fabric_repo: Path | None = None) -> str:
     """Content-addressed tag for the harness-agnostic Fabric image: ``<repo>:<digest(recipe + extras)>``."""
     recipe = _DOCKERFILE.read_bytes() + _extras_arg().encode("utf-8")
+    if fabric_repo is not None:
+        recipe += _hash_source_tree(fabric_repo).encode("utf-8")
     return f"{repo}:{hashlib.sha256(recipe).hexdigest()[:12]}"
🧰 Tools
🪛 ast-grep (0.44.1)

[error] 88-88: Command coming from incoming request
Context: subprocess.run([docker_bin, "image", "inspect", tag], capture_output=True, check=False)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[error] 149-153: Command coming from incoming request
Context: subprocess.run(
[docker_bin, "build", "--build-arg", f"EXTRAS={_extras_arg()}", "-t", tag, str(ctx)],
check=True,
env={**os.environ, "DOCKER_BUILDKIT": "1"},
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/image.py`
around lines 72 - 158, The cached Fabric image tag only depends on the
Dockerfile and extras, so changes in the staged NeMo-Fabric source can be missed
and a stale image reused. Update fabric_image_tag() so its digest also
incorporates the relevant source contents from the staged fabric_repo (or the
same build inputs used by _stage_source()), and make ensure_fabric_image()
compute the tag after resolving the repo so local source edits produce a new tag
and trigger a rebuild without requiring force_build.

Comment on lines +29 to +42
FROM python:${PYTHON_VERSION}-slim-bookworm AS runtime
COPY --from=builder /opt/venv /opt/venv
COPY --from=builder /src/target/release/fabric /usr/local/bin/fabric
# The CLI binary resolves built-in adapters from its compile-time repository path
# (CARGO_MANIFEST_DIR/../../python/src/nemo_fabric/adapters); ship just that dir to the baked path so a
# wheel-only image can resolve them. Depends on NeMo-Fabric's installed-adapter-discovery layout
# (see image.py); swap to installing the top-level adapters/* packages once that lands on main.
COPY --from=builder /src/python/src/nemo_fabric/adapters /src/python/src/nemo_fabric/adapters
# The CLI's baked path is the literal `<fabric-core crate>/../../python/src/nemo_fabric/adapters`; the
# kernel needs `/src/crates/fabric-core` to exist to walk the `..`, even though nothing lives there.
RUN mkdir -p /src/crates/fabric-core
ENV PATH=/opt/venv/bin:$PATH
RUN python -c "from nemo_fabric import FabricClient" && fabric version
WORKDIR /out

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Final image runs as root.

No USER instruction in the runtime stage. This sandbox execs agent-generated code (fabric run), so running as root widens the blast radius of a container escape.

🔒️ Add a non-root user
 ENV PATH=/opt/venv/bin:$PATH
 RUN python -c "from nemo_fabric import FabricClient" && fabric version
-WORKDIR /out
+RUN useradd --create-home --uid 1000 sandbox
+WORKDIR /out
+RUN chown sandbox:sandbox /out
+USER sandbox
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FROM python:${PYTHON_VERSION}-slim-bookworm AS runtime
COPY --from=builder /opt/venv /opt/venv
COPY --from=builder /src/target/release/fabric /usr/local/bin/fabric
# The CLI binary resolves built-in adapters from its compile-time repository path
# (CARGO_MANIFEST_DIR/../../python/src/nemo_fabric/adapters); ship just that dir to the baked path so a
# wheel-only image can resolve them. Depends on NeMo-Fabric's installed-adapter-discovery layout
# (see image.py); swap to installing the top-level adapters/* packages once that lands on main.
COPY --from=builder /src/python/src/nemo_fabric/adapters /src/python/src/nemo_fabric/adapters
# The CLI's baked path is the literal `<fabric-core crate>/../../python/src/nemo_fabric/adapters`; the
# kernel needs `/src/crates/fabric-core` to exist to walk the `..`, even though nothing lives there.
RUN mkdir -p /src/crates/fabric-core
ENV PATH=/opt/venv/bin:$PATH
RUN python -c "from nemo_fabric import FabricClient" && fabric version
WORKDIR /out
FROM python:${PYTHON_VERSION}-slim-bookworm AS runtime
COPY --from=builder /opt/venv /opt/venv
COPY --from=builder /src/target/release/fabric /usr/local/bin/fabric
# The CLI binary resolves built-in adapters from its compile-time repository path
# (CARGO_MANIFEST_DIR/../../python/src/nemo_fabric/adapters); ship just that dir to the baked path so a
# wheel-only image can resolve them. Depends on NeMo-Fabric's installed-adapter-discovery layout
# (see image.py); swap to installing the top-level adapters/* packages once that lands on main.
COPY --from=builder /src/python/src/nemo_fabric/adapters /src/python/src/nemo_fabric/adapters
# The CLI's baked path is the literal `<fabric-core crate>/../../python/src/nemo_fabric/adapters`; the
# kernel needs `/src/crates/fabric-core` to exist to walk the `..`, even though nothing lives there.
RUN mkdir -p /src/crates/fabric-core
ENV PATH=/opt/venv/bin:$PATH
RUN python -c "from nemo_fabric import FabricClient" && fabric version
RUN useradd --create-home --uid 1000 sandbox
WORKDIR /out
RUN chown sandbox:sandbox /out
USER sandbox
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/sandbox.Dockerfile`
around lines 29 - 42, The runtime stage in sandbox.Dockerfile is still executing
as root because it never switches away from the default user. Update the runtime
setup after the existing COPY/RUN steps to create and use a dedicated non-root
user, and add a USER instruction so the final image runs sandboxed instead of as
root. Keep the change in the runtime stage near the existing ENV, WORKDIR, and
fabric version check so it is easy to locate alongside the image bootstrap
logic.

Source: Linters/SAST tools

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 23814/31046 76.7% 61.4%
Integration Tests 13836/29726 46.6% 19.7%

@SandyChapman SandyChapman force-pushed the schapman/aalgo-321-containerized-fabric-runner branch 2 times, most recently from c7902c0 to 5382c71 Compare July 8, 2026 15:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py (1)

61-64: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

TYPE_CHECKING-only import of FabricConfig/FabricProfileConfig.

As per coding guidelines, "In Python code, prefer concrete type hints over string-based type hints, and do not import those types only under TYPE_CHECKING; import them normally when possible." These are imported only under TYPE_CHECKING, relying on from __future__ import annotations for deferred evaluation. Note _common.py imports nemo_relay.observability normally since it's a hard dependency — nemo_fabric here is optional, which is the likely reason for this pattern, but it still deviates from the stated guideline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py`
around lines 61 - 64, The `container_runtime.py` module is using `FabricConfig`
and `FabricProfileConfig` only inside `TYPE_CHECKING`, which conflicts with the
typing guideline. Update the `FabricAgentRuntime`-related imports so these types
are imported at runtime when available, and keep the module importable by
handling the optional `nemo_fabric` dependency safely instead of relying on
`TYPE_CHECKING`-only imports. Use the concrete type names directly in the
runtime-facing annotations/config handling paths.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py`:
- Around line 61-64: The `container_runtime.py` module is using `FabricConfig`
and `FabricProfileConfig` only inside `TYPE_CHECKING`, which conflicts with the
typing guideline. Update the `FabricAgentRuntime`-related imports so these types
are imported at runtime when available, and keep the module importable by
handling the optional `nemo_fabric` dependency safely instead of relying on
`TYPE_CHECKING`-only imports. Use the concrete type names directly in the
runtime-facing annotations/config handling paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 923c56c2-87d8-44ed-90ee-4404ed535c9d

📥 Commits

Reviewing files that changed from the base of the PR and between 4ac0cf4 and c7902c0.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • packages/nemo_evaluator_sdk/examples/fabric_container/run_e2e.py
  • packages/nemo_evaluator_sdk/pyproject.toml
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/_common.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/image.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/sandbox.Dockerfile
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/README.md
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/api.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/base.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/providers/docker.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_container_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_image.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_integration.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_api.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider_live.py
  • third_party/licenses.jsonl
  • third_party/osv-licenses.json
  • third_party/requirements-main.txt
  • tools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/license/overrides.yaml
✅ Files skipped from review due to trivial changes (3)
  • tools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/license/overrides.yaml
  • third_party/licenses.jsonl
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/README.md
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/nemo_evaluator_sdk/pyproject.toml
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_image.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/base.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/api.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_integration.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_api.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/providers/docker.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py

SandyChapman added a commit that referenced this pull request Jul 8, 2026
The agent-eval runtimes framed the harness prompt with `task.intent` — the
eval-side description of the *desired behavior* (what the grader checks for).
Exposing it to the agent under evaluation is a reward-hacking hole: the agent
can read the grader's intent and target it directly. This mirrors the
held-out-`reference` concern (AGENT-EVAL reference field work).

Unify the framing so every runtime builds the prompt from `task.inputs` only:

- New `runtimes/fabric/_common.py` with a shared, intent-free `fabric_input`;
  the host `FabricAgentRuntime` now calls it (drops the `Task id/Intent/Inputs`
  framing). Designed as the shared home so the AALGO-321 container runtime
  reuses it instead of carrying its own copy.
- `default_codex_prompt` no longer emits `Intent:`; it lifts the instruction
  from `inputs["instruction"|"prompt"]` and keeps its workspace-edit trailer.
- `docker_sandbox._task_prompt` no longer falls back to `task.intent`; a task
  with no instruction in inputs yields an empty prompt rather than leaking it.

Tests updated to lock in the intent-free contract across all three runtimes.

Relates to AALGO-321 fabric-runtime dedup (PR #604).

Signed-off-by: Sandy Chapman <schapman@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py (1)

61-64: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

TYPE_CHECKING-only import of FabricConfig/FabricProfileConfig.

Guideline forbids gating type imports behind TYPE_CHECKING. Understand the intent (nemo_fabric is an optional native dep), but this is the exact pattern the guideline disallows — flagging for visibility even though "import normally" here would reintroduce a hard dependency the module deliberately avoids.

As per coding guidelines, "do not import those types only under TYPE_CHECKING; import them normally when possible."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py`
around lines 61 - 64, The `TYPE_CHECKING`-only imports for `FabricConfig` and
`FabricProfileConfig` in `container_runtime` violate the import guideline, but
the module also needs to stay importable without the optional `nemo_fabric`
dependency. Update the typing strategy around `FabricAgentRuntime` and any
`to_mapping()`-based config usage so these symbols are available without relying
on a `TYPE_CHECKING`-gated import, while still preserving optional-dependency
behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py`:
- Around line 127-153: The cleanup in ContainerRuntime.run_tasks is not
guaranteed because ensure_fabric_image and resolve_secrets run before the
try/finally that closes the provider. Move the image provisioning and secret
resolution into the guarded block in run_tasks so that self._provider.aclose()
still runs if setup fails, keeping the semaphore/gather flow unchanged and
preserving the existing run_one logic.

In
`@packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider_live.py`:
- Around line 26-29: The _docker_ready helper can hang indefinitely on an
unresponsive Docker daemon, blocking the test run instead of skipping cleanly.
Update the subprocess.run call inside _docker_ready to use a bounded timeout and
treat timeout failures the same as an unavailable daemon by returning False.
Keep the change localized to _docker_ready so the live sandbox test setup
continues to use this readiness gate safely.

---

Nitpick comments:
In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py`:
- Around line 61-64: The `TYPE_CHECKING`-only imports for `FabricConfig` and
`FabricProfileConfig` in `container_runtime` violate the import guideline, but
the module also needs to stay importable without the optional `nemo_fabric`
dependency. Update the typing strategy around `FabricAgentRuntime` and any
`to_mapping()`-based config usage so these symbols are available without relying
on a `TYPE_CHECKING`-gated import, while still preserving optional-dependency
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 849b4043-6962-4af0-b097-d2d035af98a0

📥 Commits

Reviewing files that changed from the base of the PR and between c7902c0 and 5382c71.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • packages/nemo_evaluator_sdk/examples/fabric_container/run_e2e.py
  • packages/nemo_evaluator_sdk/pyproject.toml
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/_common.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/image.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/sandbox.Dockerfile
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/README.md
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/api.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/base.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/providers/docker.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_container_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_image.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_integration.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_api.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider_live.py
  • third_party/licenses.jsonl
  • third_party/osv-licenses.json
  • third_party/requirements-main.txt
  • tools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/license/overrides.yaml
✅ Files skipped from review due to trivial changes (1)
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/README.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • packages/nemo_evaluator_sdk/pyproject.toml
  • tools/nemo-platform-sdk-tools/src/nemo_platform_sdk_tools/license/overrides.yaml
  • third_party/licenses.jsonl
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_api.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/base.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_image.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_integration.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/providers/docker.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider.py
  • third_party/requirements-main.txt
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/sandbox/api.py

Comment thread packages/nemo_evaluator_sdk/tests/agent_eval/test_sandbox_docker_provider_live.py Outdated
SandyChapman added a commit that referenced this pull request Jul 8, 2026
The agent-eval runtimes framed the harness prompt with `task.intent` — the
eval-side description of the *desired behavior* (what the grader checks for).
Exposing it to the agent under evaluation is a reward-hacking hole: the agent
can read the grader's intent and target it directly. This mirrors the
held-out-`reference` concern (AGENT-EVAL reference field work).

Unify the framing so every runtime builds the prompt from `task.inputs` only:

- New `runtimes/fabric/_common.py` with a shared, intent-free `fabric_input`;
  the host `FabricAgentRuntime` now calls it (drops the `Task id/Intent/Inputs`
  framing). Designed as the shared home so the AALGO-321 container runtime
  reuses it instead of carrying its own copy.
- `default_codex_prompt` no longer emits `Intent:`; it lifts the instruction
  from `inputs["instruction"|"prompt"]` and keeps its workspace-edit trailer.
- `docker_sandbox._task_prompt` no longer falls back to `task.intent`; a task
  with no instruction in inputs yields an empty prompt rather than leaking it.

Tests updated to lock in the intent-free contract across all three runtimes.

Relates to AALGO-321 fabric-runtime dedup (PR #604).

Signed-off-by: Sandy Chapman <schapman@nvidia.com>
Add FabricContainerRuntime: runs NeMo Fabric + a coding agent inside a per-task
Docker sandbox and maps the result into the existing CandidateEvidence contract,
so the workspace-file, run_verifier, and trajectory metrics score container trials
with no metric changes.

- Provider-neutral sandbox seam (SandboxSpec/SandboxProvider/AsyncSandbox) with a
  Docker provider that shells out to the docker CLI. Context/artifacts cross the
  boundary by file transfer (docker cp), so a Kubernetes provider can slot in behind
  the same seam. AsyncSandbox tears down only its own sandbox; the shared provider's
  lifetime is the batch owner's (run_tasks disposes it once).
- Opaque build-if-missing Fabric image (content-addressed tag) — no Dockerfile for
  callers to write.
- Declare nemo-relay as a hard dependency and build the trajectory profile from its
  typed config, so Relay owns its schema instead of a hand-maintained dict.
- Share the host/container helpers (failed-trial builder, output extraction, evidence
  subdir naming, trajectory telemetry) in fabric/_common.py so the two runtimes cannot
  silently drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
@SandyChapman SandyChapman force-pushed the schapman/aalgo-321-containerized-fabric-runner branch from 5382c71 to 0945799 Compare July 8, 2026 15:42
@SandyChapman

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai — addressed in the latest push (also rebased onto main and vendored into sdk/python):

Fixed

  • container_runtime.py — provider leak on early failure: moved ensure_fabric_image() + resolve_secrets() inside the try/finally, so provider.aclose() runs even if provisioning or secret resolution raises.
  • image.py — unbounded docker subprocess calls: image_exists now uses a 30s timeout and raises FabricImageError on TimeoutExpired; the build gets a generous 3600s ceiling. Added test_image_exists_raises_on_daemon_timeout.
  • test_sandbox_docker_provider_live.py — readiness check: docker info now has a 15s timeout and skips (rather than hangs) an unresponsive daemon.

Deferred to a hardening follow-up (tracked; kept out of this spike to preserve the verified live e2e)

  • sandbox.Dockerfile runs as root and the provider doesn't default to --network none: both are real defense-in-depth gaps for a sandbox that execs agent code. Getting non-root right requires reworking /in, /out, and the -w workdir ownership against the docker cp flow, which I want to land with network-egress scoping and re-verify against the live run — so it's a dedicated follow-up rather than an untested Dockerfile change here. The isolation note in docker.py already flags egress control as future work.
  • Image tag doesn't hash the Fabric source: intentional — the tag is content-addressed on the recipe (Dockerfile + extras), and ensure_fabric_image(force_build=True) is the escape hatch for a dev iterating on a local NeMo-Fabric checkout. The intended evolution (a registry-backed cache behind the same seam) makes source-hashing moot.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@SandyChapman Fixes look good. Deferral rationale for non-root/network isolation and source-hashing is reasonable — agreed those can be follow-up work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant