Skip to content

feat(evaluator-sdk): agent-skill injection for FabricContainerRuntime - #841

Merged
SandyChapman merged 1 commit into
mainfrom
fabric-container-skill-injection/schapman
Jul 28, 2026
Merged

feat(evaluator-sdk): agent-skill injection for FabricContainerRuntime#841
SandyChapman merged 1 commit into
mainfrom
fabric-container-skill-injection/schapman

Conversation

@SandyChapman

@SandyChapman SandyChapman commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

Adds agent-skill injection to the sandboxed FabricContainerRuntime, so an isolated (Docker) Fabric eval can run WITH an agentskills.io skill — previously only the host FabricAgentRuntime supported skills (limitation L1 from the LAB-examples gap analysis). It mirrors the host runtime's skill design and extends the same two-mode contract to the container.

How

  • __init__ gains a skill: AgentSkill | None; new with_skill() clone method so an A/B eval derives baseline (with_skill(None)) and treated (with_skill(skill)) runtimes from one instance.
  • The injection mode is resolved once per run (the adapter is constant across the taskset) via Fabric's capability planner (_resolve_skill_mode). nemo_fabric is imported lazily on the host only when a skill is set, so the no-skill container path stays dependency-free.
  • New stage_skill_seed() in skills.py — the sandboxed sibling of install_skill. Instead of a host copytree (the container has no host workspace), it renders the bundle into the sandbox seed set (SandboxSpec.files) at the harness's in-sandbox discovery path:
    • native (hermes / any adapter that accepts: ["skills"]) → /in/skills/<name>/ + a skills.paths profile overlay (preserving any pre-configured skill paths, last-wins);
    • codex<workspace>/.agents/skills/<name>/ (self-discovery), no overlay.
  • The codex bundle is scrubbed from the downloaded /out/workspace before it's exposed as evidence (so injected files don't skew workspace-reading metrics). Native staging lives under /in and is never downloaded, so it needs no cleanup.
  • Introduces SkillMode = Literal["native", "codex_skills_dir"], threaded through both runtimes and SkillProvenance.mode.

Text bundles only: the seed set is dict[str, str], so a binary file raises SkillInjectionError (the host install_skill copytree path still handles binary bundles).

Tests

  • 8 new unit cases in test_fabric_container_runtime.py mirroring the host skill-injection suite: native seed-set + overlay, preconfigured-path preservation, runtime-discovered adapter, codex seed + evidence exclusion, fail-fast on an unsupported adapter, no-skill-no-probe, and with_skill copy. Full fabric suite (container + host + skills) = 76 passing; ruff + format clean; ty clean; SDK mirror re-vendored (make vendor).
  • Live-validated (native / hermes): ran one task twice through the runtime against a real Docker sandbox — baseline vs. a skill mandating an unguessable marker token. The marker appeared only in the treated output, confirming the agent genuinely picks up the injected skill. Provenance (mode=native, staged path, content hash) stamped correctly; no leaked containers; native skill correctly absent from workspace evidence.
  • Codex mode is unit-tested only. A full sandboxed codex run needs the codex CLI in the image, which the Fabric sandbox image does not provision yet (hermes+relay only) — separate from this change.

Notes

  • The codex branch is a workaround for a gap in Fabric's codex adapter, not a missing abstraction: the codex fabric-adapter.json declares accepts: ["models", "telemetry"] (no skills), so Fabric routes codex skills unsupported, while the codex CLI itself self-discovers .agents/skills/. When the codex adapter adds skills, this branch can be deleted and codex flows through the same native path.

Summary by CodeRabbit

  • New Features

    • Added multi-skill injection for Fabric agent and container runtimes, supporting native and Codex staging.
    • Introduced additive skill configuration via with_skill / with_skills, and enhanced trials to record multi-skill provenance.
  • Bug Fixes

    • Added fail-fast validation when injected skills can’t be routed for the selected harness adapter.
    • Improved Codex workspace cleanup and ensured skill provenance metadata is included on both successful and failed trials.
  • Tests

    • Expanded coverage for overlays, provenance stamping (single vs multi), staging rollback, skill deduplication, and collision behavior.

