By Sagar Shankaran, Founder of CallSphere
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.
Key takeaways
TL;DR — Effect.ts v3 (production at Vercel, Prisma) makes errors first-class in the type system. Combine
Effect.retrywithSchedule.exponentialand a fallback Layer and your AI agent gracefully degrades from gpt-4o → gpt-4o-mini → cached answer.
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.
effect@^3.10, @effect/ai@^0.6, @effect/ai-openai@^0.6, Node 20+ or Bun.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]
```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")<{}> {} ```
```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.
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 ?? "")); ```
```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.")), ); ```
```ts import { Effect, Cause } from "effect";
const result = await Effect.runPromiseExit(answer("hello")); if (result._tag === "Failure") console.error(Cause.pretty(result.cause)); ```
```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.
```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); ```
Effect.gen) read like async/await but yield Effects — new contributors hit confusion fast.effect core is ~50kb min+gzip. Tree-shaking is good but watch lambda cold start.Schedule.intersect ANDs, union ORs — easy to mix up.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.
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.
Written by
Sagar Shankaran· Founder, CallSphere
Sagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
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.
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.
Circuit breakers, retries, and fallbacks for AI systems require LLM-aware tweaks. The 2026 reliability patterns that actually hold up.
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.
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.
Convex's reactive queries auto-push every transcript update to every subscribed client. Real working code for actions, mutations, and OpenAI Realtime streaming.
© 2026 CallSphere LLC. All rights reserved.
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI