Skip to content

fix(evaluator): keep a Fabric trial completed when only Relay teardown failed - #1205

Closed
SandyChapman wants to merge 1 commit into
mainfrom
aalgo-495-fabric-relay-teardown-recovery/schapman
Closed

fix(evaluator): keep a Fabric trial completed when only Relay teardown failed#1205
SandyChapman wants to merge 1 commit into
mainfrom
aalgo-495-fabric-relay-teardown-recovery/schapman

Conversation

@SandyChapman

@SandyChapman SandyChapman commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

NVBug 6562846: a Fabric DeepAgents invocation completes its turn — model calls, tool calls, workspace changes, a final assistant response — and is then reported as failed because NeMo Relay's telemetry teardown raises RuntimeError: invalid argument: scope handle is not at the top of the stack. QA saw 31 such trials in the 2026-08-05 full Evaluator regression, including at parallelism=1, so it is not an Evaluator concurrency problem. Before: those trials score as adapter invocation failures. After: when the failure is provably telemetry-only, the trial stays completed with the fault recorded on it.

Related Issue

AALGO-495. NVBug 6562846.

Changes

  • FabricAgentRuntime._to_trial no longer fails a trial whose only failure is a Relay telemetry-teardown fault. It falls through to the normal success path, so the recovered trial carries the same output and evidence (workspace, fabric_result.json, any promoted ATIF/ATOF artifacts) as any other completed trial.
  • New _telemetry_teardown_error predicate. It requires both halves of the evidence before recovering: the adapter's own output.error carries the Relay scope-stack signature, and output.response holds a non-empty final assistant message. It reads output.error rather than result.error because Fabric normalizes every adapter-reported failure to the same top-level adapter_reported_failure code, so only the adapter's own error string distinguishes a telemetry teardown from a real agent failure.
  • The recovery is recorded, never silent: recovered_from_telemetry_fault, fabric_status, and telemetry_error land on the trial metadata (and a warning is logged), so it is auditable and the possibly-truncated trajectory is not read as complete. This mirrors the existing recovered_from_mcp_binding path.
  • Three unit tests: the recovery, plus two negative cases (no final response; a non-telemetry failure that happens to carry a response) that guard against this widening into general failure suppression.
  • Regenerated the vendored SDK mirror (make vendor).

Why this is a mitigation, not the fix

The root cause is upstream and not vendored in this repo:

  • NVIDIA/NeMo-Relaynemo_relay/integrations/langchain/callbacks.py maps every chain run onto one process-global LIFO ScopeStack held in a ContextVar. LangGraph schedules child tasks with copy_context(), which preserves that same mutable stack rather than cloning it, so overlapping chain callbacks close out of LIFO order. _pop_scope also drops the handle from _scope_handles before attempting the Relay pop, stranding an untracked live scope. Relay ships fork_asyncio_context() for exactly this case; the integration never uses it.
  • NVIDIA/NeMo-Fabricadapters/deepagents/adapter.py catches the teardown exception in the same try that guards the agent invocation, so an observability fault rewrites a successful functional outcome.

Verified still live on 2026-08-10 by re-running the bug's minimal repro (two overlapping async chain lifecycles inside one outer Relay scope): reproduces on nemo-relay 0.6.0 (the version pinned here) and on 0.7.2, the newest release — callbacks.py is byte-identical between the two. Upstream issues are being filed separately.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with documentation updates
  • Documentation only
  • Contributor tooling or automation
  • CI, build, or test infrastructure

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Documentation updated for user-visible behavior
  • Documentation not applicable — justification: internal trial-normalization behavior in the evaluator SDK; no user-facing surface or config changes.

Verification

  • Pull request title follows the repository's Conventional Commit format
  • Every commit includes an appropriate Signed-off-by: trailer
  • uv run pre-commit run -a passes, or any blocked checks are identified below
  • Targeted tests pass, or tests are marked not applicable above
  • No secrets, API keys, or credentials are included

Targeted validation:

  • uv run --frozen pytest packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py -q37 passed (34 before, 3 new).
  • Mutation check: stubbing _telemetry_teardown_error to return None fails test_fabric_runtime_recovers_trial_when_only_relay_teardown_failed — the new test genuinely exercises the new code.
  • uv run --frozen pytest packages/nemo_evaluator_sdk/tests/ -q1451 passed.
  • uv run ruff check packages/nemo_evaluator_sdk/ — clean. uv run ruff format --check on both changed source files — clean.
  • tools/lint/lint-python-types.sh (the exact CI type-check entrypoint) — exit 0; no diagnostics in the changed files.
  • tools/lint/lint-sdk-vendored.sh and tools/lint/lint-cli.shpass after committing the regenerated mirror.

Blocked locally, not marked as passed:

  • uv run pre-commit run -a — all Python hooks pass (ruff, ruff format, ty, config-reference docs, uv.lock drift, copyright headers, merge conflicts, plugin-import check). Two hooks fail for local-environment reasons unrelated to this change and are left to CI: uv-lock requires uv 0.9.14 and this machine has 0.9.30 (no pyproject.toml/uv.lock change in this PR), and studio-lint-staged cannot run without web/node_modules (no web/ files changed).
  • tools/lint/lint-openapi.sh fails locally with mapfile: command not found (macOS bash 3.2) and tools/lint/lint-web-sdk.sh fails on missing web/node_modules — both environmental, neither touched by this PR.

