TechFabricTechFabricHarness
EcosystemTooling

OpenTelemetry

Export agent, model, tool, task and shell activity as OpenTelemetry spans, so an agent run appears in the tracing you already have.

Fabric provides flat event exporters and a hierarchical observer. Your application owns the OpenTelemetry SDK, exporter, sampling, credentials, and shutdown lifecycle.

fh add opentelemetry

The managed recipe installs the OpenTelemetry API and creates a hierarchical observer configured with content redaction and Foundry-compatible semantic conventions.

import { trace } from '@opentelemetry/api';
import { init } from '@fabric-harness/sdk';
import { createOpenTelemetryObserver } from '@fabric-harness/sdk/otel-observer';

const observe = createOpenTelemetryObserver({
  tracer: trace.getTracer('support-agent'),
  conventions: 'foundry',
  correlation: { agentId: 'support', jobId: runId, submissionId, tenantId },
});

const fabric = await init({ onEvent: observe });

The observer creates nested turn, tool, shell, and task spans. conventions: 'foundry' emits gen_ai.* names and attributes compatible with Azure AI Foundry and OpenTelemetry GenAI consumers. Stable job, agent, session, submission, and tenant IDs are span attributes. Use openTelemetryExporter() when one flat span per duration-bearing Fabric event is preferable.

Do not export prompts, tool arguments, or results without a deliberate redaction and retention policy. See Telemetry for exporters, attributes, and event coverage.

Bounded, opt-in content

The hierarchical observer accepts contentBudgetBytes (default 57,344; safe integer at least 128). Content remains off unless captureContent: true is set. This is a shared per-span UTF-8 budget for content attribute keys plus values, not a separate limit per field and not a total-span or collector limit. Operational metadata and usage attributes remain available after content runs out. Up to 16,384 bytes (at most half a small budget) are reserved for response-side content.

const observe = createOpenTelemetryObserver({
  tracer: trace.getTracer('development-agent'),
  captureContent: true, // Only after the application's disclosure/retention review.
  contentBudgetBytes: 8_192,
  // Runs on detached data. Returning undefined drops content; throwing omits it.
  transformContent: content => applicationRedactor(content),
  additionalSecrets: runtimeSecrets,
});

Prompt text/results use fabric.content.input / fabric.content.output; compaction summaries use fabric.content.summary. When supplied by an event producer, tool input and output/result use gen_ai.tool.call.arguments and gen_ai.tool.call.result. Ordinary session tool events deliberately omit raw payloads: enabling this observer does not recover content absent from the event stream. The flat exporter remains metadata-only.

Strings truncate on Unicode boundaries. Oversized structured values become the valid JSON string "[TRUNCATED]", rather than malformed JSON. If even the omission marker cannot fit, no content attribute is written. The boolean fabric.content.truncated and fabric.content.omitted flags report loss as operational metadata outside the content pool. They add a small, fixed overhead to the exported span. A span's first value for each content key wins; repeated events cannot replenish its budget.

Credential-pattern redaction runs after the detached transform; a failed transform never falls back to raw content. A custom sanitize event callback remains authoritative and receives detached input. Built-in credential redaction is not a PII classifier. Financial/identity applications should keep capture disabled until their own redactor and retention/disclosure policy are verified. Content transforms are not invoked when capture is off.