Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions packages/nemo_platform/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,18 @@ nemo-data-designer-plugin = [
"pandas",
]

# Generated from [tool.bundle-package]; do not edit by hand.
nemo-eval-author-plugin = [
"pydantic>=2",
"harbor>=0.18",
"nooa>=0.0.9",
"nemo-experimentalist-plugin",
"nemo-insights-plugin",
"nemo-platform-plugin",
"pyyaml>=6.0.3",
"tomlkit>=0.13.3",
]

# Generated from [tool.bundle-package]; do not edit by hand.
nemo-evaluator-plugin = [
"cloudpickle>=3.1.1",
Expand Down Expand Up @@ -309,6 +321,22 @@ nemo-evaluator-sdk = [
"nemo-fabric>=0.1.1,<0.3.0",
]

# Generated from [tool.bundle-package]; do not edit by hand.
nemo-experimentalist-plugin = [
"pydantic>=2",
"httpx",
"harbor>=0.16",
"nemo-evaluator-sdk",
"opentelemetry-proto>=1.42.1",
"protobuf>=6.0.0",
"nooa>=0.0.9",
"pyyaml>=6.0.3",
"nemo-eval-author-plugin",
"nemo-insights-plugin",
"nemo-platform-plugin",
"tomlkit>=0.13.3",
]

# Generated from [tool.bundle-package]; do not edit by hand.
nemo-guardrails-plugin = [
"nemo-platform-plugin",
Expand All @@ -319,6 +347,20 @@ nemo-guardrails-plugin = [
"langchain-openai>=1.3.5",
]

# Generated from [tool.bundle-package]; do not edit by hand.
nemo-insights-plugin = [
"fastapi>=0.115",
"httpx",
"nemo-platform-plugin",
"nooa>=0.0.9",
"opentelemetry-exporter-otlp>=1.42.1",
"opentelemetry-sdk>=1.42.1",
"pydantic>=2.12.0",
"pyyaml>=6.0.3",
"typer>=0.20.0",
"tzdata==2026.2",
]

# Generated from [tool.bundle-package]; do not edit by hand.
nemo-optimization-plugin = [
"nemo-platform-plugin",
Expand Down Expand Up @@ -437,8 +479,11 @@ plugins = [
"nemo-platform[nemo-anonymizer-plugin]",
"nemo-platform[nemo-auditor-plugin]",
"nemo-platform[nemo-data-designer-plugin]",
"nemo-platform[nemo-eval-author-plugin]",
"nemo-platform[nemo-evaluator-plugin]",
"nemo-platform[nemo-experimentalist-plugin]",
"nemo-platform[nemo-guardrails-plugin]",
"nemo-platform[nemo-insights-plugin]",
"nemo-platform[nemo-optimization-plugin]",
"nemo-platform[nemo-safe-synthesizer-plugin]",
"nemo-platform[nemo-switchyard]",
Expand Down Expand Up @@ -511,6 +556,13 @@ anonymizer = "nemo_anonymizer_plugin.cli:AnonymizerCLI"
auditor = "nemo_auditor.cli:AuditorPluginCLI"
data-designer = "nemo_data_designer_plugin.cli.main:DataDesignerCLI"
evaluator = "nemo_evaluator.cli:EvaluatorPluginCLI"
insights = "nemo_insights_plugin.cli:InsightsCLI"

# Generated from [tool.bundle-package]; do not edit this table by hand.
[project.entry-points."nemo.cli.agents"]
eval-author = "nemo_eval_author_plugin.cli:EvalAuthorCLI"
experimentalist = "nemo_experimentalist_plugin.cli:ExperimentalistCLI"
analyst = "nemo_insights_plugin.analyst.cli:AnalystCLI"

# Generated from [tool.bundle-package]; do not edit this table by hand.
[project.entry-points."nemo.client_provider"]
Expand All @@ -519,11 +571,25 @@ platform = "nmp.common.client_factory:PlatformNemoClientProvider"
# Generated from [tool.bundle-package]; do not edit this table by hand.
[project.entry-points."nemo.controllers"]
agents-deployment = "nemo_agents_plugin.runner.controller:AgentDeploymentController"
insights-analysis = "nemo_insights_plugin.controller:InsightsAnalysisController"

# Generated from [tool.bundle-package]; do not edit this table by hand.
[project.entry-points."nemo.docs"]
auditor = "nemo_auditor.docs:get_docs_path"

# Generated from [tool.bundle-package]; do not edit this table by hand.
[project.entry-points."nemo.experimentalist.components"]
"strategy.evolutionary" = "nemo_experimentalist_plugin.experimentalist.strategies.evolutionary"
"builder.code-edit" = "nemo_experimentalist_plugin.experimentalist.components.coder"
"builder.import" = "nemo_experimentalist_plugin.experimentalist.components.importer"
"selector.pareto-diversity" = "nemo_experimentalist_plugin.experimentalist.components.selector"
"proposer.code-change" = "nemo_experimentalist_plugin.experimentalist.components.proposer"
"terminator.convergence" = "nemo_experimentalist_plugin.experimentalist.components.terminator"
"root-cause-analyzer.trace" = "nemo_experimentalist_plugin.experimentalist.components.analyzer"
"trajectory-scorer.goal-tree" = "nemo_experimentalist_plugin.experimentalist.components.trace_scorer"
"outcome-evaluator.harbor-native" = "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor_native"
"outcome-evaluator.harbor-runner" = "nemo_experimentalist_plugin.experimentalist.components.evaluator.harbor_evaluator"

# Generated from [tool.bundle-package]; do not edit this table by hand.
[project.entry-points."nemo.functions"]
"anonymizer.preview" = "nemo_anonymizer_plugin.functions.preview:PreviewFunction"
Expand All @@ -546,6 +612,7 @@ nemo-switchyard = "nemo_switchyard.middleware:SwitchyardMiddleware"
"data-designer.create" = "nemo_data_designer_plugin.jobs.create:CreateJob"
"evaluator.evaluate" = "nemo_evaluator.jobs.evaluate:EvaluateJob"
"evaluator.agent-evaluate" = "nemo_evaluator.jobs.agent_evaluate:AgentEvalJob"
"insights.analyze-job" = "nemo_insights_plugin.jobs.analyze:AnalyzeJob"

# Generated from [tool.bundle-package]; do not edit this table by hand.
[project.entry-points."nemo.optimization.backends"]
Expand All @@ -560,6 +627,7 @@ auditor = "nemo_auditor.sdk:auditor_sdk_resources"
data_designer = "nemo_data_designer_plugin.sdk.resources:data_designer_sdk_resources"
evaluator = "nemo_evaluator.sdk.resources:evaluator_sdk_resources"
safe_synthesizer = "nemo_safe_synthesizer_plugin.sdk.resources:safe_synthesizer_sdk_resources"
insights = "nemo_insights_plugin.sdk:insights_sdk_resources"

# Generated from [tool.bundle-package]; do not edit this table by hand.
[project.entry-points."nemo.sdk_provider"]
Expand All @@ -577,6 +645,7 @@ anonymizer = "nemo_anonymizer_plugin.service:AnonymizerService"
auditor = "nemo_auditor.service:AuditorPluginService"
data-designer = "nemo_data_designer_plugin.service:DataDesignerService"
evaluator = "nemo_evaluator.service:EvaluatorPluginService"
insights = "nemo_insights_plugin.service:InsightsService"

# Generated from [tool.bundle-package]; do not edit this table by hand.
[project.entry-points."nemo.skills"]
Expand All @@ -588,6 +657,8 @@ anonymizer = "nemo_anonymizer_plugin.skills:get_skills_path"
guardrails = "nemo_guardrails_plugin.skills:get_skills_path"
platform = "nemo_platform.skills:skills_dir"
safe-synthesizer = "nemo_safe_synthesizer_plugin.skills:get_skills_path"
experimentalist = "nemo_experimentalist_plugin.skills:skills_dir"
insights = "nemo_insights_plugin.skills:skills_dir"
[tool.uv.sources]
nemo-platform-sdk = { workspace = true }
nemo-platform-ext = { workspace = true }
Expand Down Expand Up @@ -639,7 +710,14 @@ nemo-agents-plugin = { source = "../../plugins/nemo-agents/src/nemo_agents_plugi
nemo-anonymizer-plugin = { source = "../../plugins/nemo-anonymizer/src/nemo_anonymizer_plugin", module = "nemo_anonymizer_plugin", inherit = { "entry-points" = ["nemo.*"] } }
nemo-auditor-plugin = { source = "../../plugins/nemo-auditor/src/nemo_auditor", module = "nemo_auditor", inherit = { "entry-points" = ["nemo.*"] } }
nemo-data-designer-plugin = { source = "../../plugins/nemo-data-designer/src/nemo_data_designer_plugin", module = "nemo_data_designer_plugin", inherit = { "entry-points" = ["nemo.*"] } }
# Eval Author, Experimentalist, and Insights ship together: Experimentalist imports
# EvalAuthor and EvalAuthorConfig at module scope, Eval Author still borrows
# Experimentalist helpers, and both read the Insights profile contract. Bundling all
# three into one distribution collapses that dependency cycle into intra-wheel extras.
nemo-eval-author-plugin = { source = "../../plugins/nemo-eval-author/src/nemo_eval_author_plugin", module = "nemo_eval_author_plugin", inherit = { "entry-points" = ["nemo.*"] } }
nemo-evaluator-plugin = { source = "../../plugins/nemo-evaluator/src/nemo_evaluator", module = "nemo_evaluator", inherit = { "entry-points" = ["nemo.*"] } }
nemo-experimentalist-plugin = { source = "../../plugins/nemo-experimentalist/src/nemo_experimentalist_plugin", module = "nemo_experimentalist_plugin", inherit = { "entry-points" = ["nemo.*"] } }
nemo-insights-plugin = { source = "../../plugins/nemo-insights/src/nemo_insights_plugin", module = "nemo_insights_plugin", inherit = { "entry-points" = ["nemo.*"] } }
# Required by agents.optimize (OptimizeJob lives here; agents plugin imports it at router setup).
nemo-optimization-plugin = { source = "../../plugins/nemo-optimization/src/nemo_optimization", module = "nemo_optimization", inherit = { "entry-points" = ["nemo.*"] } }
nemo-guardrails-plugin = { source = "../../plugins/nemo-guardrails/src/nemo_guardrails_plugin", module = "nemo_guardrails_plugin", inherit = { "entry-points" = ["nemo.*"] } }
Expand Down
2 changes: 1 addition & 1 deletion plugins/nemo-eval-author/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ dependencies = [
"pydantic>=2",
# Harbor 0.18 provides the discovery APIs used by this plugin.
"harbor>=0.18",
"nooa",
"nooa>=0.0.9",
"nemo-experimentalist-plugin",
"nemo-insights-plugin",
"nemo-platform",
Expand Down
13 changes: 6 additions & 7 deletions plugins/nemo-experimentalist/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,12 @@ instead of restoring Curator imports, configuration, or aliases. The obsolete
Configure Platform services, trace storage, and Insights analysis according
to the Platform documentation; this repository does not own a service,
scheduler, or testbed setup.
- `nooa` is pinned to an immutable public GitHub revision in the workspace root
`pyproject.toml` under `[tool.uv.sources]`; this plugin's `pyproject.toml` only
declares the dependency. It is a commit rather than a tag because the callable
`@strategy(llm=...)` support this plugin depends on landed after `v0.0.8`.
Update the root pin, the matching pin in
`examples/tau3-nooa-agent/pyproject.toml`, the revision quoted in
`framework-skills/nooa/SKILL.md`, and both lock files together. Keep the
- `nooa` comes from PyPI. This plugin's `pyproject.toml` declares the floor
(`nooa>=0.0.9`), which is the first release carrying the callable
`@strategy(llm=...)` support the components depend on. When raising the floor,
move it in this plugin, `nemo-insights`, `nemo-eval-author`, and
`examples/tau3-nooa-agent/pyproject.toml`, update the tag quoted in
`framework-skills/nooa/SKILL.md`, and relock both lock files together. Keep the
Platform-supplied Insights plugin separate.
- This branch uses merged Platform PR 718 contracts only. After the Platform
handoff lands, rebase and repin before adopting any new Platform testbed or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.9.14 /uv /bin/uv
# `uv pip install --system` rather than `uv sync`: there is no project here, just
# one pinned dependency going into the image's own interpreter.
RUN uv pip install --system --no-cache \
"nooa[tracing] @ git+https://github.com/NVIDIA-NeMo/labs-OO-Agents.git@6e0274dd03f883254a084cfb9f871ea580e03434"
"nooa[tracing]==0.0.9"

WORKDIR /app
RUN groupadd --gid 10001 smoke-agent \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ timeout_sec = 60.0
[environment]
# Set by scripts/build_image.py; a test asserts it matches the current content
# hash of the Dockerfile and records file. Tasks ship no environment/ directory.
docker_image = "smoke-agent-env:sha-3f3415c0dbf2"
docker_image = "smoke-agent-env:sha-effb361c1cea"
cpus = 1
memory_mb = 1024
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ readme = "README.md"
requires-python = ">=3.12,<3.14"
dependencies = [
"mcp==1.25.0",
"nooa[tracing]",
"nooa[tracing]>=0.0.9",
"opentelemetry-exporter-otlp-proto-http",
"orjson==3.11.9",
]
[tool.uv.sources]
# Keep in step with the workspace root pin.
nooa = { git = "https://github.com/NVIDIA-NeMo/labs-OO-Agents.git", rev = "6e0274dd03f883254a084cfb9f871ea580e03434" }
10 changes: 7 additions & 3 deletions plugins/nemo-experimentalist/examples/tau3-nooa-agent/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 8 additions & 9 deletions plugins/nemo-experimentalist/framework-skills/nooa/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,24 @@

name: nooa
description: Build, modify, debug, or optimize agents using NVIDIA-labs OO Agents (NOOA). Use for nooa.Agent subclasses, ellipsis generation methods, CodeAct or Predict strategies, ShellTools, Skill/TextSkill, context and persistence, MCP, tracing, middleware, channels, or trace analysis.
compatibility: Python >= 3.12,<3.14; uv; nooa at the revision pinned in the workspace root pyproject.toml
compatibility: Python >= 3.12,<3.14; uv; nooa at the version floor declared by this plugin
metadata:
upstream: https://github.com/NVIDIA-NeMo/labs-OO-Agents
revision: 6e0274dd03f883254a084cfb9f871ea580e03434
version: ">=0.0.9"
---
<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

# NVIDIA-labs OO Agents (NOOA)

Use the `nooa` package and namespace this repository pins. The revision is set in
the workspace root `pyproject.toml` under `[tool.uv.sources]` — the plugin's own
`pyproject.toml` declares `nooa` without one.
Use the `nooa` package and namespace. It comes from PyPI, and the plugin's own
`pyproject.toml` declares the version floor.

For detailed framework guidance, consult the skills at the immutable upstream
revision this repository pins:
https://github.com/NVIDIA-NeMo/labs-OO-Agents/tree/6e0274dd03f883254a084cfb9f871ea580e03434/skills
For detailed framework guidance, consult the skills at the upstream tag that
matches the floor:
https://github.com/NVIDIA-NeMo/labs-OO-Agents/tree/v0.0.9/skills

Before changing framework behavior, inspect the pinned implementation or the
Before changing framework behavior, inspect the installed implementation or the
matching upstream skill instead of guessing. In Experimentalist:

- Call `super().__init__()` before attaching tools or skills.
Expand Down
2 changes: 1 addition & 1 deletion plugins/nemo-experimentalist/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies = [
"nemo-evaluator-sdk",
"opentelemetry-proto>=1.42.1",
"protobuf>=6.0.0",
"nooa",
"nooa>=0.0.9",
"pyyaml>=6.0.3",
"nemo-eval-author-plugin",
"nemo-insights-plugin",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,38 @@
import shutil
import sys
import tomllib
from collections.abc import Iterable
from pathlib import Path
from typing import Any

from packaging.requirements import InvalidRequirement, Requirement
from packaging.specifiers import SpecifierSet
from packaging.utils import canonicalize_name

_REPO_ROOT = Path(__file__).resolve().parents[4]
_EXAMPLE_DIR = Path(__file__).resolve().parents[2] / "examples" / "smoke-agent"
_SHARED = _EXAMPLE_DIR / "dataset" / "_shared"
_HASHED = ("Dockerfile", "records.json")
_RENDERED_TASK_TREE_SHA256 = "d8d4face3d4aa0f5697faaea2bd6146a73573290910c216146a3753ced4b7906"
_RENDERED_TASK_TREE_SHA256 = "f46c78735a97e31185ce9b17250c9d97389cd8e8068590e2a658e54db4e95308"


def _nooa_requirement(candidates: Iterable[str]) -> Requirement:
"""Return the nooa requirement among *candidates*, skipping anything unparseable."""
for candidate in candidates:
try:
requirement = Requirement(candidate)
except InvalidRequirement:
continue
if canonicalize_name(requirement.name) == "nooa":
return requirement
raise AssertionError("no nooa requirement found")


def _root_nooa_rev() -> str:
data = tomllib.loads((_REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
return data["tool"]["uv"]["sources"]["nooa"]["rev"]
def _plugin_nooa_specifier() -> SpecifierSet:
"""Return the nooa version range the plugin declares for itself."""
plugin_pyproject = Path(__file__).resolve().parents[2] / "pyproject.toml"
data = tomllib.loads(plugin_pyproject.read_text(encoding="utf-8"))
return _nooa_requirement(data["project"]["dependencies"]).specifier


def _expected_tag() -> str:
Expand Down Expand Up @@ -180,14 +199,26 @@ def test_verifier_does_not_echo_answers() -> None:
assert forbidden not in code, f"{forbidden} publishes ground truth to the trial log"


def test_dockerfile_nooa_rev_matches_workspace() -> None:
"""Check that the task image uses the workspace's NOOA revision."""
def test_dockerfile_pins_one_nooa_the_plugin_accepts() -> None:
"""Check that the task image pins a single NOOA release from the plugin's range.

The tag is a hash of this Dockerfile, so a version range would let the installed
NOOA drift while the tag stayed put -- exactly the stale-content case the
content-addressed tag exists to catch. The pin also has to satisfy the plugin's
own range, or the agent under test runs against a different framework build.
"""
dockerfile_path = _SHARED / "Dockerfile"
if not dockerfile_path.is_file():
return # Task 4 creates it.
found = re.search(r"labs-OO-Agents\.git@([0-9a-f]{40})", dockerfile_path.read_text(encoding="utf-8"))
assert found is not None, "Dockerfile must pin NOOA to an explicit revision"
assert found.group(1) == _root_nooa_rev()
# Requirements reach `uv pip install` as double-quoted arguments.
quoted = dockerfile_path.read_text(encoding="utf-8").split('"')[1::2]
pins = [spec for spec in _nooa_requirement(quoted).specifier if spec.operator == "=="]

assert len(pins) == 1, f"Dockerfile must pin NOOA to one exact version, got {pins}"
plugin_range = _plugin_nooa_specifier()
assert pins[0].version in plugin_range, (
f"image pins nooa=={pins[0].version}, which the plugin's {plugin_range} rejects"
)


def test_task_image_runs_as_a_non_root_user() -> None:
Expand Down
Loading
Loading