NylorunDocsBeta
RunSessions

Traces

Read a turn as an OpenTelemetry trace and join it to your app's trace.

const trace = await session.trace(turnId);

for (const { scopeSpans } of trace.resourceSpans)
  for (const { spans } of scopeSpans)
    for (const span of spans) console.log(span.name, span.startTimeUnixNano, span.endTimeUnixNano);

Each turn is one OpenTelemetry trace, built from the session's log. The Runtime exports it to an OTLP collector when the operator sets one up; see Docker Compose. You can read any turn's trace from the Runtime without a collector.

Read a turn's trace

session.trace(turnId) calls GET /v1/sessions/{id}/turns/{turnId}/trace and returns the turn's spans as OTLP/HTTP JSON (resourceSpans), the same spans the exporter posts. It needs the Host feature traces and an application key. Post the answer to a collector's /v1/traces to backfill a turn it missed: the ids are the same on every read and export.

SpanWhat it covers
invoke_agent <manifest>The turn, from its start to its end, named after its agent manifest. The root of every other span. gen_ai.agent.name is the manifest's name, gen_ai.agent.id the Agent's id, and nylorun.manifest.version the version the turn ran.
chat <model>One model call, with the Model Gate's own start, end, usage, and cost.
execute_tool <tool>One tool call, or one flow HTTP stage. gen_ai.tool.name is the name the model called it by, and nylorun.tool.identity the tool's identity (an MCP tool's own name, or an HTTP tool's declared name). An MCP or HTTP call has the Tool Gate's own start and end.
turn.paused, command.approve, command.respondA marker with no length, for each pause and answer.

A turn still running has no root span yet. A trace holds no egress: the connections a pod opens are agent.egress events on its Agent's stream, each naming the turns that were running. To see a turn's, read them with client.agents.events(id, { types: ["agent.egress"], since, until }) around the turn; see Egress records. Attribute names follow the OpenTelemetry GenAI semantic conventions; the rest are nylorun.*. When the harness reported other usage than the gate measured, the span carries nylorun.usage.mismatch.

By default a span carries references, not content: nylorun.event.cursor points at the log event it came from, which history() reads. What a person said, the model's output, tool results, and error messages are left out unless the operator opts in per kind; see Docker Compose. A span never carries a credential value, a prompt, a person's id, an identity header, or an idempotency key.

Trace context

Send a W3C traceparent header, and tracestate if you have one, and the turn joins your trace: its root span becomes a child of your span. The Runtime reads the header on:

RequestEndpoint
A message commandPOST /v1/sessions/{id}/commands
An AG-UI run on an AgentPOST /v1/ag-ui/agents/{agentId}
An A2A message to an AgentPOST /v1/a2a/agents/{agentId}

@nylorun/agents has no trace option, and createAgUiHandler() and createA2aHandler() do not pass the browser's headers on. Give the client a fetch that adds the header from your tracer, for example with @opentelemetry/api:

import { createClient } from "@nylorun/agents";
import { context, propagation } from "@opentelemetry/api";

const client = createClient({
  url: process.env.NYLORUN_RUNTIME_URL!,
  key: process.env.NYLORUN_SERVER_KEY!,
  fetch: (input, init) => {
    const headers = new Headers(init?.headers);
    const carrier: Record<string, string> = {};
    propagation.inject(context.active(), carrier);
    for (const [name, value] of Object.entries(carrier)) headers.set(name, value);
    return fetch(input, { ...init, headers });
  },
});

Pass that client to createAgUiHandler({ client }) or createA2aHandler({ client }) and their runs join the trace active in your server. The Runtime reads the header only on the requests in the table; other requests ignore it.

The turn hands its own context on, so your services see the calls as part of the same trace:

ReceiverHow
HTTP toolsA traceparent header, beside Idempotency-Key.
MCP serversA traceparent header on the call's requests.
Sandbox commandsThe TRACEPARENT environment variable.
A custom harnesstrace on the turn's start.

Per-call reads

For one session's calls without spans, page the call records:

const tools = await session.toolCalls({ turnId });
for (const call of tools.calls) console.log(call.toolName, call.status, call.attempts, call.durationMs);

const models = await session.modelCalls({ turnId });
for (const call of models.calls) console.log(call.model, call.outcome, call.usage?.costUsd);
CallEach entry
session.toolCalls()effectId, toolName, kind, server, tool (the tool's identity), skill, plugin, input, status (invoking, held, completed, failed, or cancelled), attempts, error, and timing, in the order the calls were first sent. Host feature tool-calls.
session.modelCalls()effectId, provider, model, outcome, startedAt, endedAt, and usage with its cost by token kind (costSplit), as the Model Gate metered them.

Both take turnId, limit (default 50, at most 200), and cursor; pass the answer's nextCursor to read on. The effectId joins a call to its span and to its row in the session log.

CallEndpoint
session.trace(turnId)Read a turn's trace
session.toolCalls()Page a session's tool calls
session.modelCalls()Page a session's model calls

Next step

On this page