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 effectEffect-backed tools
Use effectTool() when a tool needs Effect services, layers, retries, typed failures, or composable resource management.
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.
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>.
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:
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:
| Direction | Behavior |
|---|---|
| Effect runtime to Stratus | Effect.runPromise(program, { signal }) passes the signal into run() |
| Stratus to Effect tools | Tool execute options include options.signal |
| Stratus to Effect models | Model 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.
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
| Export | Use |
|---|---|
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 |
StratusEffectError | Tagged error wrapper for failures from promise-based Stratus APIs |
Last updated on