FabricFabricHarness
Reference

Telemetry

Events, metrics, and OpenTelemetry hooks.

Fabric Harness emits structured events for every meaningful step in an agent run. The full notes live in docs/telemetry.md.

What a correlated run looks like

The run identifier ties model attempts, tool effects, policy decisions, schema validation, duration, usage, and cost together. Exporters may render this differently, but they should preserve the same parent-child and failure-classification evidence.

Correlated agent trace showing prompt, Genie tool, denied policy effect, output validation, duration, tokens, and cost
Representative UIOne run identity correlates model work, tools, policy decisions, schema validation, latency, usage, and estimated cost.

Event types

agent_start
text_delta
prompt_start       prompt_end
tool_start         tool_end
command_start      command_end
task_start         task_end
approval_requested approval_granted approval_denied approval_expired
checkpoint_created checkpoint_restored
compaction
mount
metric
result_retry      result
error

Transports

  • SSEGET /agents/:name/:id/stream replays legacy session events; add ?offset=... to tail the persistent conversation stream as offset-addressed records. Finite jobs expose run events at GET /runs/:runId/events?offset=....
  • OpenTelemetryopenTelemetryExporter adapts Fabric's TelemetrySpan shape to any @opentelemetry/api Tracer (App Insights, Jaeger, Honeycomb, generic OTLP). Pass conventions: 'foundry' to emit gen_ai.* span names matching Azure AI Foundry / OpenTelemetry Generative AI semantic conventions.
  • LangfuselangfuseExporter for direct trace export (peer-deps langfuse).
  • Azure Monitor / App InsightsapplicationInsightsExporter from @fabric-harness/azure/app-insights (peer-deps applicationinsights).
  • ConsoleconsoleTelemetryExporter for local debugging.
import { openTelemetryExporter, langfuseExporter, eventToTelemetrySpan } from '@fabric-harness/sdk';
import { Langfuse } from 'langfuse';

const langfuse = new Langfuse({ publicKey, secretKey, baseUrl });
const exporter = langfuseExporter({ client: langfuse, attributes: { service: 'support-agent' } });

const session = await fabric.session(undefined, {
  onEvent: async (event) => {
    const span = eventToTelemetrySpan(event);
    if (span) await exporter.export(span);
  },
});

Metrics from the CLI

fh metrics <session-id> [--json]

Aggregates:

  • token usage (input + output + total) and costUsd if the provider reports it,
  • tool calls (with per-tool breakdown for the top 5),
  • shell commands and durations,
  • artifacts,
  • mounts (count, total bytes, total files, top sources by bytes),
  • model attempts.

Per-event hooks

Subscribe to events at agent or session scope via the onEvent callback. Wire any of the exporters above (or a custom one implementing TelemetryExporter) into the callback to forward spans.

Hierarchical OpenTelemetry observer

openTelemetryExporter emits one flat span per duration-bearing event. For a nested trace — a single parent span per operation, with turn, tool, and shell spans inside — use the observer instead:

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

const observe = createOpenTelemetryObserver({
  tracer: trace.getTracer('fabric-harness'),
  conventions: 'fabric', // or 'foundry' for gen_ai.* span names + attributes
});

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

It lives on the @fabric-harness/sdk/otel-observer subpath (not the main entry) because building parent spans needs @opentelemetry/api at runtime, while the main SDK keeps it an optional peer dependency. The tree is inferred from event start/end ordering; a task() sub-session traces separately with a wrapping task span, and an error event closes open spans with ERROR status.