Skip to content

Observability ​

mastra-effect can stitch Mastra's active tool span into Effect's Tracer.ParentSpan at the Promise FFI boundary (Toolkit.toMastraTools / Agent.of). Effect child spans created with Effect.withSpan nest under Mastra's execute_tool span — one trace tree, no second exporter.

Opt in with MastraObservability.layer() ​

Span stitching is opt in. Provide MastraObservability.layer() in your Effect context to enable it. Without the layer, tool FFI is a plain no-op.

ts
import { Agent, MastraTool, Toolkit } from "mastra-effect"
import { MastraObservability } from "mastra-effect/Observability"
import { Context, Effect, Layer } from "effect"

const toolkit = Toolkit.make(MyTool)

class DocsAgent extends Context.Service<DocsAgent>()("app/DocsAgent", {
  make: Effect.gen(function* () {
    return yield* Agent.of("docs-agent", {
      name: "Docs Agent",
      instructions: "Be helpful.",
      model: "openai/gpt-4o",
      toolkit,
      handlers: {
        my_tool: Effect.fn(function* (params) {
          yield* Effect.withSpan("handler-work")(Effect.succeed(params))
          return "done"
        }),
      },
    })
  }),
}) {
  static readonly layer = Layer.effect(DocsAgent, DocsAgent.make)
}

const appLayer = DocsAgent.layer.pipe(Layer.provide(MastraObservability.layer()))

When Mastra calls a tool's execute Promise and MastraObservability is in context:

  1. currentSpanFromToolContext reads tracingContext.currentSpan (or legacy tracing.currentSpan).
  2. The bridge enters Mastra async-local context and provides Tracer.ParentSpan to the handler Effect.
  3. Handler Effect.withSpan children inherit the Mastra parent trace/span ids.

Omit the layer to skip stitching ​

If you do not provide MastraObservability.layer(), handlers run without Mastra ALS or Effect ParentSpan stitching at the Promise boundary. This is the default — no per-agent config needed.

Configure Mastra once ​

Set up Mastra observability in your app (exporters, Observability, optional OtelBridge). See Mastra tracing docs.

ts
import { Mastra } from "@mastra/core"
import { Observability } from "@mastra/observability"

const mastra = new Mastra({
  observability: new Observability({
    // exporters, service name, etc.
  }),
})

Low-level FFI helpers ​

ts
import {
  currentSpanFromToolContext,
  enterMastraSpanContext,
  runEffectInMastraSpan,
} from "mastra-effect/Observability"

Use these when you own the Promise boundary outside Agent.of.

What we do not do ​

  • Stand up a second OpenTelemetry exporter or duplicate Mastra's agent-loop tracing.
  • Fork or replace Mastra's agent loop — we only bridge at the tool FFI used by Agent.of.

Optional peers ​

@mastra/observability and @opentelemetry/api are optional peer dependencies. The package imports span types from @mastra/core/observability; install the peers when you configure exporters in your app.