Not part of v11. Follow-up to owning Mastra agent tracing ourselves instead of relying on @mastra/sentry.
Context
Mastra AI spans currently reach Sentry through @mastra/sentry (SentryExporter), which lives in the Mastra repo (mastra-ai/mastra#11890). That closed JS-718 / JS-1279 by contributing an exporter upstream rather than adding an in-SDK integration. Their implementation is a useful reference for what we want to capture (agent runs, model generations, tool calls, token usage, workflows).
The current setup has a few structural problems:
- SDK version coupling.
@mastra/sentry depends on @sentry/node. Every SDK major — including v11 gen_ai attribute changes — requires a coordinated bump on Mastra's release train. We cannot ship those updates ourselves, which is why the exporter was left out of JS-3110.
- Mismatch with the post-OTel default path. The Node SDK is dropping OpenTelemetry instrumentations as the out-of-the-box mechanism in favor of Orchestrion. The exporter can still call
startInactiveSpan, but it is not a first-party integration: it does not register with Orchestrion, does not participate in default integrations, and does not share our gen_ai conventions pipeline. Routing Mastra through OTel into Sentry remains possible, but it is a worse fit.
- Cannot coexist with a host SDK. The exporter calls
Sentry.init() on its own @sentry/node instance, which overwrites the application's client. That rules out using it alongside Bun, Deno, Cloudflare, Next.js, or any other @sentry/* setup already in the process.
Our existing AI auto-instrumentation also does not cover Mastra. @mastra/core does not depend on ai, openai, or @anthropic-ai/sdk, so openaiIntegration / anthropicAIIntegration / vercelAIIntegration never fire. Without the exporter, a fully configured @sentry/node app only gets HTTP spans for model calls — no gen_ai semantics, tokens, tools, or agent tree.
Proposed direction to investigate
Port the exporter into packages/server-utils/src/ai/mastra/ (LangChain-style: duck-typed exporter, no Mastra runtime dep, hand-written types), re-export from @sentry/node / Cloudflare / Edge. Drop SDK lifecycle ownership: read the active client, no-op if none, never init/close.
Activation is Node Orchestrion, same as LangChain — not the OpenAI pattern of wrapping the model call. LangChain's chat-model hooks don't open spans; Orchestrion wraps BaseChatModel.invoke so the subscriber can inject createLangChainCallbackHandler into args[1].callbacks, and LangChain's own dispatch drives the tree. Mastra already has that dispatch (the observability bus fanning SPAN_STARTED / SPAN_ENDED to exporters). Orchestrion's job is to inject our exporter so Sentry.init() is enough.
Wrapping Agent.generate / Workflow.createRun ourselves would rebuild Mastra's span taxonomy and miss scorer, memory, and workflow types unless we wrap those too.
Concretely:
packages/server-utils/src/ai/mastra/ — the exporter (createMastraExporter()), same role as createLangChainCallbackHandler
packages/server-utils/src/orchestrion/config/mastra.ts — inject channels on @mastra/observability / @mastra/core
packages/server-utils/src/integrations/mastra.ts — mastraIntegration subscribes and splices the exporter into exporters[]
Edge/Workers still work off that same config: the Node runtime hook is --import @sentry/node/orchestrion; Cloudflare/Next get it from the bundler plugin. The exporter itself stays in server-utils and doesn't import @sentry/node.
Runtime coverage is a goal, not a side effect. Mastra officially runs on Node, Bun, Deno, and Cloudflare; @mastra/sentry does not (Cloudflare cannot load @sentry/node; Bun/Deno load it and then overwrite @sentry/bun / @sentry/deno). First-party ownership should close that gap the same way LangChain does:
- Floor:
createMastraExporter() on @sentry/core only, re-exported from @sentry/node, @sentry/bun, @sentry/deno, @sentry/cloudflare. Manual exporters: [createMastraExporter()] works anywhere we already have a server SDK — including Cloudflare, which is impossible today.
- Native: the same Orchestrion config in
SENTRY_INSTRUMENTATIONS so the Node hook, Bun plugin, and Cloudflare Vite plugin all inject the exporter. mastraIntegration is re-exported from those packages (same as langChainIntegration).
Fan-out to other Mastra exporters (Langfuse, OTel, etc.) is already native and would keep working. The value is joining the agent tree onto the surrounding request trace in Sentry — not replacing Mastra Cloud.
Open questions
- Does Mastra deprecate
@mastra/sentry and point at us? If not, users keep installing theirs and we have two exporters.
- Is the cheaper alternative enough? Switching
@mastra/sentry to a @sentry/core peer dep and dropping init/close would fix dual-client + Workers without a move. The remaining reason to own it is convention drift.
- Type maintenance. Hand-writing Mastra
SpanType / attribute shapes follows the LangChain precedent, but those types will drift. Confirm that's the better trade than our conventions drifting out of their exporter.
- Trace join. Confirm
startInactiveSpan on Mastra root spans actually parents under the inbound HTTP request (async context across the observability bus). If they orphan, the whole value proposition is gone.
- Orchestrion chokepoint. Prove the hook on a real Mastra app before writing the config. If the app never constructs
Observability, there is no bus, so injecting into exporters[] is a no-op — we'd have to install observability from the Mastra constructor, which is more invasive than LangChain (callbacks always exist).
- Runtime verification. Confirm the exporter + auto-injection on Node, Bun, Deno, and Cloudflare (Mastra's documented edge target), not Node-only Orchestrion. Cloudflare is the case that is impossible today.
Out of scope
- v11 work. Do not attach this to the 11.0 project.
Not part of v11. Follow-up to owning Mastra agent tracing ourselves instead of relying on
@mastra/sentry.Context
Mastra AI spans currently reach Sentry through
@mastra/sentry(SentryExporter), which lives in the Mastra repo (mastra-ai/mastra#11890). That closed JS-718 / JS-1279 by contributing an exporter upstream rather than adding an in-SDK integration. Their implementation is a useful reference for what we want to capture (agent runs, model generations, tool calls, token usage, workflows).The current setup has a few structural problems:
@mastra/sentrydepends on@sentry/node. Every SDK major — including v11 gen_ai attribute changes — requires a coordinated bump on Mastra's release train. We cannot ship those updates ourselves, which is why the exporter was left out of JS-3110.startInactiveSpan, but it is not a first-party integration: it does not register with Orchestrion, does not participate in default integrations, and does not share our gen_ai conventions pipeline. Routing Mastra through OTel into Sentry remains possible, but it is a worse fit.Sentry.init()on its own@sentry/nodeinstance, which overwrites the application's client. That rules out using it alongside Bun, Deno, Cloudflare, Next.js, or any other@sentry/*setup already in the process.Our existing AI auto-instrumentation also does not cover Mastra.
@mastra/coredoes not depend onai,openai, or@anthropic-ai/sdk, soopenaiIntegration/anthropicAIIntegration/vercelAIIntegrationnever fire. Without the exporter, a fully configured@sentry/nodeapp only gets HTTP spans for model calls — no gen_ai semantics, tokens, tools, or agent tree.Proposed direction to investigate
Port the exporter into
packages/server-utils/src/ai/mastra/(LangChain-style: duck-typed exporter, no Mastra runtime dep, hand-written types), re-export from@sentry/node/ Cloudflare / Edge. Drop SDK lifecycle ownership: read the active client, no-op if none, neverinit/close.Activation is Node Orchestrion, same as LangChain — not the OpenAI pattern of wrapping the model call. LangChain's chat-model hooks don't open spans; Orchestrion wraps
BaseChatModel.invokeso the subscriber can injectcreateLangChainCallbackHandlerintoargs[1].callbacks, and LangChain's own dispatch drives the tree. Mastra already has that dispatch (the observability bus fanningSPAN_STARTED/SPAN_ENDEDto exporters). Orchestrion's job is to inject our exporter soSentry.init()is enough.Wrapping
Agent.generate/Workflow.createRunourselves would rebuild Mastra's span taxonomy and miss scorer, memory, and workflow types unless we wrap those too.Concretely:
packages/server-utils/src/ai/mastra/— the exporter (createMastraExporter()), same role ascreateLangChainCallbackHandlerpackages/server-utils/src/orchestrion/config/mastra.ts— inject channels on@mastra/observability/@mastra/corepackages/server-utils/src/integrations/mastra.ts—mastraIntegrationsubscribes and splices the exporter intoexporters[]Edge/Workers still work off that same config: the Node runtime hook is
--import @sentry/node/orchestrion; Cloudflare/Next get it from the bundler plugin. The exporter itself stays inserver-utilsand doesn't import@sentry/node.Runtime coverage is a goal, not a side effect. Mastra officially runs on Node, Bun, Deno, and Cloudflare;
@mastra/sentrydoes not (Cloudflare cannot load@sentry/node; Bun/Deno load it and then overwrite@sentry/bun/@sentry/deno). First-party ownership should close that gap the same way LangChain does:createMastraExporter()on@sentry/coreonly, re-exported from@sentry/node,@sentry/bun,@sentry/deno,@sentry/cloudflare. Manualexporters: [createMastraExporter()]works anywhere we already have a server SDK — including Cloudflare, which is impossible today.SENTRY_INSTRUMENTATIONSso the Node hook, Bun plugin, and Cloudflare Vite plugin all inject the exporter.mastraIntegrationis re-exported from those packages (same aslangChainIntegration).Fan-out to other Mastra exporters (Langfuse, OTel, etc.) is already native and would keep working. The value is joining the agent tree onto the surrounding request trace in Sentry — not replacing Mastra Cloud.
Open questions
@mastra/sentryand point at us? If not, users keep installing theirs and we have two exporters.@mastra/sentryto a@sentry/corepeer dep and dropping init/close would fix dual-client + Workers without a move. The remaining reason to own it is convention drift.SpanType/ attribute shapes follows the LangChain precedent, but those types will drift. Confirm that's the better trade than our conventions drifting out of their exporter.startInactiveSpanon Mastra root spans actually parents under the inbound HTTP request (async context across the observability bus). If they orphan, the whole value proposition is gone.Observability, there is no bus, so injecting intoexporters[]is a no-op — we'd have to install observability from theMastraconstructor, which is more invasive than LangChain (callbacks always exist).Out of scope