> 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.

# Python Quick Start

> Instrument one Python 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:

* Python 3.11 or newer
* `uv`

## Install

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

```bash
mkdir relay-python-quickstart
cd relay-python-quickstart
uv init
uv add nemo-relay==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.py` beside `plugins.toml`:

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

import asyncio
import json
from pathlib import Path
from typing import TypedDict

import nemo_relay


class SearchArgs(TypedDict):
    query: str


async def search(args: SearchArgs) -> nemo_relay.ToolExecutionResult[dict[str, str]]:
    return nemo_relay.ToolExecutionResult({"marker": args["query"]})


async def model(request: nemo_relay.LLMRequest) -> dict[str, object]:
    return {"content": "RELAY_QUICKSTART_RESPONSE_OK"}


async def main() -> None:
    async with nemo_relay.plugin.activate({}, "plugins.toml"):
        try:
            with nemo_relay.scope.scope("demo-agent", nemo_relay.ScopeType.Agent) as handle:
                nemo_relay.scope.event("initialized", handle=handle, data={"binding": "python"})

                tool_result = await nemo_relay.tools.execute(
                    "emit_marker",
                    {"query": "RELAY_QUICKSTART_TOOL_OK"},
                    search,
                    handle=handle,
                )
                llm_result = await nemo_relay.llm.execute(
                    "demo-provider",
                    nemo_relay.LLMRequest(
                        {},
                        {
                            "messages": [
                                {
                                    "role": "user",
                                    "content": "RELAY_QUICKSTART_RESPONSE_OK",
                                }
                            ]
                        },
                    ),
                    model,
                    handle=handle,
                )

                print(tool_result.result["marker"])
                print(llm_result["content"])
        finally:
            await nemo_relay.subscribers.flush_async()

    events = [json.loads(line) for line in Path("logs/events.jsonl").read_text().splitlines()]
    if not events or any(
        not isinstance(event, dict) or event.get("atof_version") != "0.1" or event.get("kind") not in {"scope", "mark"}
        for event in events
    ):
        raise RuntimeError("Expected a nonempty ATOF 0.1 event stream")
    starts = {
        event["uuid"]: event for event in events if event["kind"] == "scope" and event.get("scope_category") == "start"
    }
    closed = [
        event
        for event in events
        if event["kind"] == "scope"
        and event.get("scope_category") == "end"
        and event.get("uuid") in starts
        and all(event.get(key) == starts[event["uuid"]].get(key) for key in ("name", "category", "parent_uuid"))
    ]
    if not {"agent", "tool", "llm"} <= {event.get("category") for event in closed}:
        raise RuntimeError("Expected paired agent, tool, and LLM lifecycle records")
    print("RELAY_QUICKSTART_EVENTS_OK")


asyncio.run(main())

```

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

```bash
uv run python quickstart.py
```

## 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#python)
* [Instrument Application Code](/nemo/relay/v0.11.0/instrument-applications/code-examples)