> 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 full documentation content, see https://docs.nvidia.com/nemo/relay/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/nemo/relay/_mcp/server.

# Subscribers

This page explains how subscribers consume lifecycle events without changing runtime
execution.

## What Subscribers Are

Subscribers are consumers of the NeMo Relay event stream. They receive emitted
lifecycle events and use them for observation, forwarding, export, or analysis.
On native Rust, Python, Node.js, and FFI surfaces, event-producing calls enqueue
subscriber delivery on a process-wide background dispatcher and return without
waiting for subscriber callbacks or exporter work.

## How Subscribers Relate to Events

Events describe what happened. Subscribers are the components that watch those
events.

That separation matters:

* The runtime can emit one canonical event stream
* Native event calls stay non-blocking for subscriber work
* Many subscribers can consume that same stream
* Observability behavior stays downstream from execution semantics

## Registration Levels

Middleware and subscribers can be registered at different levels depending on their
lifetime and visibility.

### Global Subscribers

Global subscribers remain active process-wide until they are removed.

### Scope-Local Subscribers

Scope-local subscribers are owned by one active scope and disappear when that
scope closes.

Deregistering a subscriber affects future emissions. Events that were already
emitted carry a subscriber snapshot, so queued callbacks from that snapshot may
still run after deregistration.

### Plugin-Installed Subscribers

Plugins can install subscribers as reusable, configuration-driven runtime
components.

## What Subscribers Consume

Subscribers consume the canonical event stream. They do not define the event
model. They react to it.

This lets plain subscribers, exporters, and tracing adapters share one runtime
source of truth.

## Common Subscriber Roles

Subscribers are commonly used for in-process observation, counters, debugging, and
exporter handoff.

### In-Process Observation

Some subscribers stay inside the process and power custom logging, analytics, or
debugging logic.

#### Host Integration Event JSON

For host integrations that need a serialized event payload, use the event
object's canonical JSON helpers instead of reconstructing payloads from native
attributes. Python subscribers can call `event.to_dict()` or `event.to_json()`
from the callback while still using the normal subscriber registration API.

This pattern is useful when an agent runtime, framework adapter, or plugin host
already has its own lifecycle hooks but wants NeMo Relay to be the shared
telemetry representation. The host integration maps those hooks into NeMo Relay
scopes, LLM calls, tool calls, or marks. NeMo Relay emits the canonical ATOF event
stream, and each subscriber chooses whether to consume the native event object,
the canonical JSON helper, or an exporter-specific translation.

```mermaid
flowchart
    Host[Host Integration]

    subgraph NeMoFlow[NeMo Relay]
        direction TB
        Binding[Binding API]
        Core[Rust Core Runtime]
        Events[Canonical ATOF Event Stream]
        Dispatcher[Async Subscriber Dispatcher]
        Observer[In-Process Subscriber]
        Json[Canonical Event JSON]
        Exporters[Exporter Subscribers]
        Backends[JSONL / ATIF / OTLP]

        Binding -->|emits scopes, tools, LLMs, marks| Core
        Core --> Events
        Events --> Dispatcher
        Dispatcher --> Observer
        Observer -->|to_dict / to_json / JSON| Json
        Dispatcher --> Exporters
        Exporters --> Backends
    end

    Host -->|maps lifecycle hooks| Binding
    Json -. host consumes canonical telemetry .-> Host
```

The important boundary is that subscribers do not define the event schema. They
receive the runtime event and may serialize it through the binding helper when
they need a stable JSON payload. Exporter subscribers, such as the ATOF JSONL
exporter, consume the same event stream and serialize the same canonical event
shape for their target backend.

Native subscribers are invoked by one process-wide worker thread in FIFO event
order and subscriber snapshot order. WebAssembly keeps its current synchronous
delivery behavior because this ticket does not add thread-based dispatch there.

## Waiting for Delivery

Use the flush API when application shutdown, tests, or examples must observe
side effects from subscriber callbacks that have already been queued:

* Rust: `nemo_relay::api::subscriber::flush_subscribers()?`
* Python: `nemo_relay.subscribers.flush()`
* Node.js: `flushSubscribers()`, then await an event-loop tick for JavaScript
  callback side effects
* FFI: `nemo_relay_flush_subscribers()`
* WebAssembly: `flushSubscribers()` succeeds as a no-op

### Forwarding and Export

Some subscribers translate the event stream into external formats or transport
it to another system.

### Analytics and Diagnostics

Some subscribers derive measurements, trajectories, or diagnostics from the
event stream without affecting execution behavior.

## Built-In Subscriber Examples

These examples show how built-in subscriber patterns relate to custom subscribers and
exporters.

### Custom Subscribers

A plain custom subscriber is the right choice when you want in-process handling
of the canonical event stream.

### Agent Trajectory Interchange Format (ATIF) Exporter

The [Agent Trajectory Interchange Format (ATIF) exporter](/observability-plugin/atif)
collects lifecycle events and emits trajectory artifacts for offline analysis,
replay, or debugging.

### Agent Trajectory Observability Format (ATOF) JSONL Exporter

The [Agent Trajectory Observability Format (ATOF) JSONL exporter](/observability-plugin/atof)
writes the canonical event stream to a native filesystem path as one raw ATOF
event per line.

### OpenTelemetry Subscriber

The OpenTelemetry subscriber maps runtime events into OTLP traces for tracing
backends.

### OpenInference Subscriber

The OpenInference subscriber maps runtime events into OTLP traces using
OpenInference semantics for model-centric observability.

Detailed setup, configuration, and API shape for these subscribers belongs in
[Observability](/observability-plugin/about).
For configuration-driven setup, use the built-in
[`observability` plugin](/observability-plugin/configuration)
to install ATOF, ATIF, OpenTelemetry, and OpenInference subscribers from one
plugin component.

## Practical Guidance

Use these practices when applying the concept in application or integration code.

* Use a plain subscriber when you want in-process custom behavior.
* Flush subscribers before asserting on printed output, captured lists, or files
  written by subscriber callbacks.
* Use `event.to_dict()` or `event.to_json()` when a host runtime or exporter
  needs the canonical event JSON shape in-process.
* Use a scope-local subscriber when the observation should disappear with the
  owning scope.
* Use a plugin-installed subscriber when the behavior should be reusable and
  config-driven.
* Use an exporter-oriented subscriber when the event stream should leave the
  process.