> This page is for NeMo Relay, version v0.11.0.
> For other versions, use one of these documentation indexes:
> - Latest (v0.10.4) (default): https://docs.nvidia.com/nemo/relay/latest/llms.txt
> - dev: https://docs.nvidia.com/nemo/relay/dev/llms.txt
> - v0.11.0: https://docs.nvidia.com/nemo/relay/v0.11.0/llms.txt
> - v0.10.4: https://docs.nvidia.com/nemo/relay/v0.10.4/llms.txt
> - v0.9.4: https://docs.nvidia.com/nemo/relay/v0.9.4/llms.txt
> - v0.8.4: https://docs.nvidia.com/nemo/relay/v0.8.4/llms.txt
> - v0.7.3: https://docs.nvidia.com/nemo/relay/v0.7.3/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/nemo/relay/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/nemo/relay/_mcp/server.

# Node.js Quick Start

> Instrument one Node.js workflow and write its Relay events to a local ATOF file.

## Goal

Instrument one scope, tool call, and large language model (LLM) call with NeMo Relay, then verify the
result in a local Agent Trajectory Observability Format (ATOF) file.

> **Note**
>
> This example uses local tool and model callbacks. It does not require an
> inference key or a live model.

## Prerequisites

Before you start, make sure you have:

* Node.js 24 or newer
* npm

## Install

Create a project and install the NeMo Relay version for these docs.
For an existing Node.js project, run only the `npm install` command from its directory.

```bash
mkdir relay-node-quickstart
cd relay-node-quickstart
npm init -y
npm install --save-exact nemo-relay-node@0.11.0
```

## Configure

Save the following configuration as `plugins.toml` in your project directory:

```toml title={"plugins.toml"}
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

version = 1

[policy]
unknown_component = "error"
unknown_field = "error"
unsupported_value = "error"

[[components]]
kind = "observability"
enabled = true

[components.config]
version = 4
enable_full_payloads = false

[components.config.atof]
enabled = true

[[components.config.atof.sinks]]
type = "file"
output_directory = "logs"
filename = "events.jsonl"
mode = "overwrite"

```

> **Warning**
>
> ATOF output can contain sensitive prompts, responses, tool arguments, and tool
> results. Protect `logs/events.jsonl` even when full payload export is disabled.

## Run

Save this file as `quickstart.js` beside `plugins.toml`:

```js title={"quickstart.js"}
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

const fs = require("node:fs");
const relay = require("nemo-relay-node");
const plugin = require("nemo-relay-node/plugin");

async function main() {
  const activation = await plugin.initialize(plugin.defaultConfig(), "plugins.toml");

  try {
    await relay.withScope("demo-agent", relay.ScopeType.Agent, async (handle) => {
      relay.event("initialized", handle, { binding: "node" }, null);

      const toolResult = await relay.toolCallExecute(
        "emit_marker",
        { marker: "RELAY_QUICKSTART_TOOL_OK" },
        (args) => ({ result: { marker: args.marker } }),
        handle,
        null,
        null,
        null,
      );

      const llmResult = await relay.llmCallExecute(
        "demo-provider",
        new relay.LlmRequest(
          {},
          { messages: [{ role: "user", content: "RELAY_QUICKSTART_RESPONSE_OK" }] },
        ),
        () => ({ content: "RELAY_QUICKSTART_RESPONSE_OK" }),
        handle,
        null,
        null,
        null,
        null,
      );

      console.log(toolResult.result.marker);
      console.log(llmResult.content);
    });
  } finally {
    try {
      await relay.flushSubscribers();
    } finally {
      await activation.close();
    }
  }

  const eventText = fs.readFileSync("logs/events.jsonl", "utf8").trim();
  if (!eventText) throw new Error("Relay event file is empty");
  const events = eventText.split(/\r?\n/).map((line) => JSON.parse(line));
  if (events.some((event) => !event || event.atof_version !== "0.1" ||
      !["scope", "mark"].includes(event.kind))) {
    throw new Error("Expected ATOF 0.1 records");
  }
  const starts = new Map(events.filter((event) =>
    event.kind === "scope" && event.scope_category === "start",
  ).map((event) => [event.uuid, event]));
  const closed = events.filter((event) => {
    const start = starts.get(event.uuid);
    return event.kind === "scope" && event.scope_category === "end" && start &&
      ["name", "category", "parent_uuid"].every((key) => event[key] === start[key]);
  });
  if (!["agent", "tool", "llm"].every((category) =>
      closed.some((event) => event.category === category))) {
    throw new Error("Expected paired agent, tool, and LLM lifecycle records");
  }
  console.log("RELAY_QUICKSTART_EVENTS_OK");
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

```

Run it from the project directory so the relative output path resolves there:

```bash
node quickstart.js
```

## Verify

The program checks the event file for paired agent, tool, and LLM lifecycle
records. A successful run prints these markers in order:

```text
RELAY_QUICKSTART_TOOL_OK
RELAY_QUICKSTART_RESPONSE_OK
RELAY_QUICKSTART_EVENTS_OK
```

> **Note**
>
> The file stores ATOF events as newline-delimited JSON (JSONL).

## For More Details

For more details, refer to:

* [Installation Overview](/nemo/relay/v0.11.0/getting-started/installation#nodejs)
* [Instrument Application Code](/nemo/relay/v0.11.0/instrument-applications/code-examples)