pi.mdx

September 23, 2026 ยท View on GitHub

{/* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 */}

Use the NVIDIA NeMo Fabric nvidia.fabric.pi adapter to run tasks with the Pi SDK in a persistent Node.js process.

Install the Adapter

Pi requires Node.js 22.19.0 or newer.

Install for Consumers

Install the npm adapter in the project that owns the NeMo Fabric configuration, then install the compatible Pi SDK harness version selected by that project:

npm install nemo-fabric-adapters-pi
npm install @earendil-works/pi-ai@^0.84.2 @earendil-works/pi-coding-agent@^0.84.2

The Pi packages are optional peers: installing nemo-fabric-adapters-pi alone does not install the harness. Starting the adapter without compatible Pi packages returns pi_harness_unavailable.

Install for Source Development

For focused Pi development in the NeMo Fabric source tree, install the Pi adapter workspace and its pinned harness from the repository root:

just install-typescript-pi

To install and build all maintained TypeScript workspaces instead, run:

just build-typescript

The full build installs its own dependencies, so you do not need to run just install-typescript-pi first.

Install NeMo Relay

Relay-enabled Pi runs require nemo-relay>=0.9.0,<0.10.0 on PATH. Install it separately from the npm adapter:

pip install "nemo-relay-cli-bin>=0.9.0,<0.10.0"

To select a specific Relay executable instead of relying on PATH, set FABRIC_NEMO_RELAY_COMMAND to its absolute path:

FABRIC_NEMO_RELAY_COMMAND="/absolute/path/to/nemo-relay" uv run python your_app.py

The adapter does not bundle the Relay Pi extension. Obtain the crates/cli/assets/pi-extension directory from the Relay 0.9 release and configure its path as described in the next section.

Configure the Adapter

Select the Pi harness integration and point NeMo Fabric discovery at the adapter descriptor. The following example uses the descriptor from an installed npm package:

from nemo_fabric import DiscoveryConfig, FabricConfig, HarnessConfig
from nemo_fabric import MetadataConfig, ModelConfig, ToolsConfig

config = FabricConfig(
    metadata=MetadataConfig(name="pi-review"),
    discovery=DiscoveryConfig(
        local_paths=[
            "./node_modules/nemo-fabric-adapters-pi/pi.fabric-adapter.json"
        ]
    ),
    harness=HarnessConfig(adapter_id="nvidia.fabric.pi"),
    models={
        "default": ModelConfig(
            provider="nvidia",
            model="nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
            api_key_env="NVIDIA_API_KEY",
            base_url="https://integrate.api.nvidia.com/v1",
        )
    },
    tools=ToolsConfig(enabled=["read"], blocked=[]),
)

For a source build, set discovery.local_paths to adapters/typescript/pi/pi.fabric-adapter.json instead.

The adapter supports one selected model from Pi's catalog, an optional base URL override, replace system instructions, tool policy, explicit skill paths, and explicit local Pi extensions. Set models.<role>.api_key_env to the name of the environment variable that contains the provider credential.

The provider and model pair must already exist in Pi's catalog. A base URL can override a known model endpoint, but it does not define a new provider.

Configure Skills and Extensions

Add normalized skill paths with the Python SDK. NeMo Fabric resolves these paths from the base_dir passed to plan(), doctor(), or run():

config.add_skill_path("./skills/code-review")

Pi extensions are adapter-specific trusted code. Configure extension files relative to environment.workspace:

config.harness.settings["extensions"] = ["extensions/review.ts"]

The adapter loads only the skill and extension paths in the configuration. It does not load ambient Pi resources from the user profile or workspace.

Configure NeMo Relay

Enable Relay with the standard NeMo Fabric configuration and provide the Relay Pi extension as an adapter setting:

config.runtime.artifacts = "./artifacts/pi"
config.enable_relay(output_dir="./artifacts/relay")
config.harness.settings["relay_extension_path"] = (
    "/path/to/NeMo-Relay/crates/cli/assets/pi-extension"
)

Relay requires runtime.artifacts so NeMo Fabric can create the runtime-owned configuration passed to the adapter. The extension path can be absolute or relative to environment.workspace. It can identify a JavaScript or TypeScript file or a Pi extension package directory. Unlike user-configured Pi extensions, the Relay extension does not need to remain inside environment.workspace when an absolute path is used.

When the runtime starts, the adapter validates the Relay 0.9 CLI, writes an explicit plugins.toml, starts a loopback gateway, and loads the extension into the isolated Pi session. The result includes relay_runtime and relay_artifacts in output. The gateway can produce ATOF, ATIF, OpenTelemetry, and OpenInference output from the Relay observability configuration.

