Observability
Add OpenTelemetry spans and metrics to Edictum TypeScript agents with the @edictum/otel package.
Right page if: you want to add OpenTelemetry tracing or metrics to an Edictum TypeScript agent. Wrong page if: you want Python observability -- see https://docs.edictum.ai/docs/reference/observability. For audit sinks (file, server, stdout), see https://docs.edictum.ai/docs/reference/audit-sinks. Gotcha: `@edictum/otel` requires `@opentelemetry/api` as a peer dependency. If OTel is not installed, `createTelemetry()` returns a no-op implementation that silently drops all spans and metrics.
Audit events tell you what happened. OpenTelemetry tells you how long it took and where it fits in your distributed trace. The @edictum/otel package emits governance-specific spans for every contract evaluation and counters for denied/allowed tool calls.
Getting Started
Install
pnpm add @edictum/otel @opentelemetry/apiFor the configureOtel() setup helper, you also need the SDK packages:
pnpm add @opentelemetry/sdk-trace-base @opentelemetry/sdk-metrics @opentelemetry/resourcesFor gRPC export:
pnpm add @opentelemetry/exporter-trace-otlp-grpc @opentelemetry/exporter-metrics-otlp-grpcQuick setup with configureOtel
import { configureOtel } from '@edictum/otel'
await configureOtel({
serviceName: 'my-agent',
endpoint: 'http://localhost:4317',
protocol: 'grpc',
resourceAttributes: {
'deployment.environment': 'production',
},
})This registers a TracerProvider and MeterProvider globally. If a tracer provider is already registered, it is not overridden (set force: true to override). The meter provider is always registered so that Edictum metrics work regardless of your tracing setup.
Use GovernanceTelemetry
import { GovernanceTelemetry } from '@edictum/otel'
const telemetry = new GovernanceTelemetry()
// Start a span for a tool call
const span = telemetry.startToolSpan(envelope)
// After pipeline evaluation
telemetry.setSpanOk(span)
// or on error:
telemetry.setSpanError(span, 'Denied by contract: block-dotenv')
span.end()configureOtel Options
| Option | Type | Default | Description |
|---|---|---|---|
serviceName | string | "edictum-agent" | Service name in traces |
endpoint | string | "http://localhost:4317" | Collector endpoint |
protocol | "grpc" | "http" | "http/protobuf" | "grpc" | Export protocol |
resourceAttributes | Record<string, string> | {} | Extra resource attributes |
edictumVersion | string | — | Sets edictum.version attribute |
force | boolean | false | Override existing TracerProvider |
Environment variables take precedence:
| Env Var | Overrides |
|---|---|
OTEL_SERVICE_NAME | serviceName |
OTEL_EXPORTER_OTLP_ENDPOINT | endpoint |
OTEL_EXPORTER_OTLP_PROTOCOL | protocol |
Spans and Metrics
GovernanceTelemetry emits:
Spans (tracer name: edictum):
- One span per tool call evaluation, with attributes for tool name, verdict, contract IDs, and principal
Counters (meter name: edictum):
edictum.calls.denied-- number of denied tool callsedictum.calls.allowed-- number of allowed tool calls
Both counters include a tool_name attribute for per-tool breakdowns.
No-Op Fallback
If @opentelemetry/api is not installed, you can still import @edictum/otel safely:
import { createTelemetry } from '@edictum/otel'
// Returns GovernanceTelemetry if OTel is available, NoOpTelemetry otherwise
const telemetry = await createTelemetry()NoOpTelemetry implements the same interface but silently drops all spans and metrics. This lets library code use telemetry without forcing a dependency on consumers.
You can also check availability explicitly:
import { hasOtel, hasOtelAsync } from '@edictum/otel'
// Synchronous check (may miss async-loaded modules)
if (hasOtel()) {
const telemetry = new GovernanceTelemetry()
}
// Async check (reliable)
if (await hasOtelAsync()) {
const telemetry = new GovernanceTelemetry()
}Input Sanitization
All string inputs to spans and metrics are sanitized:
- Control characters are stripped
- Attribute keys are capped at 1,000 characters
- Attribute values are capped at 10,000 characters
- Tool names are sanitized before use in span names and metric labels
This prevents telemetry injection from malicious tool names or arguments.
Next Steps
- Audit sinks -- file, server, and stdout audit output
- Python observability -- OTel for Python agents
- Server SDK -- server-backed audit streaming
Last updated on