Reporting telemetry from an external agent

Use this guide to send telemetry from an agent that runs outside Runtype. Runtype reads traces from Flue, the Vercel AI SDK, LangGraph, the OpenAI Agents SDK, Mastra, and your own agent loop, and reads Cloudflare Agents SDK traces with the limits described in Cloudflare Agents SDK. If your agent emits OpenTelemetry data, configure the exporter without a Runtype SDK.

Runtype stores the imported data as external executions. Your agent keeps using its existing model provider and credentials. Runtype does not run the agent or bill the imported run as a Runtype execution.

Create a telemetry API key

Create a dedicated API key for telemetry. On the dashboard API Keys page, select Telemetry Ingest. The permission group grants one scope: TELEMETRY:WRITE.

Use a dedicated key because the key can appear in collector configuration, container environments, and other shared locations. Ingest only appends data, so the key does not need read or management permissions.

The API does not let one API key create another API key. Create the telemetry key in the dashboard, then store it in the exporter configuration.

Existing keys with AGENTS:WRITE continue to work with the ingest endpoints.

Configure the OTLP/HTTP exporter

Set one base endpoint and identify the Runtype agent that receives the runs:

Environment variables
$OTEL_EXPORTER_OTLP_ENDPOINT=https://api.runtype.com/v1/otel
$OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer rt_YOUR_API_KEY,x-runtype-agent-id=agent_YOUR_AGENT_ID

Replace rt_YOUR_API_KEY with the telemetry API key. Replace agent_YOUR_AGENT_ID with the ID of the Runtype agent that receives the runs.

OpenTelemetry appends /v1/traces, /v1/metrics, and /v1/logs to the base endpoint for each signal. A single endpoint therefore configures all three signals.

Create the agent in the dashboard before you send telemetry. Runtype displays runs per agent.

Runtype accepts OTLP over HTTP, not OTLP over gRPC. If your SDK defaults to gRPC, set the HTTP protocol:

Use when the SDK defaults to gRPC
$OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

Restart the agent. The next exported run appears in the Runs view.

Configure the exporter in code

If your application builds the OpenTelemetry pipeline in code, configure the trace exporter with the same endpoint.

For a Node.js application, use the following TypeScript configuration:

TypeScript SDK
1import { NodeSDK } from '@opentelemetry/sdk-node'
2import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'
3
4const sdk = new NodeSDK({
5 traceExporter: new OTLPTraceExporter({
6 url: 'https://api.runtype.com/v1/otel/v1/traces',
7 headers: {
8 authorization: `Bearer ${process.env.RUNTYPE_API_KEY}`,
9 'x-runtype-agent-id': 'agent_YOUR_AGENT_ID',
10 },
11 }),
12})
13sdk.start()

Replace agent_YOUR_AGENT_ID with the ID of the Runtype agent that receives the runs.

For a Python application, configure the HTTP trace exporter as follows:

Python SDK
1import os
2
3from opentelemetry import trace
4from opentelemetry.sdk.trace import TracerProvider
5from opentelemetry.sdk.trace.export import BatchSpanProcessor
6from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
7
8exporter = OTLPSpanExporter(
9 endpoint="https://api.runtype.com/v1/otel/v1/traces",
10 headers={
11 "authorization": f"Bearer {os.environ['RUNTYPE_API_KEY']}",
12 "x-runtype-agent-id": "agent_YOUR_AGENT_ID",
13 },
14)
15provider = TracerProvider()
16provider.add_span_processor(BatchSpanProcessor(exporter))
17trace.set_tracer_provider(provider)

Replace agent_YOUR_AGENT_ID with the ID of the Runtype agent that receives the runs.

Use this cURL request to verify the endpoint, API key, and content type with an empty OTLP/JSON export:

cURL smoke test
$curl -X POST https://api.runtype.com/v1/otel/v1/traces \
> -H "Authorization: Bearer $RUNTYPE_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{"resourceSpans": []}'

Set RUNTYPE_API_KEY to the telemetry API key before you run the command. The endpoint returns HTTP 200 with {} when it accepts the empty export.

For Python, install opentelemetry-exporter-otlp-proto-http. The Python gRPC exporter is the default, so select the HTTP exporter or set OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf.

