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
7 changes: 5 additions & 2 deletions .agents/skills/contribute-adapter/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,11 @@ Decide the following before implementation:
`failed: true`, and structured `error` (`code`, `message`, `retryable`, and
optional `metadata`); Fabric normalizes it into a failed `RunResult`.

> **TODO:** Revisit this output contract when Fabric adds streaming support;
> update this guidance and affected adapter evidence then.
- NVIDIA NeMo Relay streaming through `Runtime.invoke_stream()` does not change
the adapter lifecycle stdout contract. Fabric injects an SDK-owned NDJSON ATOF
endpoint before the adapter starts, NeMo Relay sends raw records out of band,
and the adapter still returns exactly one terminal lifecycle response. Do not
emit stream records on adapter stdout.

- Scope workspace, generated config, state, sessions, and artifacts to the
resolved runtime context. Stateful adapters must isolate Fabric runtime IDs.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ flowchart TB
Adapter["Selected NeMo Fabric adapter"]
Harness["Agent harness runtime\nHermes Agent | Codex | Claude Code | LangChain Deep Agents | custom"]
Artifacts["Normalized results and artifacts\nresponse | logs | patches | telemetry refs"]
Relay["NeMo Relay\nATOF | ATIF | OTel | OpenInference when enabled"]
Relay["NVIDIA NeMo Relay\nATOF | ATIF | OTel | OpenInference when enabled"]

Consumer --> Core
Config --> Core
Expand All @@ -198,7 +198,8 @@ Use the following resources to learn about NeMo Fabric:

- [Example Notebooks](examples/notebooks/README.md) provide a guided tour of the Python SDK.
- [Python SDK guide](docs/sdk/python.mdx): typed configuration, planning,
diagnostics, requests, multi-turn runtimes, parallelism, results, and errors.
diagnostics, requests, multi-turn runtimes, NeMo Relay streaming,
parallelism, results, and errors.
- [Experimentation CLI](docs/experimentation/cli.mdx): presets, maintained
examples, editable application scaffolds, and explicit non-goals.
- [Getting Started overview](docs/about-nemo-fabric/overview.mdx): interface
Expand Down
4 changes: 4 additions & 0 deletions adapters/common/src/nemo_fabric_adapters/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ def merge_unique(*values: Any) -> list[str]:
return merged


def without_none(mapping: dict[str, Any]) -> dict[str, Any]:
return {key: value for key, value in mapping.items() if value is not None}


def dump_yaml(value: dict[str, Any]) -> str:
try:
import yaml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ async def invoke(self, invocation: dict[str, Any]) -> dict[str, Any]:
user_message = request.get("input") or ""
if not isinstance(user_message, str):
user_message = json.dumps(user_message, sort_keys=True)
request_id = request.get("request_id")