@github-actions github-actions Bot added the feat label Jul 22, 2026
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 27565/35320 78.0% 62.5%
Integration Tests 16093/34032 47.3% 19.8%

@SandyChapman
SandyChapman force-pushed the fabric-container-skill-injection/schapman branch from ce8121f to 0ed94f6 Compare July 22, 2026 12:22
@SandyChapman
SandyChapman changed the base branch from main to fabric-multi-skill-injection/schapman July 22, 2026 12:22
@SandyChapman
SandyChapman force-pushed the fabric-container-skill-injection/schapman branch from 0ed94f6 to 83e14d8 Compare July 22, 2026 12:33
@SandyChapman
SandyChapman marked this pull request as ready for review July 22, 2026 12:41
@SandyChapman
SandyChapman requested review from a team as code owners July 22, 2026 12:41
@SandyChapman
SandyChapman force-pushed the fabric-multi-skill-injection/schapman branch 2 times, most recently from ed1a8a0 to 32f367e Compare July 28, 2026 15:24
Base automatically changed from fabric-multi-skill-injection/schapman to main July 28, 2026 15:59
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Fabric runtimes now support additive multi-skill injection, typed routing modes, sandbox staging, rollback, provenance metadata, Codex cleanup, collision validation, and expanded tests.

Changes

Fabric multi-skill injection

Layer / File(s) Summary
Skill contracts and installation
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/skills.py, packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_skills.py
Typed modes, immutable skill sets, multi-skill staging, merged native overlays, provenance generation, and rollback are added.
Agent runtime composition and trial metadata
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
The runtime accepts additive skill sets, installs multiple skills, records multi-skill provenance, preserves single-skill metadata, and cleans Codex bundles from evidence.
Container routing and sandbox evidence
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py, packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_container_runtime.py
The container runtime resolves routing per batch, stages native or Codex skills, validates collisions, removes injected Codex directories, and records metadata.

Sequence Diagram(s)

sequenceDiagram
  participant FabricAgentRuntime
  participant FabricContainerRuntime
  participant FabricPlanner
  participant Sandbox
  participant TrialEvidence
  FabricAgentRuntime->>FabricContainerRuntime: run_tasks with skills
  FabricContainerRuntime->>FabricPlanner: resolve skill injection mode
  FabricPlanner-->>FabricContainerRuntime: native or codex_skills_dir
  FabricContainerRuntime->>Sandbox: stage skill seed and profiles
  Sandbox-->>FabricContainerRuntime: execution result and workspace
  FabricContainerRuntime->>TrialEvidence: clean bundles and record provenance
Loading

Possibly related PRs

Suggested reviewers: arpitsardhana, ngoncharenko

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: agent-skill injection support for FabricContainerRuntime.
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 fabric-container-skill-injection/schapman

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: 1

🧹 Nitpick comments (2)
packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_container_runtime.py (1)

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

Fake Fabric planner is duplicated from test_fabric_runtime.py.

_FakeHarness, _FakeConfig, _FakeProfile, _FakeAdapterInfo, _FakePlan, _harness_name are near-identical in both test modules. Move to a shared conftest.py helper so routing semantics can't drift between the host and container suites.

