Skip to content
AI Engineering
AI Engineering12 min read0 views

Build an AI Agent with Effect.ts: Typed Errors & Retries (2026)

Effect.ts v3 makes every LLM failure a typed effect channel. Wire OpenAI calls with Schedule retries, fallback layers, and Cause inspection — no try/catch.

TL;DR — Effect.ts v3 (production at Vercel, Prisma) makes errors first-class in the type system. Combine Effect.retry with Schedule.exponential and a fallback Layer and your AI agent gracefully degrades from gpt-4o → gpt-4o-mini → cached answer.

What you'll build

An "answer service" with three failure modes — RateLimitError, TimeoutError, ContentFilterError — each surfaced as a typed channel. The service retries 3x on rate-limits, falls back to a cheaper model on timeouts, and short-circuits on content filter.

Prerequisites

  1. effect@^3.10, @effect/ai@^0.6, @effect/ai-openai@^0.6, Node 20+ or Bun.

Architecture

flowchart TD
  Q[Question] --> P[Effect program]
  P --> R{retry policy}
  R -->|429| RT[exp backoff x3]
  R -->|503/timeout| FB[fallback Layer mini]
  R -->|content filter| CR[Cause.fail]
  RT --> R
  FB --> O[reply]

Step 1 — Define typed errors

```ts import { Data } from "effect";

export class RateLimitError extends Data.TaggedError("RateLimit")<{ retryAfter: number }> {} export class TimeoutError extends Data.TaggedError("Timeout")<{ ms: number }> {} export class ContentFilter extends Data.TaggedError("ContentFilter")<{}> {} ```

Step 2 — Wrap OpenAI as an Effect

```ts import { Effect, Schedule, Duration } from "effect"; import OpenAI from "openai"; const oa = new OpenAI();

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →

export const ask = (q: string, model: string) => Effect.tryPromise({ try: () => oa.chat.completions.create({ model, messages: [{ role: "user", content: q }], }), catch: (e: any) => { if (e?.status === 429) return new RateLimitError({ retryAfter: 2 }); if (e?.code === "ETIMEDOUT") return new TimeoutError({ ms: 30000 }); if (e?.error?.code === "content_filter") return new ContentFilter(); return e; }, }).pipe(Effect.map((r) => r.choices[0].message.content ?? "")); ```

Step 3 — Compose with retry + fallback

```ts import { pipe } from "effect";

const retryOnRate = Schedule.exponential(Duration.seconds(1)) .pipe(Schedule.intersect(Schedule.recurs(3)), Schedule.whileInput((e: any) => e._tag === "RateLimit"));

export const answer = (q: string) => pipe( ask(q, "gpt-4o"), Effect.retry(retryOnRate), Effect.catchTag("Timeout", () => ask(q, "gpt-4o-mini")), Effect.catchTag("ContentFilter", () => Effect.succeed("I can't help with that.")), ); ```

Step 4 — Run + observe Cause

```ts import { Effect, Cause } from "effect";

const result = await Effect.runPromiseExit(answer("hello")); if (result._tag === "Failure") console.error(Cause.pretty(result.cause)); ```

Step 5 — Tracing

```ts import { NodeSdk } from "@effect/opentelemetry"; const Tracing = NodeSdk.layer(() => ({ resource: { serviceName: "ai-agent" }, spanProcessor: new BatchSpanProcessor(new OTLPTraceExporter()), })); Effect.runPromise(answer("...").pipe(Effect.provide(Tracing))); ```

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

Step 6 — Add a fallback Layer

```ts import { Layer } from "effect"; import { OpenAiClient } from "@effect/ai-openai";

const PrimaryAI = OpenAiClient.layer({ apiKey: process.env.OPENAI_API_KEY! }); const FallbackAI = OpenAiClient.layer({ apiKey: process.env.AZURE_KEY!, baseUrl: "https://eastus.openai.azure.com" }); const AI = Layer.orElse(PrimaryAI, () => FallbackAI); ```

Pitfalls

  • Async/await mental model: Effect generators (Effect.gen) read like async/await but yield Effects — new contributors hit confusion fast.
  • Bundle size: effect core is ~50kb min+gzip. Tree-shaking is good but watch lambda cold start.
  • Schedule precedence: Schedule.intersect ANDs, union ORs — easy to mix up.

How CallSphere does this in production

CallSphere uses Effect-style layered fallbacks across 37 agents in 6 verticals with 90+ tools and 115+ DB tables — primary OpenAI → Azure OpenAI → cached answer. Healthcare (FastAPI), OneRoof (Next.js 16 + React 19), Salon (NestJS 10 + Prisma), Sales (Node.js 20 + React 18 + Vite). $149/$499/$1,499, 14-day trial, 22% affiliate.

FAQ

Why not try/catch? Try/catch loses the error type at the function boundary — Effect keeps it.

Can I incrementally adopt? Yes. Convert one service to Effect, return Promises at the edge with Effect.runPromise.

Performance overhead? ~5-15µs per Effect step, dwarfed by network latency.

Effect.ts vs fp-ts? Effect supersedes fp-ts in 2026 — fp-ts maintainer joined Effect team.

Sources

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

AI Engineering

Vercel AI SDK v5 Agent Patterns: stopWhen, prepareStep, and Loop Control

AI SDK 5 ships fully typed chat for React, Svelte, Vue, and Angular plus first-class agent loop primitives. Here are the patterns that matter for shipping in 2026.

AI Engineering

Mastra.ai: The TypeScript Agent Framework Worth Trying in 2026

Mastra.ai is becoming the go-to TypeScript agent framework in 2026. Workflows, RAG, evals, and an honest comparison with Vercel AI SDK 5 for serious teams.

Technology

Reliability Patterns for AI Systems: Circuit Breakers, Retries, Fallbacks

Circuit breakers, retries, and fallbacks for AI systems require LLM-aware tweaks. The 2026 reliability patterns that actually hold up.

AI Engineering

Idempotency Keys for AI Tool Calls: Stripe-Style Safety When the Agent Retries

Network retries, queue redeliveries, and saga compensations all create double-execution risk. Idempotency keys at the tool boundary prevent your AI agent from booking the same slot twice.

AI Infrastructure

Dead-Letter Queues for AI Agent Retries: Isolating Poison Messages Without Stalling The Bus

An AI agent that loops forever on a malformed event takes the whole consumer group down. DLQs quarantine poison messages, retry queues handle the transient ones, and a monitoring loop closes the gap.

AI Infrastructure

Build a Convex-Backed Voice Agent with a Reactive Database

Convex's reactive queries auto-push every transcript update to every subscribed client. Real working code for actions, mutations, and OpenAI Realtime streaming.