Effect-TS Primer
Reactive Agents is built on Effect-TS. You don’t need to be an Effect expert to use the framework, but understanding these concepts helps.
Common Effect helpers
Section titled “Common Effect helpers”The effect package is already installed when you use reactive-agents. Pull symbols explicitly so examples are copy-paste friendly:
import { Effect } from "effect";// Advanced composition (layers, services, tests):import { Layer, Context, Schema, Data, Ref } from "effect";| Helper | When to use |
|---|---|
Effect.succeed(x) | Pure success value — lifecycle hooks, trivial tool handlers |
Effect.fail(e) | Fail the Effect with error e (prefer tagged errors in app code) |
Effect.sync(() => …) | Wrap synchronous code; use Effect.try if it might throw |
Effect.try(() => …) | Wrap synchronous code that might throw (e.g. JSON.parse) |
Effect.promise(() => somePromise) | Bridge an existing Promise |
Effect.gen(function* () { … yield* … }) | Multi-step workflows, yield* services from Context.Tag |
Effect.runPromise(program) | Run an Effect from async main or tests |
program.pipe(Effect.provide(layer)) | Supply dependencies before runPromise |
Effect.catchTag("Tag", handler) | Recover from a single tagged error type |
Most builder users only touch Effect.succeed, Effect.fail, and sometimes Effect.try or Effect.promise inside hooks and tools.
Framework Effect API (@reactive-agents/runtime)
Section titled “Framework Effect API (@reactive-agents/runtime)”These are Reactive Agents entry points and utilities — not re-exports from effect, but built to work with Effect programs:
| API | What it does | Defined in |
|---|---|---|
ReactiveAgentBuilder.buildEffect() | Builds the agent as Effect.Effect<ReactiveAgent, Error> so you can yield* it inside Effect.gen | builder.ts |
ReactiveAgent.runEffect(input) | Runs a task as Effect.Effect<AgentResult, Error> — pipe Effect.retry, Effect.timeout, etc. | same |
unwrapError, unwrapErrorWithSuggestion, errorContext | Unwrap nested FiberFailure / Cause from Effect.runPromise into plain errors and optional fix hints | errors.ts |
createRuntime() / createLightRuntime() | Produces Layer stacks you provide before running engine-level Effect programs | runtime.ts |
LifecycleHook.handler | Must return Effect.Effect<ExecutionContext, ExecutionError> | types.ts |
Note: The lightweight agentFn, pipe, parallel, and race helpers in compose.ts are Promise-based callables for chaining agents; they are not Effect wrappers.
import { Effect } from "effect";import { ReactiveAgents, unwrapError,} from "@reactive-agents/runtime";
const program = Effect.gen(function* () { const agent = yield* ReactiveAgents.create() .withProvider("anthropic") .buildEffect(); return yield* agent.runEffect("Summarize Effect-TS in one paragraph");});
const result = await Effect.runPromise(program).catch((e) => { throw unwrapError(e);});Import unwrapError from @reactive-agents/runtime (the root reactive-agents package does not re-export it today).
Effect<A, E, R>
Section titled “Effect<A, E, R>”An Effect is a description of a computation that:
- Succeeds with value
A - Fails with error
E - Requires services
R
import { Effect } from "effect";
// A simple Effect that succeedsconst hello = Effect.succeed("Hello, world!");
// An Effect that might failconst parse = (input: string): Effect.Effect<number, Error> => Effect.try(() => JSON.parse(input));
// An Effect that requires a serviceconst greet = Effect.gen(function* () { const agent = yield* AgentService; return yield* agent.getAgent("agent-1");});Layer<Out, Err, In>
Section titled “Layer<Out, Err, In>”A Layer is a recipe for constructing services:
- Provides service
Out - Might fail with
Err - Requires dependency
In
import { Layer, Context, Effect } from "effect";
// Define a serviceclass MyService extends Context.Tag("MyService")< MyService, { readonly greet: (name: string) => Effect.Effect<string> }>() {}
// Create a Layer that provides itconst MyServiceLive = Layer.succeed(MyService, { greet: (name) => Effect.succeed(`Hello, ${name}!`),});Context.Tag
Section titled “Context.Tag”Tags identify services in the Effect dependency injection system:
class AgentService extends Context.Tag("AgentService")< AgentService, { readonly createAgent: (config: AgentConfig) => Effect.Effect<Agent, AgentError>; readonly getAgent: (id: AgentId) => Effect.Effect<Agent, AgentNotFoundError>; }>() {}Schema
Section titled “Schema”Effect Schema provides runtime validation with TypeScript types:
import { Schema } from "effect";
const AgentConfig = Schema.Struct({ name: Schema.String, model: Schema.String, maxIterations: Schema.Number.pipe(Schema.between(1, 100)),});
type AgentConfig = typeof AgentConfig.Type;Data.TaggedError
Section titled “Data.TaggedError”Typed, pattern-matchable errors:
import { Data, Effect } from "effect";
class AgentNotFoundError extends Data.TaggedError("AgentNotFoundError")<{ readonly agentId: string;}> {}
// Pattern match on _tagconst handle = Effect.catchTag("AgentNotFoundError", (e) => Effect.succeed(`Agent ${e.agentId} not found`));Mutable state in a pure, concurrent-safe way:
import { Ref } from "effect";
const counter = yield* Ref.make(0);yield* Ref.update(counter, (n) => n + 1);const value = yield* Ref.get(counter);For Framework Users
Section titled “For Framework Users”If you’re using the ReactiveAgents.create() builder, you interact with standard async/await:
// No Effect knowledge needed!const agent = await ReactiveAgents.create() .withProvider("anthropic") .build();
const result = await agent.run("Hello!");The Effect-TS internals are only exposed when you need advanced control via buildEffect() and runEffect() — see Framework Effect API. For raw Effect.* usage, add import { Effect } from "effect" — see the generic helpers table above.