Send traces from a supported framework

Each framework below needs an OpenTelemetry provider and exporter standing behind its instrumentation. Instrumentation alone emits spans that nothing sends, and the run never appears.

FrameworkWhat to installWhat to enable
Flue@runtypelabs/flue-otel, @opentelemetry/api, @opentelemetry/sdk-trace-node, @opentelemetry/exporter-trace-otlp-httpinstrument(createRuntypeFlueInstrumentation())
Vercel AI SDK@opentelemetry/sdk-node, @opentelemetry/exporter-trace-otlp-protoexperimental_telemetry: { isEnabled: true, functionId: 'my-agent' }
LangGraphopeninference-instrumentation-langchain, opentelemetry-exporter-otlp-proto-httpLangChainInstrumentor().instrument(tracer_provider=provider)
OpenAI Agents SDKopeninference-instrumentation-openai-agents, opentelemetry-exporter-otlp-proto-httpOpenAIAgentsInstrumentor().instrument(tracer_provider=provider)
Mastra@mastra/observability, @mastra/otel-exporterAn OtelExporter on the Mastra constructor’s observability config

The Connect an existing agent page in the dashboard renders a paste-ready snippet for each of these, already pointed at the API host your account uses.

Mastra’s exporter is the one exception to the environment variables above: it reads neither OTEL_EXPORTER_OTLP_ENDPOINT nor OTEL_EXPORTER_OTLP_HEADERS, and it wants the full trace path, so pass the endpoint and headers to OtelExporter yourself:

Mastra
1new OtelExporter({
2 provider: {
3 custom: {
4 endpoint: `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`,
5 headers: {
6 authorization: `Bearer ${process.env.RUNTYPE_API_KEY}`,
7 'x-runtype-agent-id': process.env.RUNTYPE_AGENT_ID,
8 },
9 protocol: 'http/protobuf',
10 },
11 },
12})

Flue reports an iteration count, a stop reason and approval decisions, so its runs reach the highest fidelity Runtype records. The other four have no approval or skill concept to report, so their runs reach content fidelity: the run, its model, usage, cost, tool calls, transcript and final output.

Cloudflare Agents SDK

Runtype reads traces from the Cloudflare Agents SDK (agents 0.22) but does not offer it on the Connect an existing agent page, because of how Workers traces a chat session. Three facts to plan around, verified against a real AIChatAgent under wrangler dev:

  • Nothing self-instruments. A plain AIChatAgent calling streamText produces no GenAI spans. Wrap the AI SDK once, at module scope, and call the wrapped namespace from onChatMessage:

    Instrument the AI SDK
    1import * as ai from 'ai'
    2import { wrapAISDK } from 'agents/observability/ai'
    3
    4export const tracedAI = wrapAISDK(ai, { storeMessages: true, storeTools: true })

    Content is off by default here, unlike the other supported frameworks. Without storeMessages and storeTools the run has no transcript, and with it no eval capture.

  • The export is a dashboard destination, not an environment variable. The spans go through Workers Observability, so the endpoint and headers live on a destination you create in the Cloudflare dashboard, and wrangler.jsonc only names it. Follow the Flue on Cloudflare Workers steps; the OTEL_EXPORTER_* variables do nothing on a Worker. OTLP export needs a Workers Paid plan, and every span of the request, storage calls included, is billed, so lower head_sampling_rate once the first trace has landed.

  • One WebSocket connection is one trace. Every chat turn sent over the connection useAgentChat holds open nests under the request that upgraded it, so a session of five turns arrives as one trace carrying five invoke_agent spans. Runtype files one run per trace, so that session is recorded as one run: the first turn’s transcript, the last turn’s answer, and every tool call in between. A turn served by a plain HTTP request is its own trace and records correctly. Treat an Agents SDK run as one connection.

When the trace does arrive, Runtype reads its canonical gen_ai.* attributes as-is and lifts the first model call’s gen_ai.input.messages onto the run, since the SDK records messages on chat spans only. Tool-approval legs (tool_approval spans, which carry the real call’s id) are kept in Logs and never counted as a second tool call.

Send several services through one collector

If an OpenTelemetry Collector exports several instrumented services in one request, identify each service with a resource attribute:

