From: aidotengineer

Effect is a TypeScript library designed for building robust, type-safe, and composable systems [00:00:17]. It is particularly useful for building reliable AI systems [00:00:03], especially those that interact directly with end-users, rely on LLMs in production, and must operate reliably under uncertain conditions [00:00:07].

Why Effect?

While the TypeScript language itself provides a strong foundation, it can fall short when dealing with [00:00:27]:

Effect provides the tools to handle such situations confidently as a platform evolves [00:00:37].

Key Features

Effect helps in building more stable, testable, and maintainable code at scale [00:01:07] by offering:

  • Strong type guarantees across the stack [00:00:44]
  • Powerful composition primitives [00:00:48]
  • Built-in concurrency, streaming, interruptions, and retry mechanisms [00:00:50]
  • Structured error modeling [00:00:54]
  • A clean dependency injection system that simplifies testing and modernization [00:00:57]
  • Easy observability via OpenTelemetry [00:01:02]

Effect can be gradually adopted into an existing codebase and feels like a natural extension of TypeScript [00:01:14].

Architectural Integration (14.ai Example)

At 14.ai, Effect is used across the entire stack [00:01:23]:

  • React Front End: Powers dashboards, agent UIs, knowledge management, insights, analytics, and SDKs [00:01:28].
  • Internal RPC Server: Handles app logic, built on Effect RPC and a modified TanStack Query [00:01:37].
  • Public API Server: Uses Effect HTTP with auto-generated OpenAPI docs from annotated schemas [00:01:47].
  • Data Processing Engine: Syncs data from CRM, docs, and databases for real-time analytics and reporting [00:01:54].
  • Agent Workflows: Written in a custom DSL built on Effect, allowing a mix of deterministic and non-deterministic behavior [00:02:00].
  • Database: PostgreSQL is used for data and vector storage, with Effect SQL handling queries [00:02:09].

All data is modeled using Effect schemas, providing runtime validation, encoding, decoding, type-safe input/output handling, and auto-generated documentation [00:02:15].

Agent Workflow Design

Agents are designed as planners that take user input, devise a plan, choose actions, workflows, or sub-agents, execute them, and repeat until the task is complete [00:02:28].

  • Actions: Small, focused units of execution, similar to tool calls (e.g., fetching payment info, searching logs) [00:02:40].
  • Workflows: Deterministic multi-step processes (e.g., canceling a subscription involves collecting a reason, offering retention, checking eligibility, and performing cancellation) [00:02:51].
  • Sub-agents: Group related actions and workflows into larger, domain-specific modules (e.g., a billing agent or log retrieval agent) [00:03:03].

This complexity is modeled using a domain-specific language for workflows built on Effect’s functional pipe-based system. This allows expressing concepts like branching, sequencing, retries, state transitions, and memory in a clear and composable way [00:03:12].

Reliability and Testing

For mission-critical systems, reliability is key [00:03:32]. Effect facilitates this by:

  • Fallback Mechanisms: If one LLM provider fails, the system can fall back to another with similar performance characteristics (e.g., GPT-4 Mini to Gemini Flash 2.0 for tool calling) [00:03:36]. This is modeled with retry policies that track state to avoid retrying failed providers [00:03:47].
  • Stream Duplication: Answers streamed to end-users can be duplicated, sending one stream directly to the user and another for internal storage (e.g., analytics) [00:03:55]. Effect makes this process easy [00:04:05].
  • Dependency Injection: Heavy use of dependency injection allows mocking LLM providers and simulating failure scenarios for testing. Services can be swapped with mock versions without affecting system internals [00:04:09].

Developer Experience

Effect provides an excellent developer experience for building agentic systems [00:04:25]:

  • Schema-centric Design: Input, output, and error types are defined upfront, providing powerful encoding/decoding, strong type safety guarantees, and automatic documentation [00:04:32].
  • Dependency Injection: Services are provided at the system’s entry point, allowing easy composition and mocking for testing. Dependencies are guaranteed at the type level at compile time [00:04:48].
  • Modularity and Composability: Services are modular, making it easy to override behavior or swap implementations without impacting system internals [00:05:06].
  • Strong Guard Rails: Effect helps prevent common mistakes, allowing engineers new to TypeScript to become productive quickly [00:05:14].

Lessons Learned

While powerful, using Effect effectively requires discipline [00:05:31].

  • Error Handling: It’s easy to accidentally catch errors upstream or out of sight, silently losing important failures if not careful [00:05:49].
  • Dependency Injection at Scale: Tracing where services are provided across multiple layers or subsystems can become challenging [00:05:55].
  • Learning Curve: Effect has a significant ecosystem with many concepts. It can be overwhelming initially, but benefits compound once the initial learning curve is overcome [00:06:10].

Ultimately, Effect helps build predictable and resilient systems, but it requires thoughtful design [00:06:27].

Incremental Adoption and Benefits

A significant advantage of Effect is its incremental adoptability [00:06:37]. One can start with a single service or endpoint and expand from there [00:06:41].

Effect is particularly useful for LLM and AI based systems where reliability and coping with non-determinism are crucial [00:06:47]. It provides the tools to make systems predictable and observable [00:06:53].

It brings the rigor of functional programming into real-world TypeScript in a practical way for production use [00:07:01]. One doesn’t need to be a functional programming purist to gain significant value; starting small allows benefits to build up over time [00:07:07].