Agent Trajectory Observability Format (ATOF)
Agent Trajectory Observability Format (ATOF)
Use the atof section to write raw Agent Trajectory Observability Format (ATOF)
0.1 events to JSONL files, send them to raw-event collectors, or do both.
File sinks are useful for local debugging, offline inspection, and preserving the canonical event stream before another exporter translates it. Stream sinks send the same events to collectors in near real time. They can also transform dotted object keys before delivery.
plugins.toml Example
Add the following ATOF exporter configuration to plugins.toml:
Before you validate or activate this example, set
NEMO_RELAY_ATOF_AUTH_HEADER to the complete authorization header value, such
as Bearer <token>. NeMo Relay rejects a missing or blank value.
This configuration registers the plugin-managed ATOF exporter and writes one
JSON object per lifecycle event to logs/events.jsonl. It also applies the
replace_dots field-name policy and sends each raw event to the named archive
stream sink.
Fields
The following table describes the top-level ATOF settings:
File Sinks
Each file sink writes the raw event stream to one JSONL file. The following table describes each file sink:
Stream Sinks
Each stream sink receives the same canonical ATOF event that file sinks write. Before sending it, NeMo Relay applies the configured field-name policy and then uses the transport-specific encoding. WebSocket sends a JSON text message, while the HTTP transports send a JSONL record. Each stream runs independently, so a failed connection does not block file output or other streams.
The following table describes each stream sink:
Use header_env for secrets so the configuration stores the environment
variable name instead of the secret value.
preserve sends canonical ATOF field names unchanged. replace_dots replaces
dots in JSON object keys with underscores recursively. If that creates a
duplicate key, NeMo Relay keeps both values and appends _2, _3, and later
suffixes in a deterministic order. Validation rejects any other
sinks[i].field_name_policy value for a stream sink.
The following transports are supported:
http_postsends each event as one JSONL record in an HTTPPOSTrequest withContent-Type: application/x-ndjson. Any2xxresponse is treated as success.websocketopens one connection and sends each event as one JSON text message.ndjsonopens one long-lived HTTP upload and writes each event as one newline-delimited JSON record.
force_flush() flushes file output and drains queued stream events while
keeping stream connections open. shutdown() is terminal: it flushes pending
work, closes the connections, and ignores events that arrive later.
Migrate From Version 1
Observability component configuration now requires version = 2. NeMo Relay
does not translate the removed ATOF fields from version 1.
Make the following changes when you migrate a plugin configuration:
- Move
output_directory,filename, andmodefrom[components.config.atof]into a sink withtype = "file". - Replace each
[[components.config.atof.endpoints]]entry with a[[components.config.atof.sinks]]entry that hastype = "stream". - Keep static request headers in
headers, or useheader_envto read complete header values from environment variables at activation time.
The plugin sends every event to every configured sink. The manual
AtofExporter API is different: each exporter owns one file or stream sink.
Expected Output
Each file sink writes an emitted scope, tool, LLM, middleware, or mark event as one ATOF JSON object per line. Stream sinks encode the event as described in Stream Sinks. For event field semantics, refer to Events.
Coding-agent sessions emit a session.start mark with the logical
metadata.session_id, a Relay-owned UUIDv7 metadata.session_instance_id, and
optional session metadata such as metadata.user_id. Later trace exporters
use these identities for correlation, but ATOF preserves them as raw event
metadata.
ATOF is the raw-event export path. It preserves the canonical event shape; it
does not add semantic extraction, replay policy, scope-owner resolution, or
exporter projection rules. Each owning agent scope starts fresh, and a
compaction mark refreshes it. The first subsequent LLM start annotation
retains complete history. Later starts retain system instructions, the latest
user message, and every following assistant or tool message. With a request
codec annotation, the emitted event input uses the same projection. If that
event-only encoding fails, Relay retains the complete event input and
annotation. The request used for provider execution remains unchanged. Refer to
Events for the complete freshness contract.
Register the plugin before instrumented work starts. Clear it during shutdown so every sink flushes pending events and stream connections close cleanly.
Plugin Configuration
Use plugin configuration when you want NeMo Relay to manage the ATOF exporter lifecycle. The following examples configure and activate the exporter through each supported language binding.
validate() checks only the supplied in-memory object. initialize() also
layers discovered plugins.toml configuration. For effective file-backed
validation, refer to Plugin Configuration Files
and run the gateway with the same configuration path that production uses.
Python
Node.js
Rust
Manual API
Use the manual AtofExporter API when a test or script needs a custom
subscriber name or explicit registration window. A manual exporter owns one
sink. To send the same events to several destinations, create and register one
exporter for each sink or use the observability plugin.
Python
Node.js
Rust
Common Configuration and Runtime Issues
Common configuration and runtime issues include:
- ATOF is enabled without at least one plugin-configured sink.
- A file sink’s
modeis notappendoroverwrite. - A stream sink has an empty
url, an unsupportedtransport, or atimeout_millisvalue of0. - The output directory is not writable at runtime.
nemo-relay doctorcannot deliver its synthetic ATOF mark probe to a configured stream sink.- A file sink is configured in a target that cannot access the native filesystem.