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.

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
errorTransports
- SSE —
GET /agents/:name/:id/streamreplays legacy session events; add?offset=...to tail the persistent conversation stream as offset-addressed records. Finite jobs expose run events atGET /runs/:runId/events?offset=.... - OpenTelemetry —
openTelemetryExporteradapts Fabric'sTelemetrySpanshape to any@opentelemetry/apiTracer (App Insights, Jaeger, Honeycomb, generic OTLP). Passconventions: 'foundry'to emitgen_ai.*span names matching Azure AI Foundry / OpenTelemetry Generative AI semantic conventions. - Langfuse —
langfuseExporterfor direct trace export (peer-depslangfuse). - Azure Monitor / App Insights —
applicationInsightsExporterfrom@fabric-harness/azure/app-insights(peer-depsapplicationinsights). - Console —
consoleTelemetryExporterfor 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
costUsdif 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.