Intro0:00
Hi, I'm Michael, co-founder and CTO of Fourteen.ai, where we're building an AI-native customer support platform. Our systems interact directly with end users, rely on LLMs in production, and have to operate reliably under uncertain conditions. To manage that complexity, we use Effect, a TypeScript library designed for building robust, type-safe, and composable systems.
The TypeScript language itself gives us a great foundation, but it starts to fall short when we're dealing with unreliable APIs, complex dependencies between systems, non-deterministic model outputs, or long-running workflows. Effect gives us the tools to handle such situations confidently as our platform evolves.
Among other things, it provides strong type guarantees across the stack: powerful composition primitives, built-in concurrency streaming, interruptions and retry mechanisms, structured error modeling, and a clean dependency injection system that makes testing and modelization much easier. It also offers very easy observability via open telemetry.
In addition to helping us build more stable, testable, and maintainable code at scale, we chose Effect also because it can be gradually adopted in an existing codebase and feels like a natural extension of TypeScript itself. So, quickly about our architecture: we use Effect across our entire stack.
Here are how the main components fit together. We have a React frontend that powers everything from dashboards to the agent's IDE, knowledge management, insights, analytics, and SDKs. Then we have an internal RPC server that handles app logic built on Effect RPC and a modified version of TanStack Query on the frontend.
Architecture1:27
Our public API server uses Effect HTTP, within OpenAPI docs auto-generated from annotated schemas. Our data processing engine syncs data from CRMs, docs, and databases, and processes it for real-time analytics and reporting. Agent workflows are written in a custom DSL built on Effect, allowing us to mix deterministic and non-deterministic behavior.
And finally, we use a Postgres database for both data and vector storage, with Effect SQL handling queries. Everything is modeled using Effect schemas, so we get runtime validation, encoding, decoding, type-safe input and output handling across the stack, and even auto-generated documentation for free.
Our agents are basically planners: they take input from the user, come up with a plan, choose theright action, workflow, or sub-agent, execute them, and repeat until the task is complete. Actions are small, focused units of execution, like fetching payment info or searching through logs; you can think of them like tool calls.
Agents2:28
Workflows are deterministic, multi-step processes. For instance, canceling a subscription might involve collecting a reason, offering a retention option if applicable, checking eligibility, and then performing the cancellation. Finally, sub-agents group together related actions and workflows into larger domain-specific modules, like a billing agent or a log retrieval agent.
In order to model this complexity, we built a domain-specific language for workflows using Effect's functional pipe-based system as the foundation. And this lets us express things like branching, sequencing, retries, state transitions, and memory in a very clear and composable way.
Now, as you're powering mission-critical systems, reliability really is key. If one LLM provider fails, for instance, we fall back to another provider with similar performance characteristics. For instance, GPT-4.0 mini could fall back to Gemini Flash 2.0 for tool calling.
Reliability3:32
We model this with retry policies, which also track state, so we avoid retrying failed providers. Oftentimes, answers are streamed to the end user. We, for that purpose, duplicate token streams: one directly to the user and one for storage on our side, for instance analytics.
Effect allows us to do that very easily. For testing, we make heavy use of dependency injection to mock LLM providers and simulate failure scenarios. With this DI approach, we can easily swap service providers with mock versions without actually affecting the internals of our system.
DevEx4:24
This brings us to developer experience. With all these tools available, the developer experience for building agentic systems in Effect really is excellent. First of all, it's schema-centric, so we define input, output, and error types upfront. And those schemas come with powerful encoding and decoding built in.
They provide strong type safety guarantees, and they're automatically documented, so we can get clarity and consistency without extra effort. Dependency injection, obviously, is also a strong point. So services are provided at the entry point of our systems. They can be composed however we need and easily mocked for testing.
Dependencies are present at the type level, guaranteeing that compile time that all required services are provided. Services themselves are modular and composable, which makes it easy to override behavior or swap in different implementations without affecting the internals of our system.
And finally, Effect provides very strong guardrails. Engineers who are new to TypeScript can become productive quickly because the framework helps prevent common mistakes. Once you get past the initial learning curve, it's harder to fall into bad patterns.
A bit about the lessons learned here. So Effect is powerful, but using it well takes discipline. It's really nice to write code for the happy path: things look really clean and explicit, and everything seems to just flow. But that can also give you a false sense of safety.
Lessons5:31
It's easy to accidentally catch errors somewhere upstream or out of sight, and silently lose important failures if you're not careful. Dependency injection is another area, I would say, where it can be hard sometimes to grasp at scale. It's great in principle, but tracing where services are provided, especially when you're dealing with multiple layers or subsystems, can become kind of hard to follow.
The learning curve is real. Effect is a big ecosystem with a lot of concepts and tools. It can be overwhelming at first, but really, once you get past this initial bump, things just somehow start to click. And from that point on, the benefits compound.
So at the end of the day, Effect helps us build systems that are predictable and resilient, but it's not magic; you still have to think. One of the great things about Effect is that you don't have to go all in on day one.
Closing6:28
You can adopt it incrementally. Start with a single service or endpoint and build from there. Effect is especially useful for LLM and AI-based systems, where reliability and coping with non-determinism really matter. You want your systems to be predictable and observable, and Effect gives you theright tools to make it happen.
It also brings the rigor of functional programming into real-world TypeScript, but in a way that's practical for production use. You don't have to be a functional programming purist to get a lot of value. Just start small and let the benefits build up over time.
So if you're building with agents, LLMs, or exploring Effect, feel free to get in touch. Thank you.