Summary by CodeRabbit

  • Bug Fixes

    • Fabric evaluations now recover completed trials when a known telemetry teardown fault occurs after a valid final response.
    • Telemetry and Fabric error details remain available for troubleshooting.
    • Unrelated failures, or teardown faults without a final response, continue to be reported as failed.
  • Tests

    • Added coverage for successful recovery and safeguards against incorrect recovery.

…n failed

NVBug 6562846: a DeepAgents invocation finishes its turn — model calls, tool
calls, workspace changes, final assistant response — and is then reported as
failed because NeMo Relay's telemetry teardown raises

    RuntimeError: invalid argument: scope handle is not at the top of the stack

Relay keeps one process-global LIFO scope stack in a ContextVar. LangGraph
schedules child tasks with copy_context(), which shares that same mutable stack
rather than cloning it, so overlapping chain callbacks close out of LIFO order;
the Fabric DeepAgents adapter catches the resulting teardown error in the same
try that guards the invocation and rewrites a successful run into an adapter
failure. QA saw 31 such trials in the 2026-08-05 regression, at parallelism 1 as
well as 3/5/10, so it is not an Evaluator concurrency problem.

The root cause is upstream in NeMo-Relay and NeMo-Fabric, neither of which is
vendored here. Until that lands, stop scoring the false negative: when a Fabric
failure carries the Relay scope-stack signature in the adapter's own
output.error *and* a non-empty final response, treat the trial as completed and
record the fault (recovered_from_telemetry_fault, fabric_status,
telemetry_error) so the recovery is auditable and the possibly-truncated
trajectory is not read as complete. This mirrors the existing
recovered_from_mcp_binding path.

The predicate deliberately needs both halves of the evidence: a run with no
final response, or a failure that is not the telemetry one, still fails.

Signed-off-by: Sandy Chapman <schapman@nvidia.com>
@SandyChapman
SandyChapman requested review from a team as code owners August 10, 2026 16:11
@github-actions github-actions Bot added the fix label Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 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: ba2bceb8-88a8-4b39-aedd-35bc75172ede

📥 Commits

Reviewing files that changed from the base of the PR and between 8794430 and 57a0f95.

⛔ Files ignored due to path filters (1)
  • sdk/python/nemo-platform/src/nemo_platform/beta/evaluator/agent_eval/runtimes/fabric/runtime.py is excluded by !sdk/**
📒 Files selected for processing (2)
  • 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

📝 Walkthrough

Walkthrough

The Fabric runtime now recovers runs affected only by a known Relay telemetry teardown failure when a non-empty final response exists. Tests verify metadata preservation and rejection of incomplete or unrelated failures.

Changes

Relay teardown recovery

Layer / File(s) Summary
Runtime recovery handling
packages/nemo_evaluator_sdk/src/nemo_evaluator_sdk/agent_eval/runtimes/fabric/runtime.py
The runtime detects the Relay scope-stack error, requires a non-empty final response, and converts eligible failures into completed trials while retaining error metadata.
Recovery regression coverage
packages/nemo_evaluator_sdk/tests/agent_eval/test_fabric_runtime.py
Tests cover successful recovery, metadata retention, missing responses, and unrelated model failures.

Suggested reviewers: a2bondar, aahunt-nv, ajaythorve

🚥 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 The title clearly and concisely describes the main change: preserving completed Fabric trials when Relay teardown fails.
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 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch aalgo-495-fabric-relay-teardown-recovery/schapman

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

@github-actions

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 31788/40399 78.7% 63.4%
Integration Tests 18458/38329 48.2% 20.8%

if telemetry_error is None:
return self._failed_trial(task, evidence_dir, _result_error(result), extra_metadata=base_metadata)
# The agent finished its turn and produced a final response; only Relay's telemetry teardown
# failed, and the Fabric adapter's single try/except rewrote that into an invocation failure

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.

nit: repeated comments at _RELAY_SCOPE_STACK_ERROR as well as here

if not isinstance(output, Mapping):
return None
error = output.get("error")
if not isinstance(error, str) or _RELAY_SCOPE_STACK_ERROR not in error:

@arpitsardhana arpitsardhana Aug 10, 2026

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.

I will reluctantly approve it.
But this smells like encoding Relay/Fabric workarounds in our code.
Ideally relay fixes it and we just bump up the version. (I did same with last ATIF nvbug).
At worst, the issue is moved to next release(which is okay since it is P1)

I shall however leave it your judgement whether to ship it versus, let relay fix this bug and bump version

@SandyChapman

Copy link
Copy Markdown
Contributor Author

Closing in favour of the upstream fix.

This treated the Relay teardown error as non-fatal in the evaluator, which worked around the symptom rather than the cause. The actual defect was in Relay's LangChain callback handler dropping a scope close that arrived out of LIFO order, and it is now fixed upstream in NeMo-Relay #755, merged to release/0.7.

That fix cannot reach the platform immediately — it needs a Relay 0.7.x release, and NeMo Fabric currently pins nemo-relay>=0.6.0,<0.7, which excludes the whole 0.7 line. Until that chain completes, the behaviour is documented as a known issue in #1261 rather than mitigated here.

auto-merge was automatically disabled August 12, 2026 18:58

Pull request was closed

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