result_state: Any = None
events: list[dict[str, Any]] = []
Expand All @@ -590,7 +591,9 @@ async def invoke(self, invocation: dict[str, Any]) -> dict[str, Any]:
callback_handler = self._callback_handler_type()
async with self._relay_plugin.plugin(self._relay_plugin_config):
with self._relay_scope.scope(
"deepagents-request", self._relay_scope_type.Agent
"deepagents-request",
self._relay_scope_type.Agent,
metadata={"nemo_fabric_request_id": request_id},
):
(
result_state,
Expand Down
59 changes: 50 additions & 9 deletions adapters/hermes/src/nemo_fabric_adapters/hermes/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@
LOGGER = logging.getLogger(__name__)


def _fabric_stream_sink_enabled(config: dict[str, Any] | None) -> bool:
if config is None:
return False
for component in config.get("components") or []:
if not isinstance(component, dict) or component.get("kind") != "observability":
continue
component_config = component.get("config")
if not isinstance(component_config, dict):
continue
atof = component_config.get("atof")
if not isinstance(atof, dict):
continue
if any(
isinstance(sink, dict) and sink.get("name") == "nemo-fabric-stream"
for sink in atof.get("sinks") or []
):
return True
return False


def validate_hermes_telemetry_provider(payload: dict[str, Any]) -> None:
providers = common_utils.telemetry_providers(payload)
if any(provider != "relay" for provider in providers):
Expand Down Expand Up @@ -342,20 +362,41 @@ async def invoke(self, invocation: dict[str, Any]) -> dict[str, Any]:
user_message = request.get("input") or ""
if not isinstance(user_message, str):
user_message = json.dumps(user_message, sort_keys=True)
try:
self._relay_session_pending = self._relay_plugin_config is not None
self._relay_finalize_hook_invoked = False
result, adapter_stdout = _invoke_hermes_turn(

def invoke_turn() -> tuple[dict[str, Any], str]:
return _invoke_hermes_turn(
agent=self._agent,
settings=self._settings,
user_message=user_message,
conversation_history=self._conversation_history,
)
finally:
# Hermes' Relay plugin materializes ATIF when its session-finalize
# hook runs. Finalize the telemetry session for each Fabric
# invocation while retaining the native AIAgent and SessionDB.
self._finalize_relay_session()

self._relay_session_pending = self._relay_plugin_config is not None
self._relay_finalize_hook_invoked = False
if _fabric_stream_sink_enabled(self._relay_plugin_config):
from nemo_relay import ScopeType, scope

with scope.scope(
"nemo-fabric-invocation",
ScopeType.Agent,
metadata={
"nemo_fabric_request_id": request.get("request_id"),
},
):
try:
result, adapter_stdout = invoke_turn()
finally:
# The Hermes plugin pushes its session below this correlation
# scope, so finalize that session before popping the parent.
self._finalize_relay_session()
else:
try:
result, adapter_stdout = invoke_turn()
finally:
# Hermes' Relay plugin materializes ATIF when its session-finalize
# hook runs. Finalize the telemetry session for each Fabric
# invocation while retaining the native AIAgent and SessionDB.
self._finalize_relay_session()
messages = result.get("messages") or []
if isinstance(messages, list):
self._conversation_history = messages
Expand Down
14 changes: 11 additions & 3 deletions docs/about-nemo-fabric/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,15 @@ Harness installation and credential requirements differ by adapter. The
contains the complete Hermes Agent environment recipe.

Refer to the [Python SDK guide](../sdk/python.mdx) for planning,
diagnostics, typed requests, and multi-turn runtime examples.
diagnostics, typed requests, multi-turn runtimes, and NVIDIA NeMo Relay streaming.

## Choose Your Interface

| Interface | Use it when | Start with |
| --- | --- | --- |
| Python SDK | Your application owns job config, runtime lifecycle, or multi-turn state | [Client API](../reference/api/python-library-reference/nemo_fabric.client.md) |
| Runtime API | You need multiple ordered turns over one live harness runtime | [Runtime](../reference/api/python-library-reference/nemo_fabric.runtime.md) |
| Streaming API | You need live ATOF records generated by NeMo Relay during a runtime turn | [Streaming](../reference/api/python-library-reference/nemo_fabric.streaming.md) |
| `nemo-fabric` CLI | You are experimenting with harnesses, running maintained examples, or troubleshooting configs | [Experimentation CLI](../experimentation/cli.mdx) |
| JSON Schema | You are building editors, validation, code generation, or another language binding | Committed schemas in the [repository](https://github.com/NVIDIA/NeMo-Fabric/tree/main/schemas) |

Expand All @@ -141,8 +142,9 @@ obtain complete typed configs from built-in presets or maintained examples.
observability settings without mutating the base config.
3. **Plan and diagnose** to resolve the adapter and check capabilities and
requirements before spending work on a runtime.
4. **Run or start a runtime** through the shared start, invoke, and stop
lifecycle contract.
4. **Run or start a runtime** through the shared lifecycle contract. To consume
live ATOF records, enable NeMo Relay, start the runtime with `streaming=True`,
and call `Runtime.invoke_stream()`.
5. **Consume evidence** from `RunResult`: output, structured failure details,
artifacts, events, and telemetry references.

Expand All @@ -161,6 +163,12 @@ obtain complete typed configs from built-in presets or maintained examples.
>
Invoke multiple ordered turns and stop runtime handles safely.
</Card>
<Card
title="Streaming"
href="/reference/api/python-library-reference/streaming"
>
Consume live, raw NeMo Relay ATOF records and retrieve the terminal run result.
</Card>
<Card
title="Types"
href="/reference/api/python-library-reference/types"
Expand Down
2 changes: 2 additions & 0 deletions docs/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ navigation:
path: ./reference/api/python-library-reference/nemo_fabric.client.md
- page: Runtime
path: ./reference/api/python-library-reference/nemo_fabric.runtime.md
- page: Streaming
path: ./reference/api/python-library-reference/nemo_fabric.streaming.md
- page: Models
path: ./reference/api/python-library-reference/nemo_fabric.models.md
- page: Types
Expand Down
6 changes: 4 additions & 2 deletions docs/reference/api/python-library-reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ title: "Python SDK Reference"
slug: "/reference/api/python-library-reference"
description: "Complete reference for the public NeMo Fabric Python SDK."
---
{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0 */}
<!-- SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0 -->

# API Overview

## Modules

- [`nemo_fabric.client`](./nemo_fabric.client.md#module-nemo_fabricclient): Native Python client for resolving and running NeMo Fabric agents.
- [`nemo_fabric.runtime`](./nemo_fabric.runtime.md#module-nemo_fabricruntime): Runtime lifecycle support for the Fabric Python SDK.
- [`nemo_fabric.streaming`](./nemo_fabric.streaming.md#module-nemo_fabricstreaming): NVIDIA NeMo Relay streaming support for the NVIDIA NeMo Fabric Python SDK.
- [`nemo_fabric.models`](./nemo_fabric.models.md#module-nemo_fabricmodels): Pydantic SDK models for NeMo Fabric configuration and requests.
- [`nemo_fabric.types`](./nemo_fabric.types.md#module-nemo_fabrictypes): Public data contracts for the NeMo Fabric Python SDK.
- [`nemo_fabric.errors`](./nemo_fabric.errors.md#module-nemo_fabricerrors): Public exception hierarchy for the NeMo Fabric Python SDK.
Expand All @@ -21,6 +22,7 @@ SPDX-License-Identifier: Apache-2.0 */}
- [`client.Fabric`](./nemo_fabric.client.md#class-fabric): Primary Python entrypoint for NeMo Fabric.
- [`runtime.Runtime`](./nemo_fabric.runtime.md#class-runtime): One logical, stateful harness execution.
- [`runtime.RuntimeStatus`](./nemo_fabric.runtime.md#class-runtimestatus): Lifecycle state of a runtime.
- [`streaming.InvokeStream`](./nemo_fabric.streaming.md#class-invokestream): Async iterator of raw ATOF records for one runtime invocation.
- [`models.EnvironmentConfig`](./nemo_fabric.models.md#class-environmentconfig): Execution environment configuration supplied by the consumer.
- [`models.FabricBaseModel`](./nemo_fabric.models.md#class-fabricbasemodel): Base class for SDK-facing Pydantic models.
- [`models.FabricConfig`](./nemo_fabric.models.md#class-fabricconfig): SDK-facing typed Fabric agent configuration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ title: "Client"
slug: "/reference/api/python-library-reference/client"
description: "Resolve, plan, diagnose, and run agents with NeMo Fabric."
---
{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0 */}
<!-- SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0 -->

# <kbd>module</kbd> `nemo_fabric.client`
Native Python client for resolving and running NeMo Fabric agents.
Expand Down Expand Up @@ -145,13 +145,14 @@ Execute one complete start, invoke, and stop lifecycle.
start_runtime(
config: 'FabricConfig',
base_dir: 'str | PathLike[str] | None' = None,
overrides: 'Mapping[str, Any] | None' = None
overrides: 'Mapping[str, Any] | None' = None,
streaming: 'bool' = False
) → Runtime
```

Start a stateful runtime for one or more ordered invocations.

Each call starts a new logical runtime. Runtime-scoped overrides are recursively merged below invocation-scoped overrides.
Each call starts a new logical runtime. Runtime-scoped overrides are recursively merged below invocation-scoped overrides. Set ``streaming=True`` with NVIDIA NeMo Relay enabled to provision the SDK-owned ATOF endpoint used by ``Runtime.invoke_stream()``.



Expand All @@ -160,6 +161,7 @@ Each call starts a new logical runtime. Runtime-scoped overrides are recursively
- <b>`config`</b>: Complete typed ``FabricConfig``.
- <b>`base_dir`</b>: Base directory for resolving relative paths.
- <b>`overrides`</b>: JSON-compatible overrides applied to every invocation in the runtime unless superseded by invocation overrides.
- <b>`streaming`</b>: Whether to provision NeMo Relay ATOF streaming for ``Runtime.invoke_stream()``.



Expand All @@ -170,7 +172,7 @@ Each call starts a new logical runtime. Runtime-scoped overrides are recursively

**Raises:**

- <b>`FabricConfigError`</b>: If inputs or overrides are invalid.
- <b>`FabricConfigError`</b>: If inputs or overrides are invalid, or streaming is requested without NeMo Relay enabled.
- <b>`FabricNativeUnavailableError`</b>: If the native extension is not installed.
- <b>`FabricRuntimeError`</b>: If runtime startup fails.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ title: "Errors"
slug: "/reference/api/python-library-reference/errors"
description: "Structured exception hierarchy for config, capability, state, and runtime failures."
---
{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0 */}
<!-- SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0 -->

# <kbd>module</kbd> `nemo_fabric.errors`
Public exception hierarchy for the NeMo Fabric Python SDK.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ title: "Models"
slug: "/reference/api/python-library-reference/models"
description: "Pydantic authoring models for NeMo Fabric config and request inputs."
---
{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0 */}
<!-- SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0 -->

# <kbd>module</kbd> `nemo_fabric.models`
Pydantic SDK models for NeMo Fabric configuration and requests.
Expand Down Expand Up @@ -609,7 +609,7 @@ Return a detached JSON-compatible mapping for Rust/core calls.


## <kbd>class</kbd> `RelayConfigPolicy`
NeMo Relay config validation policy.
NVIDIA NeMo Relay config validation policy.


---
Expand Down Expand Up @@ -669,6 +669,7 @@ Return a detached JSON-compatible mapping for Rust/core calls.


## <kbd>class</kbd> `RelayAtofFileSinkConfig`

NeMo Relay ATOF file sink configuration.


Expand Down Expand Up @@ -729,6 +730,7 @@ Return a detached JSON-compatible mapping for Rust/core calls.


## <kbd>class</kbd> `RelayAtofStreamSinkConfig`

NeMo Relay ATOF stream sink configuration.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ title: "Runtime"
slug: "/reference/api/python-library-reference/runtime"
description: "Drive stateful multi-turn execution through the Runtime API."
---
{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0 */}
<!-- SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0 -->

# <kbd>module</kbd> `nemo_fabric.runtime`
Runtime lifecycle support for the Fabric Python SDK.
Expand Down Expand Up @@ -64,6 +64,12 @@ Return the unique identifier for this started runtime lifecycle.

Return the current ``ACTIVE``, ``STOPPED``, or ``FAILED`` state.

---

### <kbd>property</kbd> supports_streaming

Return whether NVIDIA NeMo Relay ATOF streaming is enabled.



---
Expand Down Expand Up @@ -103,6 +109,30 @@ Run one turn on this runtime.
---


### <kbd>method</kbd> `invoke_stream`

```python
invoke_stream(
input: 'Any' = None,
request: 'RunRequest | None' = None
) → InvokeStream
```

Start one turn and stream raw NeMo Relay ATOF records as they arrive.

``input`` and ``request`` are mutually exclusive. The returned :class:`InvokeStream` yields raw ATOF dictionaries. Await ``stream.result()`` for the terminal normalized :class:`RunResult`.



**Raises:**

- <b>`FabricCapabilityError`</b>: If the runtime was not started with NeMo Relay enabled and ``streaming=True``.
- <b>`FabricConfigError`</b>: If request fields conflict or are not JSON-compatible.
- <b>`FabricStateError`</b>: If another turn or stream is active.

---


### <kbd>method</kbd> `stop`

```python
Expand Down
Loading
Loading