The adapter supports one Relay-enabled Pi runtime per adapter process because the Relay 0.9 extension receives its gateway and upstream configuration through process environment variables. NeMo Fabric starts each runtime in a separate adapter process, so concurrent NeMo Fabric runtimes remain isolated. Direct embedders must likewise place concurrent Relay-enabled Pi runtimes in separate processes.

The released Relay 0.9.0 Pi extension emits turn_end and closes the per-turn scope, but its local ATIF trajectory remains attached to Pi's session-level agent scope. Relay therefore finalizes that trajectory only after the Pi session closes. It is not included in the invocation's relay_artifacts. The adapter does not wait for local ATIF during invocation. It collects per-invocation artifacts such as ATOF and returns the result. After runtime shutdown, retrieve the finalized file directly from the ATIF output directory. For the configuration above and the default filename template, it is written to ./artifacts/relay/<runtime_id>/trajectory-<session_id>.atif.json. Invocation results do not prevent subsequent turns.

Session, turn, and tool telemetry does not depend on model redirection. Model telemetry is available only when Relay supports the selected model API and the gateway upstream matches the model endpoint. Relay records a skipped redirect as a model_redirect mark with the reason in configured ATOF artifacts. Pi emits its startup redirect marks before NeMo Fabric registers an invocation, so they are not included in the per-invocation records from invoke_stream().

Install the matching collector for the embedded streaming path:

pip install "nemo-fabric[streaming]"

Start the runtime with streaming enabled to consume live model-turn ATOF records for successful Relay redirects in one Pi invocation:

from nemo_fabric import Fabric

async with await Fabric().start_runtime(config, streaming=True) as runtime:
    stream = runtime.invoke_stream(input="Review the latest patch")
    async for record in stream:
        print(record)
    result = await stream.result()

The terminal RunResult remains separate from the ATOF records. Fully consume each stream, or call await stream.aclose() if iteration stops early, before starting another invocation. The same runtime can then alternate invoke_stream() and invoke() calls. The embedded collector serializes both methods behind one Pi invocation lease. Streaming capture begins at the first Pi turn_start and closes at agent_settled. If Relay output is interrupted or late, the collector waits for a bounded interval, then admits the next native invocation and uses Pi's cumulative turn count to discard ambiguous delayed records until a higher turn_start arrives. This can thin the ATOF stream but does not block later invocations. Increase completion_wait_timeout from its one-second default when Relay delivery can take longer. Use the default embedded collector for Pi streaming. The Pi extension does not attach NeMo Fabric request IDs, so start_runtime(..., streaming=True, launch_collector=False) cannot correlate its records through an externally managed collector.

This Relay-backed path runs the adapter's ordinary invoke operation. It is independent of native OpenAI streaming, so the adapter descriptor's capabilities.streaming value remains false.

Configure a NeMo Fabric Tool Definition

Pi accepts trusted local JavaScript and TypeScript tool factories. The normalized definition uses kind: "module"; ref is relative to the NeMo Fabric workspace and may include a named export after #.

Create tools/review-context.js in the configured workspace:

export function createTool({ name, settings }) {
  return {
    name,
    label: "Review Context",
    description: "Return configured context for a code review.",
    parameters: { type: "object", properties: {} },
    async execute() {
      return {
        content: [{ type: "text", text: JSON.stringify(settings) }],
        details: {},
      };
    },
  };
}

Register and enable the tool:

config.add_tool_definition(
    "review_context",
    kind="module",
    ref="tools/review-context.js#createTool",
    settings={"format": "brief"},
)
config.tools.enabled = ["read", "review_context"]

The factory receives { name, settings, workspace } and returns a Pi ToolDefinition with the same name. Tool modules and explicit Pi extensions run as trusted code in the adapter process. The adapter rejects paths outside the workspace and duplicate names across built-ins, NeMo Fabric definitions, and extensions.

Understand the Runtime Lifecycle

Each NeMo Fabric runtime owns one in-memory Pi session in a persistent Node.js adapter process. Ordered invocations reuse that session and its conversation history until the runtime stops. Start a new runtime to change the model, skills, extensions, custom tools, or workspace.

Current Limitations

The current adapter does not expose MCP, native OpenAI streaming, caller-driven cancellation, or remote-service execution. Enabling a third-party extension can add extension-defined skills, prompts, or themes even though ambient Pi resources remain disabled. The Relay extension does not add those resources.

The Relay Pi hook route is available only on the loopback gateway and does not authenticate hook posts. Treat the loopback port as a local trust boundary.