Per-service attribution
$OTEL_RESOURCE_ATTRIBUTES=runtype.agent.id=agent_YOUR_AGENT_ID

Replace agent_YOUR_AGENT_ID with the ID of the Runtype agent for that service.

For each trace, Runtype resolves the agent from the resource attribute first, then the x-runtype-agent-id header, and then the runtype.agent.id attribute on the invoke_agent span. The header provides the fallback for traces that do not set the resource attribute.

Run several agents in one process

If one process runs several agents, set runtype.agent.id on the invoke_agent span that opens each run:

Per-run attribution
1span.setAttribute('runtype.agent.id', 'agent_YOUR_AGENT_ID')

Replace agent_YOUR_AGENT_ID with the ID of the agent for that run.

The resource attribute and header describe a process or export. The span attribute identifies a run when one process uses a shared resource for several agents.

If two invoke_agent spans claim different agents and neither the resource attribute nor the header identifies the agent, Runtype rejects the trace as ambiguous_agent_attribution. One trace represents one run. If agents delegate inside one trace, set attribution at the resource or header level, or export each invocation as a separate trace.

Runtype resolves attribution per trace. If some traces name an agent that your key cannot use, Runtype stores the accepted traces and reports the rejected traces as OTLP partial_success. If no trace is accepted, the endpoint returns an error. Accepted traces do not return an error, so the exporter does not resend them because another trace in the export was rejected.

Identify the instrumentation that produced a trace

Set runtype.adapter.name and runtype.adapter.version to name the library that emitted a trace and the version it ran. Runtype reads them from the resource first and from the run’s invoke_agent span second, and records the pair on the run so you can tell which producer version a run came from.

OTEL_RESOURCE_ATTRIBUTES=runtype.agent.id=agent_YOUR_AGENT_ID,runtype.adapter.name=my-exporter,runtype.adapter.version=1.4.0

@runtypelabs/flue-otel sets both for you: it stamps them on every invoke_agent span it opens, so a Flue run identifies its producer even when you do not wire the resource attributes. Your own resource attribute still wins where you set both.

The pair is provenance. Runtype never uses it to gate, price, or attribute a run, so a value you choose changes nothing but what you can read back.

Understand imported telemetry

Runtype projects traces into external executions and eval capture. It retains spans and span events in Logs. It also retains attributed OTLP LogRecord data and links it to an execution when it includes a trace ID.

Runtype acknowledges metrics but does not store them. This lets a stock exporter use the same base endpoint without retrying a missing signal endpoint.

OTLP delivery can repeat an export. Runtype stores received rows as append-only data and deduplicates retry records when it displays Logs and trace trees. Span events remain separate. Repeated byte-identical LogRecord entries converge to one displayed record.

Runtype requires agent attribution to store OTLP LogRecord data. Without runtype.agent.id on the Logs resource or the x-runtype-agent-id header, Runtype returns OTLP partial_success and does not store those records. Add one of these attribution methods to make the records visible.

After attribution, Runtype stores LogRecord bodies and attributes under the target agent’s logging policy. Set that policy to Off before enabling the Logs signal if you do not want to retain application logs. Enable personally identifiable information (PII) redaction when the payloads can contain personal data.

Runtype derives the run, tool calls, token counts, and a cost estimate from the traces. It calculates the display-only cost with the Runtype model catalog. Your provider charges you for the model calls, so treat the cost as an estimate.

To capture conversation content, set the generative AI (GenAI) content attributes on your spans. Runtype maps them to the run as follows:

AttributeResult
gen_ai.input.messagesThe run’s transcript.
gen_ai.system_instructionsThe transcript’s system turn.
gen_ai.output.messagesThe run’s final output.
gen_ai.tool.call.argumentsA tool call’s input parameters.
gen_ai.tool.call.resultA tool call’s result.

Some instrumentations use different attribute names. Runtype also recognizes flue.tool.call.arguments and flue.tool.call.result from @flue/opentelemetry, which preserve tool results that are arrays or strings, and the Vercel AI SDK’s ai.prompt.messages, ai.response.text, ai.toolCall.args, and ai.toolCall.result. If a span carries both names, Runtype uses the standard attribute. Runtype applies the same scrubbing and size limits to an aliased value, and excludes every recognized spelling from the retained span attributes in Logs.

