Agent Trajectory Interchange Format (ATIF)

View as Markdown

Use the atif section when you want one Agent Trajectory Interchange Format (ATIF) trajectory artifact per top-level Agent scope or supported coding-agent turn scope.

The plugin-managed ATIF dispatcher creates a scope-local exporter for each top-level Agent scope and each root-child custom scope marked nemo_relay_scope_role = "turn". It writes the trajectory when that root scope ends. Nested Agent scopes remain in the parent trajectory.

plugins.toml Example

Add the following ATIF exporter configuration to plugins.toml:

1version = 1
2
3[[components]]
4kind = "observability"
5enabled = true
6
7[components.config]
8version = 1
9
10[components.config.atif]
11enabled = true
12agent_name = "Planner"
13agent_version = "1.0.0"
14model_name = "unknown"
15output_directory = "logs"
16filename_template = "trajectory-{session_id}.json"

This configuration writes a trajectory file such as logs/trajectory-<scope-uuid>.json for each top-level Agent scope or supported coding-agent turn scope.

Fields

The following table describes the top-level ATIF settings:

FieldDefaultNotes
enabledfalseMust be true to write trajectories.
agent_nameNeMo RelayAgent metadata written into the trajectory.
agent_versionNeMo Relay crate versionAgent version metadata.
model_nameunknownDefault model metadata when no call-level model is present.
tool_definitionsOmittedOptional ATIF tool metadata.
extraOmittedOptional ATIF agent metadata.
output_directoryCurrent working directoryDirectory containing trajectory files. Ignored when storage is non-empty.
filename_templatenemo-relay-atif-{session_id}.jsonMust contain {session_id}.
storageOmittedOptional list of remote storage destinations. When non-empty, trajectories are uploaded to every configured backend instead of being written locally. Refer to Remote Storage.

Remote Storage

Use storage when local trace files are not durable across sessions, such as in sandboxed runtimes. When storage is non-empty, Relay uploads each completed trajectory to every configured backend instead of writing a local file. output_directory is ignored.

Each storage entry is tagged with a type discriminator so additional backends can be added without breaking existing configs. S3-compatible object storage and HTTP endpoints are supported.

S3-Compatible Storage

Configure an S3-compatible destination as follows:

1[components.config.atif]
2enabled = true
3filename_template = "trajectory-{session_id}.json"
4
5[[components.config.atif.storage]]
6type = "s3"
7bucket = "nemo-relay-traces"
8key_prefix = "openshell/"

HTTP Endpoint Storage

Configure an HTTP destination as follows:

1[components.config.atif]
2enabled = true
3filename_template = "trajectory-{session_id}.json"
4
5[[components.config.atif.storage]]
6type = "http"
7endpoint = "https://observability.example.com/atif"
8timeout_millis = 3000
9
10[components.config.atif.storage.headers]
11x-team = "agent-platform"
12
13[components.config.atif.storage.header_env]
14authorization = "NEMO_RELAY_ATIF_HTTP_AUTH"

The HTTP backend sends one POST per completed trajectory. The request body is the rendered ATIF JSON file with content-type: application/json. NeMo Relay also sets x-nemo-relay-atif-filename and x-nemo-relay-atif-session-id headers so receivers can keep the same object identity that local files and S3 uploads use.

HTTP 2xx responses are treated as success. Any non-2xx response or transport error records the endpoint as unhealthy and skips it for later trajectories. Other configured destinations continue to receive writes.

The following table describes HTTP storage settings:

FieldDefaultNotes
endpointrequiredDestination http:// or https:// URL.
headers{}Static request headers.
header_env{}Header names mapped to environment variable names containing secret values.
timeout_millis3000Per-request timeout in milliseconds. Must be positive.

Multiple Destinations

Add another [[components.config.atif.storage]] table to send each trajectory to every configured destination. For example, this configuration uses an in-cluster MinIO target and a remote HTTP endpoint:

1[components.config.atif]
2enabled = true
3filename_template = "trajectory-{session_id}.json"
4
5[[components.config.atif.storage]]
6type = "s3"
7bucket = "nemo-relay-traces"
8key_prefix = "openshell/"
9
10[[components.config.atif.storage]]
11type = "http"
12endpoint = "https://observability.example.com/atif"

Connection Fields

By default, Relay reads credentials, region, and endpoint URL from standard AWS environment variables. You can also set non-secret connection fields directly in the configuration. This lets one file describe multiple S3-compatible destinations, such as an in-cluster MinIO target and a remote AWS target:

The following table describes S3-compatible connection settings:

