stratus

Effect Interop

Use Effect services, layers, and typed errors with Stratus tools and models

Stratus ships an @usestratus/sdk/effect entrypoint for applications built with Effect. It lets tools and models return Effect.Effect values, provide service layers, and run Stratus agents inside Effect programs.

The effect package is an optional peer dependency. Install it only when you use the Effect interop entrypoint.

bun add effect

Effect-backed tools

Use effectTool() when a tool needs Effect services, layers, retries, typed failures, or composable resource management.

effect-tool.ts
import { Context, Effect, Layer } from "effect";
import { z } from "zod";
import { Agent } from "@usestratus/sdk/core";
import { effectModel, effectTool, runEffect } from "@usestratus/sdk/effect";
import { toolCallResponse } from "@usestratus/sdk/testing";

class Multiplier extends Context.Tag("Multiplier")<
  Multiplier,
  { readonly multiply: (value: number) => Effect.Effect<number> }
>() {}

const multiply = effectTool({
  name: "multiply",
  description: "Multiply a number",
  parameters: z.object({ value: z.number() }),
  layer: Layer.succeed(Multiplier, {
    multiply: (value) => Effect.succeed(value * 3),
  }),
  execute: (_context, { value }) =>
    Effect.gen(function* () {
      const multiplier = yield* Multiplier;
      const result = yield* multiplier.multiply(value);
      return String(result);
    }),
});

const model = effectModel({
  getResponse: () =>
    Effect.succeed(toolCallResponse([{ name: "multiply", args: { value: 7 } }])),
});

const agent = new Agent({
  name: "calculator",
  model,
  tools: [multiply],
  toolUseBehavior: "stop_on_first_tool",
});

const result = await Effect.runPromise(runEffect(agent, "multiply 7"));
console.log(result.output); // "21"

effectTool() accepts the same control options as tool(): timeout, isEnabled, needsApproval, and retries. The execute function still receives Stratus context, parsed params, and tool execute options with an AbortSignal.

Effect-backed models

Use effectModel() when your model adapter is already expressed as Effect.

effect-model.ts
import { Effect } from "effect";
import { effectModel } from "@usestratus/sdk/effect";

const model = effectModel({
  getResponse: (request, options) =>
    Effect.tryPromise({
      try: () => callProvider(request, options),
      catch: (error) => error,
    }),
});

For streaming models, provide getStreamedResponse() as an Effect that returns an AsyncIterable<StreamEvent>.

effect-stream-model.ts
const model = effectModel({
  getResponse,
  getStreamedResponse: (request, options) =>
    Effect.succeed(providerStream(request, options)),
});

If you omit getStreamedResponse(), Stratus derives a minimal stream from getResponse() by emitting a content_delta, any tool call events, and a final done event.

Running inside Effect

Wrap the core run APIs with Effect values:

run-effect.ts
import { Effect } from "effect";
import {
  resumeRunEffect,
  runEffect,
  streamEffect,
} from "@usestratus/sdk/effect";

const program = Effect.gen(function* () {
  const result = yield* runEffect(agent, "Summarize this ticket.");
  return result.output;
});

const output = await Effect.runPromise(program);

runEffect() and resumeRunEffect() return Effect.Effect<RunResult | InterruptedRunResult, StratusEffectError, never>. streamEffect() returns a StreamedRunResult inside an Effect.

Cancellation

Abort signals flow through both directions:

DirectionBehavior
Effect runtime to StratusEffect.runPromise(program, { signal }) passes the signal into run()
Stratus to Effect toolsTool execute options include options.signal
Stratus to Effect modelsModel request options include options.signal

This keeps route cancellation, user aborts, and tool timeouts aligned with the rest of the Stratus run loop.

Errors

Promise runner failures are wrapped in StratusEffectError, a tagged Effect error with the original cause.

errors.ts
import { Effect } from "effect";
import { runEffect, StratusEffectError } from "@usestratus/sdk/effect";

const error = await Effect.runPromise(Effect.flip(runEffect(agent, "hello")));

if (error instanceof StratusEffectError) {
  console.error(error.message, error.cause);
}

Tool failures still follow normal Stratus tool error handling. If an Effect-backed tool fails, the run loop formats the error as a tool result so the model can recover, unless your run options change that behavior.

Exports

ExportUse
effectTool()Create a Stratus function tool whose execute function returns an Effect
effectModel()Create a Stratus model from Effect-backed response functions
runEffect()Run an agent inside an Effect program
resumeRunEffect()Resume an interrupted run inside an Effect program
streamEffect()Create a streamed run inside an Effect program
StratusEffectErrorTagged error wrapper for failures from promise-based Stratus APIs
Edit on GitHub

Last updated on

On this page