OpenInference instrumentations spread a message list across indexed attributes such as llm.input_messages.0.message.content. Runtype reassembles those into the standard attributes before it reads them, and excludes the whole family from Logs the same way.

If you use Flue, the first-party @runtypelabs/flue-otel instrumentation reports an exact loop iteration count, a stop reason, and rolled-up usage. See Instrumenting a Flue agent. From 0.5 it also sends the transcript, system prompt and tool arguments and results under the standard attribute names by default, so its runs carry a transcript and can be captured as eval cases; each kind can be switched off through its content option.

Point exactly one Flue instrumentation package at a Runtype endpoint. If you export with both @runtypelabs/flue-otel and @flue/opentelemetry, Runtype receives two invoke_agent spans for one run. The reported token counts and cost then double.

Content capture is opt-in at the producer: Runtype stores conversation content only when your instrumentation puts it on the span. That opt-in is not always the gen_ai.* attributes. A supported framework’s own vocabulary counts too, and those instrumentations record content by DEFAULT, so a run from the Vercel AI SDK, LangGraph, the OpenAI Agents SDK or Mastra carries a transcript unless you turn recording off upstream (recordInputs: false / recordOutputs: false on the AI SDK, OPENINFERENCE_HIDE_INPUTS=true for OpenInference). An agent’s logging policy governs the Logs tier, not the run’s transcript.

Runtype also keeps content out of the raw span attributes it retains in Logs. It strips every recognized content name and every vendor content family, so no payload reaches the raw Logs dump, which applies neither the caps nor the scrubbing.

The families a normalizer reads reach the run through the projection instead: the gen_ai.* content attributes, the Vercel AI SDK prompt, response and tool call keys, and OpenInference’s llm.input_messages.*, llm.output_messages.* and the input.value / output.value pair on tool spans. The remaining families are stripped from Logs and read by no normalizer, so Runtype stores their payloads nowhere: OpenInference’s retrieval.documents.*, llm.prompt_template.* and embedding.embeddings.*, Mastra’s mastra.<span type>.input / .output, and the Cloudflare Agents SDK’s cloudflare.agents.metadata.* and cloudflare.agents.runtime_context.*.

Before you enable content capture, account for these limits:

  • Scrubbing and size limits. Runtype redacts credential-shaped strings before storage. It drops a content attribute over 256 KiB, content that exceeds the 384 KiB per-span budget, or content beyond the 2 MiB export budget. Runtype drops each affected value whole instead of truncating it.
  • Partial-trace fidelity. Runtype records the fidelity that each run achieves. This distinguishes an empty model response from content that the exporter omits.

Use compatibility ingest without OpenTelemetry

To send unified execution events without OpenTelemetry, post a batch to the compatibility endpoint:

Direct ingest
$curl -X POST https://api.runtype.com/v1/executions/ingest \
> -H "Authorization: Bearer rt_YOUR_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "executionId": "YOUR_EXECUTION_ID",
> "agentId": "agent_YOUR_AGENT_ID",
> "events": [
> {
> "type": "execution_start",
> "executionId": "YOUR_EXECUTION_ID",
> "seq": 0,
> "kind": "agent",
> "startedAt": "2026-01-01T00:00:00Z"
> },
> {
> "type": "execution_complete",
> "executionId": "YOUR_EXECUTION_ID",
> "seq": 1,
> "kind": "agent",
> "success": true
> }
> ]
> }'

Replace rt_YOUR_API_KEY with the telemetry API key. Replace agent_YOUR_AGENT_ID with the ID of the target Runtype agent. Replace YOUR_EXECUTION_ID with a unique ID for the run.

The request body contains a batch from the unified execution event vocabulary. The endpoint accepts the same TELEMETRY:WRITE key and populates the agent’s Runs view and eval capture, including the transcript when the events carry it. For the event schema, see the API Reference.

This endpoint does not retain the structural span and span-event records that Logs and trace_execution use. On these runs, trace_execution reports json_ingest_compatibility_path as the availability reason. Use the OTLP/HTTP endpoints when you need Logs, parent-child trace trees, or OTLP LogRecord data. Use compatibility ingest when Runs and eval capture meet your needs.

Next steps

Continue with one of these guides: