Skip to navigation

Node.js Quick Start

View as Markdown

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.

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
1

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.

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

Configure

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

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"

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

3

Run

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

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:

node quickstart.js
4

Verify

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

RELAY_QUICKSTART_TOOL_OK
RELAY_QUICKSTART_RESPONSE_OK
RELAY_QUICKSTART_EVENTS_OK

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

For More Details

For more details, refer to: