Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
48 changes: 48 additions & 0 deletions plugins/nemo-agents/examples/nemo-agent-config/agent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: test-agent
description: Test agent config

default_harness: hermes

harnesses:
hermes:
kind: hermes
model:
provider: nvidia
model: nvidia/nemotron-3-nano-30b-a3b
api_key_env: NVIDIA_API_KEY
temperature: 0.0
settings:
base_url: https://integrate.api.nvidia.com/v1
max_iterations: 1
max_tokens: 512
reasoning_config:
effort: none
enabled_toolsets: []
system_prompt: You are a concise test assistant.
codex:
kind: codex
settings:
sandbox: workspace-write
skip_git_repo_check: true
config_overrides:
model_reasoning_effort: high

models:
default:
provider: openai
model: openai/gpt-5.4

prompts:
system: prompts/system.md

skills:

environment:
workspace: ./workspace
artifacts: ./artifacts

telemetry:
enabled: false
provider: relay
output_dir: ./artifacts/relay
project: test-agent
21 changes: 10 additions & 11 deletions plugins/nemo-agents/openapi/openapi.yaml

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

4 changes: 3 additions & 1 deletion plugins/nemo-agents/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ dependencies = [
"botocore>=1.40.46,<1.40.62",
"httpx>=0.27",
"pyyaml>=6.0",
# TODO(AIRCORE-896): Add Fabric SDK/runtime and Relay as default
# dependencies once the PyPI wheels are available to the repo resolver.
# Harness adapter packages should stay target-environment dependencies.
# improvement/ subpackage — agent-improvement workflow (POC).
"anthropic>=0.88.0",
"rich>=13.7.1",
Expand Down Expand Up @@ -102,4 +105,3 @@ pythonpath = ["src"]

# Opt this plugin into OpenAPI spec generation.
[tool.nemo.openapi]

115 changes: 115 additions & 0 deletions plugins/nemo-agents/src/nemo_agents_plugin/agent_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Platform-owned agent.yaml config models for NeMo Agents.

These models back Agent.config when config_format is nemo-agents-spec-v1.
Comment thread
mmogallapalli marked this conversation as resolved.
RFC122 proposes first-class environment_spec, sandbox_spec, and harness_spec
fields on Agent; until those shapes are finalized, this config keeps those
inputs together in the versioned Agent.config payload and can be migrated once
the RFC122 contract lands.
"""

from __future__ import annotations

from pathlib import Path
from typing import Any, Self

import yaml
from nemo_agents_plugin.entities import AGENT_CONFIG_FILENAME
from pydantic import BaseModel, ConfigDict, Field, ValidationError, model_validator


class AgentConfigLoadError(ValueError):
"""Raised when a Platform-owned agent.yaml cannot be loaded."""


class ModelConfig(BaseModel):
model_config = ConfigDict(extra="forbid")

provider: str
model: str
api_key_env: str | None = None
temperature: float | None = None
settings: dict[str, Any] = Field(default_factory=dict)


class HarnessConfig(BaseModel):
model_config = ConfigDict(extra="forbid")

kind: str
model: ModelConfig | None = None
settings: dict[str, Any] = Field(default_factory=dict)


class EnvironmentConfig(BaseModel):
model_config = ConfigDict(extra="forbid")

provider: str = "local"
workspace: str = "./workspace"
artifacts: str = "./artifacts"
settings: dict[str, Any] = Field(default_factory=dict)


class TelemetryConfig(BaseModel):
model_config = ConfigDict(extra="forbid")

enabled: bool = False
provider: str | None = None
output_dir: str | None = None
project: str | None = None
atif: dict[str, Any] | None = None
atof: dict[str, Any] | None = None


class AgentConfig(BaseModel):
"""Platform-owned agent.yaml config for nemo-agents-spec-v1."""

model_config = ConfigDict(extra="forbid")

name: str
description: str = ""
default_harness: str
harnesses: dict[str, HarnessConfig]
models: dict[str, ModelConfig] = Field(default_factory=dict)
prompts: dict[str, str] = Field(default_factory=dict)
skills: dict[str, Any] | list[Any] | None = None
environment: EnvironmentConfig = Field(default_factory=EnvironmentConfig)
telemetry: TelemetryConfig = Field(default_factory=TelemetryConfig)

@model_validator(mode="after")
def _validate_default_harness(self) -> Self:
if self.default_harness not in self.harnesses:
available = ", ".join(sorted(self.harnesses))
raise ValueError(f"default_harness must reference one of harnesses: {available}")
return self


def load_agent_config(path: str | Path) -> AgentConfig:
"""Load a Platform-owned agent.yaml file as an AgentConfig."""
config_path = Path(path)

try:
raw_config = config_path.read_text(encoding="utf-8")
except OSError as error:
raise AgentConfigLoadError(f"Unable to read agent config {config_path}: {error}") from error
except UnicodeDecodeError as error:
raise AgentConfigLoadError(f"Agent config {config_path} is not valid UTF-8: {error}") from error

try:
data = yaml.safe_load(raw_config)
except yaml.YAMLError as error:
raise AgentConfigLoadError(f"YAML parse error in agent config {config_path}: {error}") from error

if not isinstance(data, dict):
raise AgentConfigLoadError(f"Agent config {config_path} root must be a YAML mapping.")

try:
return AgentConfig.model_validate(data)
except ValidationError as error:
raise AgentConfigLoadError(f"Invalid agent config {config_path}: {error}") from error


def load_agent_config_from_dir(agent_dir: str | Path) -> AgentConfig:
"""Load the canonical agent.yaml file from an agent directory."""
return load_agent_config(Path(agent_dir) / AGENT_CONFIG_FILENAME)
64 changes: 45 additions & 19 deletions plugins/nemo-agents/src/nemo_agents_plugin/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,19 @@ class Endpoint(BaseModel):
# Canonical spec storage convention
# ---------------------------------------------------------------------------
#
# Each agent has exactly one spec, named by convention. We do **not** store
# the spec location on the agent — it is fully derivable from the agent's
# workspace and name. The convention is enforced by the nemo-spec and
# nemo-build-agent skills; consumers (analyst agent, Studio, optimization
# loop) should call :func:`agent_spec_file_ref` rather than reconstruct the
# path inline.
# Each agent has exactly one spec fileset, named by convention. The fileset can
# hold both the human-readable agent spec and the machine-readable agent config.
# We do **not** store these locations on the agent - they are fully derivable
# from the agent's workspace and name. Consumers should call the file-ref
# helpers below rather than reconstructing refs inline.
#
# Layout:
# - Fileset (entity ref): ``{workspace}/{agent-name}-spec``
# - File inside fileset: ``AGENT-SPEC.md`` (industry-standard name)
# - Full file ref: ``{workspace}/{agent-name}-spec#AGENT-SPEC.md``
# - Local cache: ``agents/{agent-name}-spec/AGENT-SPEC.md``
# - Human-readable spec: ``AGENT-SPEC.md`` (industry-standard name)
# - Machine-readable cfg: ``agent.yaml``
# - Spec file ref: ``{workspace}/{agent-name}-spec#AGENT-SPEC.md``
# - Config file ref: ``{workspace}/{agent-name}-spec#agent.yaml``
# - Local cache root: ``agents/{agent-name}-spec/``
#
# This is intentionally **not** an Optional field on the Agent. The
# relationship is 1:1 and convention-bound; carrying a stored ref would
Expand All @@ -73,10 +74,21 @@ class Endpoint(BaseModel):
AGENT_SPEC_FILENAME = "AGENT-SPEC.md"
"""Canonical filename inside the agent's spec fileset."""

AGENT_CONFIG_FILENAME = "agent.yaml"
"""Canonical machine-readable agent config filename in the agent spec fileset.

This file is parsed into Agent.config when using the nemo-agents-spec-v1 format.
"""

AGENT_SPEC_LOCAL_ROOT = "agents"
"""Local directory holding agent build artifacts."""

NAT_WORKFLOW_CONFIG_FORMAT = "nat-workflow-v1"
"""Canonical format tag for the legacy NAT workflow config format."""

NEMO_AGENTS_SPEC_CONFIG_FORMAT = "nemo-agents-spec-v1"
"""Canonical format tag for the Platform-owned agent.yaml spec format."""


def agent_spec_fileset_name(agent_name: str) -> str:
"""Return the conventional fileset name holding an agent's spec."""
Expand All @@ -91,36 +103,50 @@ def agent_spec_local_path(agent_name: str, root: str | Path = AGENT_SPEC_LOCAL_R
def agent_spec_file_ref(workspace: str, agent_name: str) -> FilesetRef:
"""Return the canonical file ref ``workspace/<name>-spec#AGENT-SPEC.md``.

Use this anywhere downstream code needs to point at an agent's spec
Use this anywhere downstream code needs to point at an agent's spec -
do not reconstruct the path inline. If the layout ever changes (e.g.
moving to a per-agent bundle fileset holding multiple artifacts), this
is the only function that needs to update.
"""
return FilesetRef(f"{workspace}/{agent_spec_fileset_name(agent_name)}#{AGENT_SPEC_FILENAME}")


def agent_config_file_ref(workspace: str, agent_name: str) -> FilesetRef:
"""Return the canonical file ref ``workspace/<name>-spec#agent.yaml``.

Use this anywhere downstream code needs to point at an agent's config -
do not reconstruct the path inline. If the layout ever changes (e.g.
moving to a per-agent bundle fileset holding multiple artifacts), this
is the only function that needs to update.
"""
return FilesetRef(f"{workspace}/{agent_spec_fileset_name(agent_name)}#{AGENT_CONFIG_FILENAME}")


# TODO: RFC-122 will add specs for environment, sandbox, and harness. Add those
# specs to this object once finalized.
class Agent(NemoEntity, entity_type="agent"):
"""An agent definition — stores the NAT workflow config and metadata.
"""An agent definition — stores agent config and metadata.

Entity type: ``agent``
Primary lookup: by ``name`` within a ``workspace``.

The agent's spec lives at the location returned by
:func:`agent_spec_file_ref` — it is **not** stored on the entity
because the path is fully derivable from ``(workspace, name)``.
The agent's spec files live at the locations returned by
:func:`agent_spec_file_ref` and :func:`agent_config_file_ref` — they
are **not** stored on the entity because the paths are fully derivable
from ``(workspace, name)``.
"""

description: str = Field(default="", description="Human-readable description of the agent.")
config: dict[str, Any] = Field(
default_factory=dict,
description="NAT workflow config (YAML-equivalent dict, keyed by component name).",
description="Agent config dict interpreted according to config_format.",
)
config_format: str = Field(
default="nat-workflow-v1",
default=NAT_WORKFLOW_CONFIG_FORMAT,
description=(
"platform-internal schema version tag for the agent config dict. "
"Not read or validated by NAT — used by NeMo Platform for future config migration. "
"Currently only 'nat-workflow-v1' is supported."
"platform-internal schema version tag for the agent config dict. "
"`nat-workflow-v1` is the default legacy NAT workflow format; "
"`nemo-agents-spec-v1` identifies the Platform-owned agent.yaml spec format."
),
)

Expand Down
22 changes: 22 additions & 0 deletions plugins/nemo-agents/src/nemo_agents_plugin/fabric/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Fabric Config Boundary

This package contains the Platform-side config and translation helpers for
Fabric-backed NeMo Agents.

NeMo Platform owns the persisted agent contract. A Fabric-backed agent is stored
using the Platform-owned `nemo-agents-spec-v1` config shape, authored as
`agent.yaml` in the agent spec fileset and represented in code as `AgentConfig`.

Fabric is an execution dependency, not the persisted Platform contract. Before
calling Fabric SDK APIs, NeMo Agents translates the Platform-owned config into a
typed in-memory `FabricConfig`.

```text
Platform agent.yaml -> AgentConfig -> FabricConfig
```

`agent.yaml` is not treated as a Fabric SDK file-backed config or profile. The
Platform config may keep product concepts, defaults, and artifact references in
the shape NeMo Platform needs, while the Fabric translator owns the mapping into
Fabric's runtime fields such as harness adapter, model, environment, and
telemetry config.
4 changes: 4 additions & 0 deletions plugins/nemo-agents/src/nemo_agents_plugin/fabric/__init__.py
Comment thread
mmogallapalli marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Fabric integration helpers for NeMo Agents."""
Loading