Skip to content

OpenTelemetry — @breadai/otel ​

Traces every run. The plugin subscribes to the crumb bus and maps each crumb type to a span:

SpanOpens onCloses onAttributes
agent.runagent:run:startagent:run:end / agent:error (exception recorded)bread.agent_id, bread.run_id, bread.session_id, bread.duration_ms, bread.output_file_uri (only when the run's output is a tool-echoed file reference)
tool.call.<leaf>tool:calltool:result / tool:error (exception recorded)bread.tool_name, bread.tool_call_id, bread.duration_ms
pipeline.step.<n>pipeline:step:startpipeline:step:endbread.pipeline_id, bread.step_index, bread.agent_id
human.waithuman:requiredhuman:resumedbread.checkpoint_id, bread.tool_name, bread.kind
subagent.runsubagent:run:startsubagent:run:endbread.agent_id (the sub-agent's id), bread.parent_agent_id, bread.run_id
loop.runloop:startloop:endbread.loop_id, bread.pipeline, bread.max_iterations, bread.status, bread.iterations (the last two on close)
loop.iteration.<n>loop:iteration:startloop:iteration:end (or closed by loop:end if a failed iteration left one dangling)bread.loop_id, bread.iteration
file.generatedfile:generated(single-shot — opens and closes immediately)bread.file_uri, bread.file_mime_type

Tool, human-wait, subagent, loop, and file spans are children of their run span (subagent.run finds its parent via a secondary agentId-keyed index, since subagent:run:start carries parentAgentId, not a parent runId). close() ends any spans a crashed run left open.

Setup ​

ts
// bread.config.ts
import { defineConfig } from '@breadai/core'
import { otel } from '@breadai/otel'

export default defineConfig({
  entrypoints: ['researcher'],
  plugins: [otel({ serviceName: 'my-bread-app' })],
})

The plugin depends only on @opentelemetry/api — spans go wherever your app's registered tracer provider sends them. For local visibility, register a console exporter (this is what examples/otel does):

ts
import { ConsoleSpanExporter, NodeTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'

new NodeTracerProvider({
  spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())],
}).register()

In production, swap the exporter for OTLP and point it at your collector. Alternatively pass a tracer directly: otel({ tracer: myTracer }).

What it deliberately doesn't do ​

  • No metrics or logs — spans only.
  • No context propagation into tool execute() bodies; a tool doing its own tracing starts a new trace unless you wire propagation yourself.

Released under the MIT License.