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

# Agent Trajectory Observability Format (ATOF)

Use the `atof` section when you want the raw Agent Trajectory Observability
Format (ATOF) `0.1` event stream written as JSONL.

ATOF JSONL export is useful for local debugging, offline inspection, and
preserving the canonical event stream before it is translated into another
format.

## `plugins.toml` Example

```toml
version = 1

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

[components.config]
version = 1

[components.config.atof]
enabled = true
output_directory = "logs"
filename = "events.jsonl"
mode = "overwrite"
```

This configuration registers the plugin-managed ATOF exporter and writes one
JSON object per lifecycle event to `logs/events.jsonl`.

## Fields

| Field              | Default                                 | Notes                                |
| ------------------ | --------------------------------------- | ------------------------------------ |
| `enabled`          | `false`                                 | Must be `true` to write events.      |
| `output_directory` | Current working directory               | Directory containing the JSONL file. |
| `filename`         | Timestamped `nemo-relay-events-*.jsonl` | Explicit output filename.            |
| `mode`             | `append`                                | `append` or `overwrite`.             |

## Expected Output

Each emitted scope, tool, LLM, middleware, or mark event is written as one ATOF
JSON object per line. For event field semantics, see
[Events](/about-nemo-relay/concepts/events).

Register the plugin before instrumented work starts and clear it during
shutdown so file handles flush.

## Plugin Configuration

Use plugin configuration when the application should let NeMo Relay own the ATOF
exporter lifecycle.

```python
from nemo_relay import plugin
from nemo_relay.observability import AtofConfig, ComponentSpec, ObservabilityConfig

config = plugin.PluginConfig(
    components=[
        ComponentSpec(
            ObservabilityConfig(
                atof=AtofConfig(
                    enabled=True,
                    output_directory="logs",
                    filename="events.jsonl",
                    mode="overwrite",
                )
            )
        )
    ]
)

report = plugin.validate(config)
if any(diagnostic["level"] == "error" for diagnostic in report["diagnostics"]):
    raise RuntimeError(report["diagnostics"])

await plugin.initialize(config)
try:
    # Run instrumented application work here.
    pass
finally:
    plugin.clear()
```

```js
const plugin = require("nemo-relay-node/plugin");
const observability = require("nemo-relay-node/observability");

await plugin.initialize({
  version: 1,
  components: [
    observability.ComponentSpec({
      version: 1,
      atof: observability.atofConfig({
        enabled: true,
        output_directory: "logs",
        filename: "events.jsonl",
        mode: "overwrite",
      }),
    }),
  ],
});

try {
  // Run instrumented application work here.
} finally {
  plugin.clear();
}
```

```rust
use nemo_relay::observability::plugin_component::{
    AtofSectionConfig, ComponentSpec, ObservabilityConfig,
};
use nemo_relay::plugin::{initialize_plugins, validate_plugin_config, PluginConfig};

let component = ComponentSpec::new(ObservabilityConfig {
    atof: Some(AtofSectionConfig {
        enabled: true,
        output_directory: Some("logs".into()),
        filename: Some("events.jsonl".into()),
        mode: "overwrite".into(),
    }),
    ..ObservabilityConfig::default()
});

let config = PluginConfig {
    version: 1,
    components: vec![component.into()],
    policy: Default::default(),
};

let report = validate_plugin_config(&config);
assert!(!report.has_errors());

let active = initialize_plugins(config).await?;
```

## Manual API

Use the manual `AtofExporter` API when a test or script needs a custom
subscriber name or explicit registration window.

```python
from nemo_relay import AtofExporter, AtofExporterConfig, AtofExporterMode

config = AtofExporterConfig()
config.output_directory = "logs"
config.filename = "events.jsonl"
config.mode = AtofExporterMode.Overwrite

exporter = AtofExporter(config)
exporter.register("atof-exporter")

# Run instrumented application work here.

exporter.force_flush()
exporter.deregister("atof-exporter")
exporter.shutdown()
```

```js
const { AtofExporter } = require("nemo-relay-node");

const exporter = new AtofExporter({
  outputDirectory: "logs",
  filename: "events.jsonl",
  mode: "overwrite",
});
exporter.register("atof-exporter");

try {
  // Run instrumented application work here.

  exporter.forceFlush();
} finally {
  exporter.deregister("atof-exporter");
  exporter.shutdown();
}
```

```rust
use nemo_relay::observability::atof::{
    AtofExporter, AtofExporterConfig, AtofExporterMode,
};

let config = AtofExporterConfig::new()
    .with_output_directory("logs")
    .with_filename("events.jsonl")
    .with_mode(AtofExporterMode::Overwrite);
let exporter = AtofExporter::new(config)?;
exporter.register("atof-exporter")?;

// Run instrumented application work here.

exporter.force_flush()?;
let _ = exporter.deregister("atof-exporter")?;
exporter.shutdown()?;
```

## Common Validation Failures

* `mode` is not `append` or `overwrite`.
* The output directory is not writable at runtime.
* ATOF is enabled in a target that cannot access the native filesystem.