Braintrust Auto-Instrumentation
August 4, 2026 ยท View on GitHub
Braintrust auto-instrumentation uses the vendored Orchestrion-JS transformer to wrap selected AI SDK functions at load time or bundle time. Transformed code uses hooks stored in a shared global registry.
Instrumentation Configs
Each config identifies a package file and function:
const config = {
channelName: "chat.completions.create",
module: {
name: "openai",
versionRange: ">=4.0.0 <7.0.0",
filePath: "resources/chat/completions.mjs",
},
functionQuery: {
className: "Completions",
methodName: "create",
kind: "Async",
},
};
channelName omits the prefix. Orchestrion constructs the stable identifier:
orchestrion:<module.name>:<channelName>
The corresponding typed channel definition and plugin subscription must use the same identifier.
Generated Runtime Contract
For every configured channel, transformed modules lazily look up:
globalThis.__braintrust_instrumentation_hooks?.get(
"orchestrion:openai:chat.completions.create",
);
The lookup is retried until a hook exists, then cached. This has two important properties:
- Loading an instrumented provider before Braintrust is safe; calls run normally.
- Registering Braintrust later enables tracing without retransformation.
The generated code only applies traceInvocation, passing the configured
operator, original target, receiver, complete arguments, and moduleVersion.
The normal hook runtime handles interceptor composition, the no-listener fast
path, tracing context construction, and the legacy tracePromise, traceSync,
or traceCallback dispatch around the effective intercepted call.
The hook lifecycle mirrors tracing channels:
startbefore the target callendafter its synchronous portionasyncStartandasyncEndwhen an asynchronous result settleserrorfor synchronous throws, promise rejections, or callback errors
The same context object is passed through every phase. Subscribers may mutate arguments or returned streams before user code continues.
Invocation interceptors compose as nested middleware and may replace arguments, the receiver, the returned value, or the entire implementation. Tracing remains the outer compatibility layer, so tracing subscribers observe the interceptor's effective result.
Global Registry
The SDK installs globalThis.__braintrust_instrumentation_hooks with a
non-enumerable, non-writable property descriptor. Its value is a mutable
Map<string, TracingHook> shared by all Braintrust SDK copies in the realm.
The implementation lives in src/global-instrumentation-hooks.ts. It supports:
- composable
invoke/interceptwrappers - all five lifecycle phases
- multiple subscribers and complete unsubscription
bindStore/unbindStorefor async-context propagation- sync, promise, and callback tracing operators
- preservation of Promise subclasses, thenables, and non-Promise return values
Manual wrappers use the same registry through typed channel definitions, so manual and auto-instrumented paths share lifecycle and span behavior.
Loaders and Bundlers
The unified Node hook instruments ESM and CJS:
node --import braintrust/hook.mjs app.mjs
Bundler integrations are available for esbuild, Vite, Rollup, Webpack, Next.js, and Turbopack. Generated provider code is runtime-independent and contains no Node built-in or browser-shim import.
Bundler plugins accept browser: true when their output targets a browser or
edge-like runtime. Global hooks themselves are runtime-independent; this hint
only prevents the Node-specific Mastra source patch from entering those
bundles.
Adding an Instrumentation
- Add the narrowest supported package/version/file/function config under
configs/. - Define a typed channel with the same package and operation identifier.
- Add or update a plugin that intercepts the typed channel; use the tracing helpers only for existing instrumentation awaiting migration.
- Keep manual wrappers on that same typed channel through
invoke. - Add transformation/runtime coverage and a provider e2e scenario when the user-visible trace contract changes.
Instrumentation must preserve target behavior, including receivers, argument mutation, errors, async context, streams, and custom Promise APIs.
Testing
Relevant suites live in:
src/global-instrumentation-hooks.test.tstests/auto-instrumentations/orchestrion-js-upstream.test.tstests/auto-instrumentations/transformation.test.tstests/auto-instrumentations/runtime-execution.test.tstests/auto-instrumentations/loader-hook.test.ts
The transformation suites assert that output contains the global registry lookup
and contains neither diagnostics_channel nor dc-browser. Provider e2e tests
must run in cassette replay mode after instrumentation changes.