🤖 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/tests/agent_eval/test_fabric_container_runtime.py`
around lines 347 - 417, Move the duplicated fake Fabric support
symbols—_FakeHarness, _FakeConfig, _FakeProfile, _FakeAdapterInfo, _FakePlan,
and _harness_name—from both test modules into a shared conftest.py helper.
Update the host and container suites to import and reuse those shared
definitions, preserving the existing planning and routing behavior while
removing the local duplicates.
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py (1)

347-355: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Runtime-agnostic skill helpers are copied into both runtimes. _skill_metadata is byte-identical across the two modules, as are _existing_skill_paths and the body of _resolve_skill_mode. Same root cause as the SkillSet extraction already done: these belong in skills.py, not duplicated per runtime. Two copies of the provenance-metadata contract will drift.

  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py#L347-L355: drop the static method and import a shared skill_metadata(provenances) from skills.py.
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py#L498-L506: delete the mirrored copy and import the same shared helper; do the same for _existing_skill_paths (shared declared_skill_paths(config, profiles)).
🤖 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/runtime.py`
around lines 347 - 355, Move the duplicated skill helpers into skills.py: add
shared skill_metadata(provenances) and declared_skill_paths(config, profiles),
then remove _skill_metadata and _existing_skill_paths from
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py
at lines 347-355 and the mirrored methods from
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py
at lines 498-506. Import and use the shared helpers in both runtimes, preserving
the existing provenance metadata and path-resolution behavior.
🤖 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/skills.py`:
- Around line 317-334: Update the skill installation loop around install_skill
to compute each skill’s target root and whether it pre-existed before staging.
Register the target in staged_roots before calling install_skill only when it
was not pre-existing, so partial bundles are removed on failure without deleting
reserved task-seed paths; retain provenance handling for successful
installations.

---

Nitpick comments:
In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py`:
- Around line 347-355: Move the duplicated skill helpers into skills.py: add
shared skill_metadata(provenances) and declared_skill_paths(config, profiles),
then remove _skill_metadata and _existing_skill_paths from
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py
at lines 347-355 and the mirrored methods from
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py
at lines 498-506. Import and use the shared helpers in both runtimes, preserving
the existing provenance metadata and path-resolution behavior.

In
`@packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_container_runtime.py`:
- Around line 347-417: Move the duplicated fake Fabric support
symbols—_FakeHarness, _FakeConfig, _FakeProfile, _FakeAdapterInfo, _FakePlan,
and _harness_name—from both test modules into a shared conftest.py helper.
Update the host and container suites to import and reuse those shared
definitions, preserving the existing planning and routing behavior while
removing the local duplicates.
🪄 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: 2daa902f-d0df-4e71-be0a-4f0c1c071cd3

📥 Commits

Reviewing files that changed from the base of the PR and between 944d77e and 8c93955.

