Interbolt
Guides

OpenTelemetry

Mapping Interbolt's setup, factory, and decorator patterns onto Phoenix/OpenTelemetry vocabulary, and wiring OTelReporter next to an already-instrumented app.

OpenTelemetry

Already instrumenting your agent with Phoenix or plain OpenTelemetry? This page covers both: the vocabulary mapping if you are coming from that world, and the mechanics of wiring OTelReporter into an app that already has tracing set up.

Coming from Phoenix / OpenTelemetry

Interbolt borrows the OpenTelemetry provider shape: a setup call that returns a provider, a factory method that hands back a per-caller handle, a decorator family, and an extension point for attaching output processors after the fact. If you have already instrumented an agent with Phoenix or plain OpenTelemetry, the setup, factory, and decorator patterns below are the ones you already know. What differs is what the primitives do once you call them: OpenTelemetry observes execution and records what happened; Interbolt gates execution and decides, before the call runs, whether it is allowed to happen at all.

The mapping

Phoenix / OpenTelemetryInterbolt
register(...) returns a tracer providerconfigure(...) returns a Runtime, not named register because it configures an enforcement authority with a policy, not an attachment to a collector
provider.get_tracer(name)agent(agent_id) or runtime.agent(agent_id), returning a durable per-agent handle bound to an identity rather than a tracer bound to a module name
provider.add_span_processor(...)runtime.add_reporter(...)
trace.get_tracer_provider()get_runtime()
@tracer.tool, @tracer.llm decorators@handle.guard, @handle.track_model_call; @handle.guard runs before the call and can raise, unlike any OTel decorator (see "Where Interbolt intentionally differs" below)
using_session(...) context manageragent_context(...) / agent_context_sync(...), which binds an identity that changes enforcement outcomes for the run rather than an attribute attached to spans
OTEL_* / PHOENIX_* env varsINTERBOLT_* env vars (INTERBOLT_MODE, INTERBOLT_AUDIT, INTERBOLT_RECURSION_DEPTH)
In-memory exporter for testsInMemoryReporter
Spans in your collectorOTelReporter (span events inside your existing traces; interbolt[otel] extra)

The handle in row two is the closest structural analog to get_tracer, but it is not the default way to guard a tool. Bare @guard with identity bound at the call site by agent_context is; see Identity.

Side by side

A tool instrumented with Phoenix, the way you would already write it:

from phoenix.otel import register

tracer_provider = register(project_name="support-agent")
tracer = tracer_provider.get_tracer(__name__)


@tracer.tool
def send_email(to: str, body: str) -> None:
    ...


send_email(to="user@example.com", body=summary)

The same tool, with Interbolt's gate added on top of the existing Phoenix instrumentation:

from interbolt import OTelReporter, Policy, configure

runtime = configure(policy=Policy.from_file("policy.yaml"))
runtime.add_reporter(OTelReporter())
support = runtime.agent("support-agent")


@tracer.tool
@support.guard
def send_email(to: str, body: str) -> None:
    ...


send_email(to="user@example.com", body=summary)

@tracer.tool stays outermost, so its span is already open and recording by the time @support.guard calls check(). OTelReporter then attaches the resulting decision as an event on that same span, so the Phoenix trace and the Interbolt decision live in one place with no separate exporter to configure. See What appears in the trace for the exact event shape.

Where Interbolt intentionally differs

  • @guard is a gate rather than an observer. It evaluates policy before the wrapped call executes and raises PolicyViolation on block. No OTel decorator alters control flow; this one exists to enforce it.
  • No uninstrument(), no reporter removal, no mutable policy or mode on a live runtime. Detaching an observability instrument is harmless. Silently detaching a security gate creates a way to bypass enforcement. Enforcement inputs change only through a new configure() call.
  • Fail-closed by default. Under Mode.ENFORCE, an evaluation error blocks rather than proceeds. Observability tooling instead fails open; an enforcement layer must not.

Wiring OTelReporter

If you are not coming from Phoenix or OTel, or just want the reporter wiring on its own: add OTelReporter next to your existing instrumentation and Interbolt's decisions show up as span events inside the traces you already have.

from interbolt import OTelReporter, Policy, configure

runtime = configure(policy=Policy.from_file("policy.yaml"))
runtime.add_reporter(OTelReporter())

# elsewhere, inside a span your own instrumentation already opened:
with tracer.start_as_current_span("handle_request"):
    send_email(to=..., body=...)  # a guarded call inside the span

What appears in the trace

When a guarded call happens inside a recording span, which is the common case since your framework or app already wraps the request, turn, or tool dispatch, the decision, finding, or endorsement is added to that span as an event named interbolt.decision, interbolt.finding, or interbolt.endorsement, carrying interbolt.*-namespaced attributes (tool, action, matched rule, mode, sources; see the full mapping table). No new span is created; the decision rides inside the span your own code already started.

Two fields are deliberately never exported: contributing_labels, which is unbounded, and matched_condition, whose CEL text may embed a literal you did not intend to ship to a third-party backend. Both remain in the native records (see Events).

No span active, or no provider configured

With no recording span active, OTelReporter opens and immediately closes a small span of its own, named the same way, so the decision is still exported rather than silently dropped. If no TracerProvider is configured at all, this is a no-op by OpenTelemetry's own design, exporting nothing and raising nothing. Either way, OTelReporter never blocks or delays a decision, since attaching a span event is an in-memory operation rather than I/O.

interbolt[otel] depends only on opentelemetry-api. Wiring an actual exporter (OTLP, console, or otherwise) is your own OpenTelemetry SDK setup, unrelated to Interbolt.

Note that trace_id and span_id are also recorded on every native record (Event, Finding, Endorsement) when a span is active, so a JsonlReporter log can be correlated back to your traces even without OTelReporter attached.

Next

See Events for the versioned record schema and the complete OpenTelemetry attribute mapping.

On this page