Skip to content

Getting started ​

mastra-effect wraps Mastra with Effect 4. Use it when you want typed tool handlers, Layer-based dependencies, and Assistant UI integration for human-in-the-loop flows — without replacing Mastra's agent loop.

Early / experimental. API may change and Mastra surface coverage is incomplete.

Install ​

bash
pnpm add mastra-effect effect@rc @mastra/core
# optional — only when using MastraMemory
pnpm add @mastra/memory

For client HITL rendering:

bash
pnpm add mastra-effect @assistant-ui/react react

Peer dependencies ​

PackageRole
effect@^4.0.0-rc.113Effect 4 runtime
@mastra/core@^1.66.0Agent, tools, stream types
@mastra/memory@^1.29.0Memory implementation (optional peer)
@assistant-ui/reactAssistant UI runtime (mastra-effect/assistant-ui)
reactUI runtime (mastra-effect/assistant-ui)

Server tools ​

Define a tool, build a toolkit, and wire an agent with Context.Service + Agent.of:

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

class ApiKey extends Context.Service<ApiKey, { readonly token: string }>()("app/ApiKey") {}

const SearchDocs = MastraTool.make("search_docs", {
  description: "Search project documentation",
  inputSchema: Schema.Struct({ query: Schema.String }),
  outputSchema: Schema.String,
})

const toolkit = Toolkit.make(SearchDocs)

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: {
        search_docs: Effect.fn(function* ({ query }) {
          const api = yield* ApiKey
          return `${api.token}:${query}`
        }),
      },
    })
  }),
}) {
  static readonly layer = Layer.effect(DocsAgent, DocsAgent.make)
}

Effect.gen(function* () {
  const agent = yield* DocsAgent
  return yield* agent.stream("Find docs").pipe(
    Effect.provide(Layer.succeed(ApiKey, { token: process.env.API_KEY! })),
  )
}).pipe(Effect.provide(DocsAgent.layer), Effect.runPromise)

DocsAgent.layer carries the agent service only. Handler dependencies like ApiKey are required on the stream/generate Effect — TypeScript errors if you omit Effect.provide(Layer.succeed(ApiKey, …)).

For Mastra ↔ Effect span stitching at the tool FFI, provide MastraObservability.layer() in your app layer — see Observability.

See Agents for service patterns.

Client HITL ​

Two patterns — see Client tools for the full model.

Assistant UI human tool (annotate with a Tool UI schema):

ts
import { Schema } from "effect"
import { MastraTool, ToolUI } from "mastra-effect"

const PickAssetSchema = ToolUI.make("pick_asset", {
  stateSchema: Schema.Struct({ prompt: Schema.String }),
  answerSchema: Schema.String,
})

const PickAsset = MastraTool.make("pick_asset", {
  description: "Pick an asset",
  inputSchema: Schema.Struct({ prompt: Schema.String }),
  outputSchema: Schema.String,
}).annotate(ToolUI.Schema, PickAssetSchema)

Stream-only status card (extra handler key):

tsx
import { ClientToolkit } from "mastra-effect/assistant-ui"

const clientToolkit = ClientToolkit.of(toolkit, {
  pick_asset: ({ toolUI, resolve }) => <AssetPicker state={toolUI.state} onConfirm={resolve} />,
  status: ({ toolUI }) => <StatusCard text={toolUI.state.text} />,
})

See HITL suspend for server-tool suspend.

Package layout ​

packages/mastra-effect — mastra-effect (subpath exports)
docs/                  — documentation site

Development ​

bash
vp install
vp check
vp run -r test
vp run docs:dev