⛔ Files ignored due to path filters (3)
  • 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/runtime.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/fabric/skills.py is excluded by !sdk/**
📒 Files selected for processing (6)
  • 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/runtime.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/skills.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_container_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_skills.py

@SandyChapman
SandyChapman force-pushed the fabric-container-skill-injection/schapman branch from 8bc8139 to ee5e787 Compare July 28, 2026 16:29
@SandyChapman
SandyChapman enabled auto-merge July 28, 2026 16:32
Extends agent-skill injection to the containerized runtime so a skills A/B eval runs in a
sandbox, not just on the host. Each bundle is rendered into the sandbox seed set at the
harness's in-sandbox discovery path (native: /in/skills/<name>; codex:
<workspace>/.agents/skills/<name>), with at most ONE merged native skills overlay — Fabric
applies profile skills.paths last-wins, so per-skill overlays would drop all but the last.
Codex bundles are pruned from the downloaded evidence so injected files don't read as agent
output to workspace-reading metrics.

Review follow-ups folded in:

- SkillSet: a frozen dataclass owning the skill list, its uniqueness check, and the
  clone-on-mutation helpers, replacing the copy of that logic each runtime carried.
- Same-bundle conflict detection: .agents/skills/ is populated by two independent, equally
  valid sources — the runtime skills parameter (A/B, staged pre-start) and the task's own
  files inputs (skills a task always ships, uploaded post-start). Tasks may freely seed
  skills there; only both writing the same <name>/ is rejected, since the task upload lands
  second and would leave the stamped provenance hash describing content the agent never saw.
- Rollback fix (CodeRabbit): install_skills registered a rollback target only after
  install_skill returned, so a skill failing AFTER writing files left its partial bundle on
  disk. The target is now registered before staging, skipping paths that already exist so a
  pre-existing codex task-seed is still never deleted.

Vendor: SDK mirror synced.
Signed-off-by: Sandy Chapman <schapman@nvidia.com>
@SandyChapman
SandyChapman force-pushed the fabric-container-skill-injection/schapman branch from ee5e787 to b5df96e Compare July 28, 2026 16:41

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py (1)

224-257: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Codex bundle cleanup can turn a successful trial into a failure.

_remove_injected_bundle is documented as best-effort ("any filesystem error here must not fail an otherwise-successful trial"), but neither the call site nor the function itself guards against unexpected OSErrors from .resolve()/.exists(). If one is raised, it propagates to the outer except Exception, discarding an already-successful run and reporting the trial as failed — contradicting the stated intent.

🛡️ Proposed fix
             if skill_mode == SKILL_MODE_CODEX_SKILLS_DIR:
                 for provenance in skill_provenances:
-                    await asyncio.to_thread(_remove_injected_bundle, out_dir / "workspace", provenance["location"])
+                    try:
+                        await asyncio.to_thread(
+                            _remove_injected_bundle, out_dir / "workspace", provenance["location"]
+                        )
+                    except OSError:
+                        logger.warning(
+                            "failed to remove injected skill bundle from evidence",
+                            extra={"location": provenance["location"]},
+                        )
🤖 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 224 - 257, Guard the Codex cleanup loop in _run_task so filesystem
errors from _remove_injected_bundle, including failures during path resolution
or existence checks, are treated as best-effort cleanup failures and do not
enter the outer task-failure handler. Preserve the successful result flow by
continuing to _to_trial after cleanup attempts, while keeping unrelated task
errors handled by the existing outer except block.
🤖 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.

Outside diff comments:
In
`@packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/container_runtime.py`:
- Around line 224-257: Guard the Codex cleanup loop in _run_task so filesystem
errors from _remove_injected_bundle, including failures during path resolution
or existence checks, are treated as best-effort cleanup failures and do not
enter the outer task-failure handler. Preserve the successful result flow by
continuing to _to_trial after cleanup attempts, while keeping unrelated task
errors handled by the existing outer except block.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 59ef3cfa-9565-4471-af3f-9c8ed5d8447d

📥 Commits

Reviewing files that changed from the base of the PR and between ee5e787 and b5df96e.

⛔ Files ignored due to path filters (3)
  • 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/runtime.py is excluded by !sdk/**
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/fabric/skills.py is excluded by !sdk/**
📒 Files selected for processing (6)
  • 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/runtime.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/skills.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_container_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_skills.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py
  • packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_skills.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/skills.py
  • packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py

@SandyChapman
SandyChapman added this pull request to the merge queue Jul 28, 2026
Merged via the queue into main with commit e13178a Jul 28, 2026
59 of 60 checks passed
@SandyChapman
SandyChapman deleted the fabric-container-skill-injection/schapman branch July 28, 2026 17:09
soluwalana pushed a commit that referenced this pull request Jul 28, 2026
…#841)

Extends agent-skill injection to the containerized runtime so a skills A/B eval runs in a
sandbox, not just on the host. Each bundle is rendered into the sandbox seed set at the
harness's in-sandbox discovery path (native: /in/skills/<name>; codex:
<workspace>/.agents/skills/<name>), with at most ONE merged native skills overlay — Fabric
applies profile skills.paths last-wins, so per-skill overlays would drop all but the last.
Codex bundles are pruned from the downloaded evidence so injected files don't read as agent
output to workspace-reading metrics.

Review follow-ups folded in:

- SkillSet: a frozen dataclass owning the skill list, its uniqueness check, and the
  clone-on-mutation helpers, replacing the copy of that logic each runtime carried.
- Same-bundle conflict detection: .agents/skills/ is populated by two independent, equally
  valid sources — the runtime skills parameter (A/B, staged pre-start) and the task's own
  files inputs (skills a task always ships, uploaded post-start). Tasks may freely seed
  skills there; only both writing the same <name>/ is rejected, since the task upload lands
  second and would leave the stamped provenance hash describing content the agent never saw.
- Rollback fix (CodeRabbit): install_skills registered a rollback target only after
  install_skill returned, so a skill failing AFTER writing files left its partial bundle on
  disk. The target is now registered before staging, skipping paths that already exist so a
  pre-existing codex task-seed is still never deleted.

Vendor: SDK mirror synced.

Signed-off-by: Sandy Chapman <schapman@nvidia.com>
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.

2 participants