Agent Trajectory Observability Format (ATOF)

View as Markdown

Use the atof section when you want the raw Agent Trajectory Observability Format (ATOF) 0.1 event stream written as JSONL or streamed to raw-event collectors.

ATOF JSONL export is useful for local debugging, offline inspection, and preserving the canonical event stream before it is translated into another format. Streaming endpoints are useful when a collector wants the same raw event shape in near real time.

plugins.toml Example

Add the following ATOF exporter configuration to plugins.toml:

1version = 1
2
3[[components]]
4kind = "observability"
5enabled = true
6
7[components.config]
8version = 1
9
10[components.config.atof]
11enabled = true
12output_directory = "logs"
13filename = "events.jsonl"
14mode = "overwrite"
15
16[[components.config.atof.endpoints]]
17url = "http://localhost:8080/events"
18transport = "http_post"
19timeout_millis = 3000
20field_name_policy = "replace_dots"

This configuration registers the plugin-managed ATOF exporter and writes one JSON object per lifecycle event to logs/events.jsonl. It also sends each raw ATOF event to the configured endpoint.

Fields

The following table describes the top-level ATOF settings:

FieldDefaultNotes
enabledfalseMust be true to write events.
output_directoryCurrent working directoryDirectory containing the JSONL file.
filenameTimestamped nemo-relay-events-*.jsonlExplicit output filename.
modeappendappend or overwrite.
endpoints[]Optional streaming destinations. File output remains active when endpoints are configured.

Streaming Endpoints

Each endpoint receives the same raw ATOF JSON object that the file exporter writes as one JSONL line. Endpoints are independent: a failed endpoint is skipped or retried without blocking file output or other endpoints.

The following table describes each streaming endpoint:

FieldDefaultNotes
urlRequiredEndpoint URL.
transporthttp_posthttp_post, websocket, or ndjson.
headers{}String-to-string headers for requests or handshakes.
timeout_millis3000Per-endpoint timeout. Must be greater than 0.
field_name_policypreserveField-name handling before Relay sends endpoint events. Accepted values are preserve and replace_dots.

preserve sends canonical ATOF field names unchanged. replace_dots replaces dots in JSON object keys with underscores recursively. If replacement produces a collision, Relay keeps both values and deterministically appends _2, _3, and later suffixes as needed. Validation rejects any other endpoints[i].field_name_policy value.

  • http_post sends each event as one JSONL record in an HTTP POST request with Content-Type: application/x-ndjson. Any 2xx response is treated as success.
  • websocket opens one connection and sends each event as one JSON text message.
  • ndjson opens one long-lived HTTP upload and writes each event as one newline-delimited JSON record.

force_flush() flushes file output and drains queued endpoint events without closing streaming connections. shutdown() is terminal: it flushes pending work, closes streaming connections, and makes later events no-op.

Expected Output

Each emitted scope, tool, LLM, middleware, or mark event is written as one ATOF JSON object per line. For event field semantics, refer to Events.

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.

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. The following examples configure and activate the ATOF 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.

1import asyncio
2
3from nemo_relay import plugin
4from nemo_relay.observability import (
5 AtofConfig,
6 AtofEndpointConfig,
7 ComponentSpec,
8 ObservabilityConfig,
9)
10
11config = plugin.PluginConfig(
12 components=[
13 ComponentSpec(
14 ObservabilityConfig(
15 atof=AtofConfig(
16 enabled=True,
17 output_directory="logs",
18 filename="events.jsonl",
19 mode="overwrite",
20 endpoints=[
21 AtofEndpointConfig(
22 url="http://localhost:8080/events",
23 transport="http_post",
24 )
25 ],
26 )
27 )
28 )
29 ]
30)
31
32report = plugin.validate(config)
33if any(diagnostic["level"] == "error" for diagnostic in report["diagnostics"]):
34 raise RuntimeError(report["diagnostics"])
35
36async def main():
37 await plugin.initialize(config)
38 try:
39 # Run instrumented application work here.
40 pass
41 finally:
42 plugin.clear()
43
44asyncio.run(main())

Manual API

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

1from nemo_relay import AtofEndpointConfig, AtofExporter, AtofExporterConfig, AtofExporterMode
2
3config = AtofExporterConfig()
4config.output_directory = "logs"
5config.filename = "events.jsonl"
6config.mode = AtofExporterMode.Overwrite
7config.endpoints = [
8 AtofEndpointConfig("http://localhost:8080/events", transport="http_post")
9]
10
11exporter = AtofExporter(config)
12exporter.register("atof-exporter")
13
14# Run instrumented application work here.
15
16exporter.force_flush()
17exporter.deregister("atof-exporter")
18exporter.shutdown()

Common Configuration and Runtime Issues

  • mode is not append or overwrite.
  • endpoints[i].url is empty, endpoints[i].transport is not supported, or endpoints[i].timeout_millis is 0.
  • The output directory is not writable at runtime.
  • nemo-relay doctor cannot deliver its synthetic ATOF mark probe to a configured endpoint.
  • ATOF is enabled in a target that cannot access the native filesystem.