FieldDefaultNotes
bucketrequiredDestination bucket name.
key_prefix""Optional prefix applied to every object. A trailing / is added if missing.
access_key_idAWS_ACCESS_KEY_IDInline static access key ID.
regionAWS_REGIONBucket region.
endpoint_urlAWS_ENDPOINT_URLEndpoint override for S3-compatible storage.
allow_httpAWS_ALLOW_HTTPSet to true when targeting an HTTP endpoint.

Explicit fields take precedence; anything left unset falls back to the matching AWS_* environment variable.

Secret Credential Fields

Secret values stay out of checked-in config files. Each secret field carries a _var suffix and holds the name of an environment variable that contains the secret value. The plugin validates the name during initialization:

The following table describes secret credential settings:

FieldEnv Var FallbackNotes
secret_access_key_varAWS_SECRET_ACCESS_KEYName of the env var that holds the static secret key.
session_token_varAWS_SESSION_TOKENName of the env var that holds the optional STS session token.

Relay uploads each trajectory under {key_prefix}{rendered_filename}. It renders filename_template the same way it does for local files, so a local→remote transition keeps object names stable. Relay adds a trailing / to key_prefix when one is missing.

If an upload fails for a destination, the ATIF exporter records that destination as unhealthy and skips it for later trajectories. Other destinations continue to receive writes. The exporter reports fatal dispatcher failures, such as trajectory serialization failures, during plugin teardown. It isolates per-destination sink failures to the failed sink.

Expected Output

The exporter translates NeMo Relay lifecycle events into ATIF v1.7 trajectory data. LLM start and end events become model steps, tool events become tool calls and observations, and scope nesting contributes lineage metadata. Nested agent scopes are embedded in the parent file as subagent_trajectories and referenced from parent observation results with subagent_trajectory_ref.trajectory_id. The reference points to the embedded child trajectory by ID so consumers can validate the parent and child as one single-file ATIF v1.7 artifact.

The plugin writes each trajectory when its top-level Agent scope or supported coding-agent turn scope closes. If the plugin is cleared while that root scope is still open, teardown flushes the partial trajectory.

To correlate ATIF with OpenTelemetry or OpenInference traces from the same run, join on NeMo Relay UUIDs. The plugin-managed ATIF session_id is the top-level trajectory root scope UUID. Each step’s extra.ancestry.function_id is the event UUID, and extra.ancestry.parent_id is the parent event UUID. Trace spans expose the same values as nemo_relay.uuid and nemo_relay.parent_uuid attributes.

When present, a step’s metrics can carry prompt_tokens, completion_tokens, cached_tokens (cache read + write), and cost_usd (USD only); the trajectory final_metrics sums the metrics present on its steps as total_*. Refer to Token and Cost Field Semantics for the full mapping, including how ATIF sources these values from the codec annotation and the raw payload.

ATIF is a trajectory projection over NeMo Relay events. It should preserve the meaning of scope parentage, event UUIDs, codec annotations, and exporter-local lineage rules without becoming the source of truth for runtime ownership, middleware ordering, or provider payload decoding.

Plugin Configuration

Use plugin configuration when the application should let NeMo Relay own the ATIF dispatcher lifecycle. The following examples configure and activate the ATIF 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 AtifConfig, ComponentSpec, ObservabilityConfig
5
6config = plugin.PluginConfig(
7 components=[
8 ComponentSpec(
9 ObservabilityConfig(
10 atif=AtifConfig(
11 enabled=True,
12 agent_name="Planner",
13 agent_version="1.0.0",
14 model_name="unknown",
15 output_directory="logs",
16 filename_template="trajectory-{session_id}.json",
17 )
18 )
19 )
20 ]
21)
22
23report = plugin.validate(config)
24if any(diagnostic["level"] == "error" for diagnostic in report["diagnostics"]):
25 raise RuntimeError(report["diagnostics"])
26
27async def main():
28 await plugin.initialize(config)
29 try:
30 # Run instrumented application work here.
31 pass
32 finally:
33 plugin.clear()
34
35asyncio.run(main())

Manual API

Use the manual AtifExporter API when you need explicit collection boundaries or one exporter object per run.

1from nemo_relay import AtifExporter
2
3exporter = AtifExporter("session-1", "agent", "1.0.0", model_name="demo-model")
4exporter.register("atif-exporter")
5
6# Run instrumented application work here.
7
8trajectory = exporter.export()
9exporter.deregister("atif-exporter")
10exporter.clear()

Common Configuration and Runtime Issues

  • filename_template does not contain {session_id}.
  • The output directory is not writable at runtime.
  • Tool definitions or extra metadata are not JSON-compatible.
  • The application never opens a top-level Agent scope or a supported coding-agent turn scope, so no trajectory file is created.
  • storage[i].type is unknown or storage[i].bucket is empty for some entry.
  • storage is non-empty in a build that was compiled without the atif-storage feature.