diff --git a/plugins/nemo-agents/src/nemo_agents_plugin/agent_config.py b/plugins/nemo-agents/src/nemo_agents_plugin/agent_config.py index aa780e096a..531a144888 100644 --- a/plugins/nemo-agents/src/nemo_agents_plugin/agent_config.py +++ b/plugins/nemo-agents/src/nemo_agents_plugin/agent_config.py @@ -74,6 +74,7 @@ class TelemetryConfig(BaseModel): provider: str | None = None output_dir: str | None = None project: str | None = None + agent_name: str | None = None atif: dict[str, Any] | None = None atof: dict[str, Any] | None = None opentelemetry: dict[str, Any] | None = None diff --git a/plugins/nemo-agents/src/nemo_agents_plugin/agent_config_formats.py b/plugins/nemo-agents/src/nemo_agents_plugin/agent_config_formats.py index d5620f2bf9..358320c2f8 100644 --- a/plugins/nemo-agents/src/nemo_agents_plugin/agent_config_formats.py +++ b/plugins/nemo-agents/src/nemo_agents_plugin/agent_config_formats.py @@ -12,6 +12,7 @@ from nemo_agents_plugin.utils import ( inject_default_model, inject_fabric_gateway_url, + inject_fabric_trace_agent_name, inject_gateway_url, inject_nemo_trace_fields, ) @@ -72,8 +73,8 @@ def resolve_for_deployment( workspace: str, agent_name: str, ) -> dict[str, Any]: - del agent_name - return self._normalize(inject_fabric_gateway_url(config, workspace)) + resolved = self._normalize(inject_fabric_gateway_url(config, workspace)) + return inject_fabric_trace_agent_name(resolved, agent_name) @staticmethod def _normalize(config: dict[str, Any]) -> dict[str, Any]: diff --git a/plugins/nemo-agents/src/nemo_agents_plugin/fabric/translator.py b/plugins/nemo-agents/src/nemo_agents_plugin/fabric/translator.py index 17222833f4..ca58b78c0e 100644 --- a/plugins/nemo-agents/src/nemo_agents_plugin/fabric/translator.py +++ b/plugins/nemo-agents/src/nemo_agents_plugin/fabric/translator.py @@ -193,15 +193,21 @@ def _apply_telemetry(fabric_config: Any, config: AgentConfig, model: ModelConfig ) +def _telemetry_agent_name(config: AgentConfig) -> str: + """Return the name traces should be tagged with for this agent.""" + return config.telemetry.agent_name or config.name + + def _relay_observability_config(config: AgentConfig, model: ModelConfig) -> dict[str, Any]: telemetry = config.telemetry + agent_name = _telemetry_agent_name(config) observability: dict[str, Any] = {"version": 3} if telemetry.atif is not None: atif = dict(telemetry.atif) if telemetry.output_dir is not None: atif.setdefault("output_directory", telemetry.output_dir) - atif.setdefault("agent_name", config.name) + atif.setdefault("agent_name", agent_name) atif.setdefault("model_name", model.model) observability["atif"] = atif @@ -211,7 +217,7 @@ def _relay_observability_config(config: AgentConfig, model: ModelConfig) -> dict if telemetry.opentelemetry is not None: observability["opentelemetry"] = _relay_opentelemetry_config( telemetry.opentelemetry, - agent_name=config.name, + agent_name=agent_name, ) return observability diff --git a/plugins/nemo-agents/src/nemo_agents_plugin/utils.py b/plugins/nemo-agents/src/nemo_agents_plugin/utils.py index eae83701c7..1f83036aa6 100644 --- a/plugins/nemo-agents/src/nemo_agents_plugin/utils.py +++ b/plugins/nemo-agents/src/nemo_agents_plugin/utils.py @@ -316,6 +316,18 @@ def rebind_intake_ingest_workspace(config: dict[str, Any], workspace: str) -> di return config +def inject_fabric_trace_agent_name( + config: dict[str, Any], + agent_name: str, +) -> dict[str, Any]: + """Stamp the Platform-registered *agent_name* onto a Fabric agent spec's telemetry.""" + telemetry = config.get("telemetry") + if not agent_name or not isinstance(telemetry, dict): + return config + telemetry.setdefault("agent_name", agent_name) + return config + + def inject_nemo_trace_fields( config: dict[str, Any], workspace: str, diff --git a/plugins/nemo-agents/tests/unit/test_agent_config_formats.py b/plugins/nemo-agents/tests/unit/test_agent_config_formats.py index 16b0c94daa..476abafc50 100644 --- a/plugins/nemo-agents/tests/unit/test_agent_config_formats.py +++ b/plugins/nemo-agents/tests/unit/test_agent_config_formats.py @@ -109,3 +109,46 @@ def inject_gateway(config: dict[str, Any], workspace: str) -> dict[str, Any]: assert resolved["environment"]["provider"] == "local" assert "workflow" not in resolved assert calls == ["test-workspace"] + + +def test_nemo_agents_deployment_resolution_stamps_registered_agent_name( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr( + agent_config_formats, + "inject_fabric_gateway_url", + lambda config, workspace: config, + ) + + resolved = resolve_agent_config_for_deployment( + NEMO_AGENTS_SPEC_CONFIG_FORMAT, + _nemo_agents_config(), + workspace="test-workspace", + agent_name="test-agent-hzwy9s", + ) + + # The spec's own ``name`` stays untouched; only telemetry is retagged so + # per-agent trace queries match the Platform-registered name. + assert resolved["name"] == "test-agent" + assert resolved["telemetry"]["agent_name"] == "test-agent-hzwy9s" + + +def test_nemo_agents_deployment_resolution_keeps_explicit_telemetry_agent_name( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr( + agent_config_formats, + "inject_fabric_gateway_url", + lambda config, workspace: config, + ) + config = _nemo_agents_config() + config["telemetry"] = {"enabled": True, "agent_name": "author-chosen-name"} + + resolved = resolve_agent_config_for_deployment( + NEMO_AGENTS_SPEC_CONFIG_FORMAT, + config, + workspace="test-workspace", + agent_name="test-agent-hzwy9s", + ) + + assert resolved["telemetry"]["agent_name"] == "author-chosen-name" diff --git a/plugins/nemo-agents/tests/unit/test_fabric_translator.py b/plugins/nemo-agents/tests/unit/test_fabric_translator.py index dcba43f0d8..cba8abfd3a 100644 --- a/plugins/nemo-agents/tests/unit/test_fabric_translator.py +++ b/plugins/nemo-agents/tests/unit/test_fabric_translator.py @@ -249,8 +249,10 @@ def test_environment_spec_env_forwarded_platform_values_win(self, monkeypatch: p fabric_config = translate_agent_config(config, harness_name="codex") - assert fabric_config.environment.env["CUSTOM"] == "from-spec" - assert fabric_config.environment.env["NMP_WORKSPACE"] == "runtime-ws" + environment = fabric_config.environment + assert environment is not None + assert environment.env["CUSTOM"] == "from-spec" + assert environment.env["NMP_WORKSPACE"] == "runtime-ws" def test_environment_mirror_fields_forwarded(self) -> None: payload = copy.deepcopy(_example_yaml_config()) @@ -266,10 +268,12 @@ def test_environment_mirror_fields_forwarded(self) -> None: fabric_config = translate_agent_config(config, harness_name="codex") - assert fabric_config.environment.control_location == "in_env_control" - assert fabric_config.environment.ownership == "fabric_owned" - assert fabric_config.environment.connection == {"url": "http://sandbox"} - assert fabric_config.environment.metadata == {"team": "platform"} + environment = fabric_config.environment + assert environment is not None + assert environment.control_location == "in_env_control" + assert environment.ownership == "fabric_owned" + assert environment.connection == {"url": "http://sandbox"} + assert environment.metadata == {"team": "platform"} def test_runtime_constraints_forwarded(self) -> None: payload = copy.deepcopy(_example_yaml_config()) @@ -371,6 +375,42 @@ def test_relay_telemetry_uses_latest_fabric_shape(self) -> None: }, } + def test_relay_telemetry_prefers_platform_registered_agent_name(self) -> None: + payload = copy.deepcopy(_example_yaml_config()) + payload["telemetry"]["enabled"] = True + payload["telemetry"]["agent_name"] = "example-agent-hzwy9s" + payload["telemetry"]["opentelemetry"] = { + "enabled": True, + "endpoints": [{"type": "full", "endpoint": "http://otel-collector:4317"}], + } + config = AgentConfig.model_validate(payload) + + fabric_config = translate_agent_config(config) + + relay = fabric_config.relay + assert relay is not None + observability = relay.observability + assert observability is not None + assert observability.model_dump(exclude_none=True)["atif"]["agent_name"] == "example-agent-hzwy9s" + opentelemetry = observability.opentelemetry + assert opentelemetry is not None + assert opentelemetry.endpoints[0].service_name == "example-agent-hzwy9s" + + def test_relay_telemetry_keeps_explicit_atif_agent_name(self) -> None: + payload = copy.deepcopy(_example_yaml_config()) + payload["telemetry"]["enabled"] = True + payload["telemetry"]["agent_name"] = "example-agent-hzwy9s" + payload["telemetry"]["atif"]["agent_name"] = "author-chosen-name" + config = AgentConfig.model_validate(payload) + + fabric_config = translate_agent_config(config) + + relay = fabric_config.relay + assert relay is not None + observability = relay.observability + assert observability is not None + assert observability.model_dump(exclude_none=True)["atif"]["agent_name"] == "author-chosen-name" + def test_relay_atof_endpoint_sinks_translate_to_stream_sinks(self) -> None: payload = copy.deepcopy(_example_yaml_config()) payload["telemetry"]["enabled"] = True