Cause & Effect

Get support from the Effect community

Louis Vichy, co-founder of OpenRouter, joins Johannes Schickling and Michael Arnaldi to talk about OpenRouter’s TypeScript stack, internal tooling powered by Effect, and the engineering challenges of scaling an AI platform processing trillions of tokens weekly.

Effect is an ecosystem of tools to build production-grade software in TypeScript.
#Effect #TypeScript #Spiko #softwareDevelopment

Song: Dosi & Aisake - Cruising [NCS Release]
Music provided by NoCopyrightSounds
Free Download/Stream: http://ncs.io/Cruising
Watch: http://ncs.lnk.to/CruisingAT/youtube
  • (00:00) - Guest Intro & OpenRouter
  • (07:45) - Why Everything Runs in TypeScript
  • (15:43) - Scaling & Routing in OpenRouter’s Infrastructure
  • (18:03) - Michael’s Story: Why Effect Was Born
  • (23:11) - Effect vs. Result Types and Error Handling
  • (26:04) - Culture, Hiring & Engineering Consistency
  • (30:48) - The Case for Gradual Effect Adoption
  • (32:30) - Generators, Pipe, and Functional Design
  • (47:41) - Observability & Concurrency
  • (58:35) - Agentic Systems, Orchestration & Observability
  • (01:16:41) - Using Effect in OpenRouter’s Internal Tooling

What is Cause & Effect?

Explore how software engineers use Effect to build reliable, production-ready software in TypeScript.

you might think

is just internal tooling,

but we have a lot of

internal tooling, just so you know.

which is why Effect is

actually being used a lot.

So, instead of running a Python eval

suite on the market,

there's a bunch, we write our own, but it's

using Effect.

So, we actually have an eval suite

and also a bulk test suite,

that's all using Effect with Effect retry

and so on and so forth.

it's a very

sophisticated system.

I'm just saying is that if we use,

you might think internal

tooling is like some toy,

but no, we're actually using

it almost on a daily basis,

it's actually production load.

testing out all

the endpoint, right?

Which is a crucial part of our operation.

well, I
mean, almost every respond

transformation is using the pipe now.

And then also I'm

introducing more and more generator.

So, down the line, we finally will,

it will be making it

easier for us to transitioning

over to Effect as needed.

Welcome to Cause & Effect a podcast

about the TypeScript

library and ecosystem called Effect,

helping engineers to build

production-ready software.

I'm your host, Johannes Schickling,

and I've been building with

Effect for over four years.

With this podcast, I want

to help others understand

the powers and benefits of using Effect.

In this episode, I'm

talking to Louis Vichy,

co-founder of OpenRouter,

the unified interface for LLMs.

In this conversation, we dive deep

into the Cloudflare workers architecture,

their routing layers, and how they're

replacing their own result type

implementation with Effect

for better retries,

observability, and long-running

agentic workflows.

Let's get into it.

Hey, welcome everyone.

It's so nice to have you here.

Louis, you're from OpenRouter.

Would you mind introducing yourself?

We also have the Effect

co-creator Michael here.

Awesome. How are you all?

I'm Luis. I'm a co-founder of OpenRouter.

Me and my co-founder, we met

through GitHub essentially.

He was building out a V0 of

OpenRouter using a framework I was

building for my previous

startup and that's how we met.

Then we just love

collaborating with each other and then

around May is when

OpenRouter first launched May 2023.

So early 2023.

Essentially, the idea of OpenRouter is

at the bare bone,

it is actually a

switchboard for AI models.

That's it. We reduce the switching cost

between like Anthropic

and OpenAI and Gemini,

and Moonshot, Cohere, and

Meta to essentially zero.

We're also centralizing

all the payment for you.

Instead of like 60 different providers,

you pay just us and

then you get some credit,

and then you can call

any AI model you want.

Essentially, that is the

basic part of OpenRouter,

but OpenRouter is

actually much more than that.

You have seen Elon tweet about us on X.

the secondary product of

OpenRouter is the data that

we are allowing everyone else to access.

The data about which model

is being spent on the most,

or which model is

getting the most utility.

We measure them based on tokens.

A token is about roughly full characters.

Last week, we did about

five trillion tokens a week.

Exactly a year ago though,

we were doing about 150

billion tokens a week.

That's roughly about

more than 20x growth.

By the way, this feature was released

literally the first month

of the product, I think.

Basically, my co-founder

and I were thinking about,

"Okay, we have this data.

Should we just let everyone see it?"

Because at the time,

there were so many benchmarks.

There were so many

Llama or Meta Llama benchmark,

Llama one, right?

They're claiming some

of these Llama fighting

will be better than GPT 3.5.

And we were like, "Is

that actually the case?"

Because when we were trying them locally,

they're dumb as hell.

So we were like, "Okay,

let's make a benchmark

that's based on real

utilization, real usage, real money,

being bet on the

model, like a horse race.

And see which one get

the most of this money."

And that's essentially

is the secondary product,

which is the ranking

page where everyone can see

which model is being spent the most on.

And we do cap the amount

of free usage on a provider.

So all of that is actual usage.

Like people spending money.

That is awesome.

So you mentioned you met

your co-founder in 2023,

May 2023.

Now more than two years later,

where are you at in terms of a company?

Is it just the two of you?

Probably not.

So maybe you can shed some light

on like OpenRouter as a company.

So we were two people

writing on the thing.

He was checking Datadog.

I was just writing

code for up until January.

And then we have our

first engineering hire

who also joined us

through an open source project.

We released a couple of open source

project at the time,

early 2024.

And then Sam joined us

through he sent us a PR

on another project.

and then we still have him to this day.

last year we have four people.

Now we have about 22.

So team grow about 5x,

but the utilization

has grew more than 20x.

So I think we have a decent wiggle room.

We make enough of coffee, right?

So got to buy more coffee.

essentially the

growth curve is exactly the same.

The first year we went

from zero to 7 billion.

7 billion token, you know, in a week.

And that's when I showed my dad.

And then a year later, September 2024,

we went to about 150 billion.

And that's when I

show, you know, my friend

is my south friend.

And now, right, I'm showing you guys

and a lot of people we're interviewing,

we have five trillion.

So it's been 20x and

then more than 20x.

Hopefully the next year we'll be higher,

but we'll see, right?

Well, AI usage is only gonna increase.

And as far as I know,

from friends and so on and so forth,

OpenRouter is probably the best way

to access those different models.

We had internal

conversations and one key aspect

that I truly like about what you do

is that you're not

just a proxy to models.

You actually

consolidate the model responses,

which is key here.

So a lot of people actually

mistake us as a wrapper, right?

Building a wrapper is very easy,

but building a dev tool is hard.

Building a dev tool on top

of the API is extremely hard,

right, because you have

to ensure that the Dev X,

the user experience of the

developer is actually good

so that they can stick to us.

And this is the key kind of mistake

that people didn't think about

until they need us, right?

We have seen this a lot, by the way,

is that user

essentially didn't think too much

of OpenRouter, they

try it once, they're like,

"Okay, I'm gonna use two or three model,

"it doesn't matter, so

I'm gonna do my own."

A lot of people have

built their own OpenRouters.

And then the moment they have to say,

"Okay, now I have to

add Anthropic, you know,

Claude 4.5 came out,

they have to scramble

their entire weekends, right?

Either weekends or

they're Tuesday and Wednesday

to add a model.

With OpenRouter,

it's right there, right?

You add the same

code, you allow your user

to switch up model name, you're done.

And that essentially has been a case

of how OpenRouter has been growing,

is that every time there's a new model,

we do partner with this

model to launch them early,

and then the moment the model is launched

is outright on OpenRouter

and people who miss the

train, right, come back to us,

and need people coming in

because they also want to try the model.

That's kind of

essentially the growth engine

we have had thus far, which

is leveraging our partnership

with all this new model coming in,

Can you maybe categorize

sort of like the products

you had to build out over the years,

and also maybe

orthogonally the challenges

that maybe you hit right away

or that just appeared

over time, I suppose,

like working at that scale,

just like pulling on that scale thread,

there's probably like so

many interesting challenges

that are coming towards us.

So the one thing that

me and my co-founder really like

is to have type safety, to be honest.

So, I kid you not, like the

initial version of OpenRouter

all the models, all the endpoints,

were TypeScript object,

TypeScript literal

type object in the code.

And then the way that we do reference,

we would do strict type satisfy.

As a way to do literally

like foreign key reference

in the code.

So our code were a huge

database of these endpoints

it's not crazy.

The moment we add a new field, right,

an alert, right, because

of TypeScript and satisfy,

an alert across all our code base saying,

hey, you have to add this

field in every, you know,

this place.

And then at the time,

right, when we add new model,

we add new endpoint, we

just tell Devin to do it.

It's automated because of type,

because of type safety, right?

The moment we just want to add something,

we tell the model, it changed something,

and then the rest is

just TypeScript, you know,
at type error.

So it was not that crazy.

it was actually usable.

even satisfying a bunch of, you know,

like literal number constraint as well,

which is very fun.

I mean, you know, fun

doesn't scale, right?

So we'll talk a lot more

about the data in a moment,

but is this entire system

still written all in TypeScript

or have you moved some parts

that are maybe like critical

to be more efficient?

Are those written in Go and Rust

or is everything still in TypeScript?

Nope, everything.

Everything's still in TypeScript.

We very much just TypeScript all in.

I mean, the main

reason why I think is fine

for OpenRouter to be strictly

TypeScript is because,

I mean, that won't lie, right?

When we actually hit the bottleneck,

we sure will switch to Go and so on,

but right now we're running on top of,

you know, Cloudflare.

And essentially, the

Cloudflare worker is much, you know,

better to use TypeScript with.

The tight strictness of TypeScript

and also the flexible, the

dynamic nature of it, right?

Allow us to move much faster.

The velocity of the engineers is much

faster, I would argue.

Technically Go can do the same.

It's just that we start with TypeScript

and we use TypeScript almost everywhere

on both front end and back end, right?

So I think it's best for a

team to have the same language.

I mean, time and time again,
I mean, time and time again,

the same story sort

of like repeats, right?

Yep.

Like if you're looking at Facebook

and Mark having written

the first Facebook prototype

in PHP, guess what?

Still mostly in PHP.

Yeah.
The same for like--

Instead of switching

from PHP, they ended up

making a new kind of

Built the hack.

Yeah, exactly.

Or it's like, I mean, that's

how TypeScript came to be.

It's like, I would say

the rationale of just,

the whole idea, right,

is that let's make sure

we just speak the same language.

So, in our code base,

everything is TypeScript.

Even the scripting is TypeScript.

No bash.

We don't use bash.

We use ZX and TS up to write script

to run like database migration or

something like that.

Or like even instant response scripts or

something like that.

So that every time that we communicate

is in the same language.

And so that someone

else can pick it up, right?

And we never really have dead code

because TypeScript is

very easy to kind of pull

into a shared package

and then you can reuse it.

Honestly, I'm kind of surprised

that this is not more common.

In the Ruby on Rails world, for example,

it's like so normal.

Like obviously you

build all of your scripts

as Ruby scripts and then you run them.

it's so surprising to

me that in a JavaScript repo

where like arguably

it's as nice, if not nicer,

to write scripts in JavaScript.

This is like in the name.

Like there's still

often like more bash script

than like custom JavaScript files.

It takes about three years

for someone to understand

that you don't need your,

database migration script to be as fast.

you also don't need

like a bespoke framework

that you're allowed to have like a script

that you can just like, even

if it does only a single line,

console log, hello

world, like that's fine.

You don't need like a

bespoke CLI thingy to run it.

Well, also I would also argue that it

take about five years

for someone to ease into all the

TypeScript, you know,

like not TypeScript, but most

of Node.js ecosystem tooling.

Right?

It took a while.

Because if someone has

been in a Golang for a while,

they probably have, they

probably pollute them, you know,

their home directory with

all the Go binary already.

I mean, I did the mistake three times.

So I'm like, holy moly.

I have so many of that.

the zero point five version of Go,

Was that you add Go in

your user bin

You clone it down and then

every single main module,

you just, you keep on cloning it.

There's no, there's no Go model.

Your machine becomes a Go machine.

Yes.
Your machine is essentially a Go machine.

And then with

Python, for example, right?

You get used to either

using UV or PIP, right?

You get used to
running almost everything.

And so when people

are, you know, kind of,

used to it, right?

It's really hard for them to switch gear.

And this is why they

having a list

ensuring that when the

team is still growing,

we ensure that the

consistency is there, right?

Then there's no

fragmentation of knowledge, right?

And people can learn very quickly too.

It's not that crazy.

So tell me a bit more about

how you've picked Cloudflare

back then

did you have a lot of

experience with Cloudflare workers

from previous projects and how, like,

I suppose there's also a

non-trivial amount of money

being spent.

So I'm curious how you

think about this as well.

It's actually very cheap.

I'm not, I'm not kidding you.

Like the infrastructure

cost is break even completely.

Just by the fee.

So actually before

Cloudflare, we were all on Vercel.

Everything was on Vercel until they,

they decided to charge

for egress cost, right?

but then also the,

the network connection layer

was not first class, right?

So everything was completely abstracted.

And we also have, even

though we were on Vercel,

we also have a Cloudflare

proxy on top as well, right?

For all the, you know, like

for the DDoS protection and so on.

So there's a three loop, three hoop.

And so, I'm curious to

hear a little bit more

about the

software aspect of like,

what does a typical line of

code in your TypeScript app

actually look like?

So currently you're

running on Cloudflare workers.

So in Cloudflare workers, for

those who have never written

a Cloudflare worker, it's essential,

it's very web centric.

So you have like an export default there.

And in there, that's

basically you export an object

that returns a fetch function.

And in that fetch function,

you get a request coming in,

you do your thing, and

then you send back a response.

It's like very simple, works really well.

But like how you do your thing,

it does like, that's the

actual app that you're running.

So I'm curious like how that looks like.

The way I'm thinking about Cloudflare

or worker in general is

just the entry point, right?

And if you ever kind

of use any framework,

entry point are just

supposed to be entry point.

You just try to attract your core logic

as much as possible so that

you can pull it into a package

or a shared package so that

regardless of entry point,

but when we moved from

Vercel to Cloudflare,

it's like literally

just moving entry point.

And so there's no drag,

so the core logic that we

have is plain TypeScript

function, right, with strict typing.

A lot of time I would love

to infer the type, right?

Because I hate typing thing manually.

I like to infer the

type as much as possible.

This is why one of my

favorite brand teasers

I give people is inferring type from

third party library.

You have to see if they have struggled

through this thing before.

the Cloudflare code

is we just use the Cloudflare

Hono library to wrap all the API

under the same path and then does it.

at that point, just the entry point

with authentication and middleware

and the rest of the core

logic are plain function.

Got it.

So how is that like that

inner chunk of business logic

that's like actually

what makes OpenRouter work,

like where you're

actually route the requests.

So can you describe a

little bit the architecture

and like how trivial is

what's happening inside there

or how non-trivial, probably the latter.

So maybe you can describe roughly

like what is the life cycle of a request

as it walks through like various stages

of what makes OpenRouter, OpenRouter.

let's make the tech request

we have Llama for

Maverick, let's say maverick.

Maverick has about what like

more than 10 different providers

or serving the model.

So when a provider is serving the model,

we call that an endpoint.

That's a concept that we have internally.

A provider is, this company, right?

Is the provider containing metadata

but the provider

meaning like company names,

what they do or their location, right?

Like the data privacy

policy and stuff like that.

The model is like the actual model on,

that's the actual weight to the model

with like context

length combined those two,

we call that an endpoint.

let's say when we call

the Llama 4 model Maverick,

there's like more than 10 endpoints.

The first thing is authentication, right?

But authentication is

literally just a function.

We authenticate first.

And then after you authenticate, we

return a result, right?

Either results, we

really subscribe to the result

monad paradigm.

So almost every function

we have returning results,

we check the result and then we return

a respond as needed.

we would then take the request

and we pass it through

each of these endpoint.

And while passing through the endpoint,

so there are two stack that we applying

for each of the requests.

One stack is the

routing, the routing stack.

The routing stack

allow us to like prioritize

and order the endpoint

according to what the user wants

or according to some

heuristic like latency,

throughput or price.

So the routings are a

stack of decision step.

And secondly, we have a plugin layers.

The plugin layer allow us to like

basically transform the request

and also transform respond

as the stream coming back up

have a FIFO stack style.

And that's essentially is

the life cycle of requests.

And then we just keep

on hitting each endpoint

until one that's

actually, one is like saying,

A, we can use a 200, we

can now return some data.

That's like a full cycle.

And the code for all

of this is very modular.

Every step is a function.

And then we just have an orchestration

that's run through each of those

would be very trivial to rewrite into

Effect down the line.

But we write this code

before there was Effect.

I think it was

Effect 1.x or something.

So, and I have to say

just a brief comment

because what you

described is sounding very similar

to what I had to do in my past,

which is some sort of

consolidation engine

between different APIs

that expose a single format.

The reason Effect is born,

like more than seven years ago now,

it's because I was CTO of

a Fintech company London.

And we were doing consolidation

over peer to peer landing providers.

And we would buy and

securitize those products

to then sell them to

institutional investors.

And we had a large piece of software,

which was basically we

wanted to have a single format

for loans, as effectively

all of the loans are the same.

There's a borrower, there's a lender,

there's an interest rate,

which is charged annually,

monthly, daily, or

whatever, and so on and so forth.

But then you had those providers

that would all have

outstandingly different APIs,

sometimes to achieve exactly the same.

And we had a very similar

approach to what you described.

We also initially

started with a result monad

in Scala back in the day,

and then testing became

harder and harder and harder,

to the point where we ended up

doing tagless finally in

Scala, which is

it's absurd in complexity.

It does achieve the goal

of having everything

modular and testable,

but it is highly complex.

And that's when I decided

let's migrate everything

to TypeScript because

well, our domain is IO bound.

There's no CPU compute

used at any point in time

except for data mapping, but even there,

like you said before

once you run your

JavaScript in enough servers

you're not really using

multi-threading to

handle a single request.

You just have to respond

to thousands of requests

at the same time, but two requests can go

to even two different

instances and so on and so forth.

Plus JavaScript is still very, very fast.

So I was like, let's move

everything to TypeScript.

And I was punched in the face

because none of the

infrastructure existed

that we were using in

Scala for TypeScript.

And that's why I built Effect in the first place.

So it was precisely to be in the

consolidation engine,

a testable consolidation engine

that had embedded open

telemetry

And this is what later became the open

source project Effect.

So I think it would be extremely good fit

for actually what you're trying to do

and funny enough exactly

the same that I was doing.

Which brings us to today

where my understanding is

that OpenRouter, the core

product, is not yet using Effect.

I think you're using Effect

already for some internal tools

and we can talk

about that separately,

but maybe addressing the

elephant in the room first.

You're here on the Effect podcast,

where the core product of OpenRouter is

not yet using Effect.

Yet you've dropped the

M-bomb before, the Monad bomb,

which typically is a

word that we're not trying

to use too much because

it's this interesting dynamic

of what is the role of

functional programming

in what we're trying

to achieve with Effect.

My perspective on it is

that we should follow the path

that React has taken,

where React is in essence

a functional programming framework,

but it does so in disguise,

where it doesn't overwhelm everyone

by being too dogmatic

about functional programming.

It's just an elegant system

that tricks you into becoming

a happy functional

programmer without knowing.

I would like to argue that

because I was there back in 2015, right?

When Angular and Ionic was

kind of a king at the time

because it's pushed by Google, right?

And everyone was writing a

directive and writing, you know,

some, basically it's very

much functional programming,

but it was a bloat.

It was bloating so

much that the key sell,

at least at the time I was still young,

I was still inexperienced,

but when I listened to

people talking about,

hey

if you adopt functional programming,

your program will be very, very lean.

And I think there was

that push of just whole ass

in functional

programming, don't be ashamed of it.

And literally say, fuck

object-oriented programming.

I think that was a push.

I would argue you should be,

you should not be ashamed

of functional programming.

And even if they push it,

I would say, I mean, sure,

then just go slow, you know,

then you have a slow code base.

I wouldn't say we're ashamed of it.

I think we're just

trying to like not make it

like an obstacle.

So I think we've so far

traversed the line pretty well

by like inviting

everyone who is FP positive,

but we've also not tried

to make it define the area

to the barrier to entry through

functional programming.

so there was that guy, I forgot his name,

one of the React engineers.

Dan Abramov?

Yeah, Dan Abramov, yes, that guy.

I think he has a good

sense of community there.

I'm curious what he would say about this.

Yeah, great point.

We should invite him to

the show and see whether.

But I would say that like the result,

I mean, the reason why we

reached for the result monad,

I mean, sure, most of my

previous project were try catch,

or like, you

don't even try catch by the way.

I just have a single error

handler at the top of my apps.

but then

whatever it try, right?

Whatever error is being

thrown in underneath, right?

Is an error type that

I have defined, right?

The problem is, down the line, right?

You soon learn that it's

very hard to debug those stuff,

right?

You soon learn that the stack tray

can be very, very

long and need to function

is like any anything complex.

It'll be very hard to go

through the stack trays

because you catching

it on the top, right?

So now you always want to

somehow manage your error

or localize your error

at the call site,

and then when you start doing that,

you have nested try catch and

it's a pain in the ass to read,

right?
Because every nested layer is,

I review my code on

my phone a lot.\

And yes,

almost every try catch

kind of nesting layer is like,

I just have to scroll on one side.

I just want to scroll on

one, you know, vertically.

The result monad

essentially solve this problem,

By explicitly forcing you

to handle the error case

as a value.

I think is a value add and it is weird

I mean, it is surely a different syntax.

I think people have to,

they just have to do it.

It's the same as when

React first came out, right?

With useState.

If you say useState is an

equivalent to the class state,

that's completely wrong.

Like class state is,

you cannot just check it in and out.

With useState of React,

you can just take it in

and out fully functional.

And then now your component is so light.

And that was, I would

argue the main selling point

at the time.

because of the pain, right?

The pain of, oh my God,

my class component is

so slow or so bloated

that with this thing

now, it will be so faster.

And it has to adopt

functional programming.

So I'm curious to hear a little bit more

of like when you grew

the engineering team

and like you as the founders,

you had the privilege

of like later foundations

or like set the taste as like,

hey, this is how we're doing things.

I think that's probably

still holds to some degree,

sure, like new people joining,

bring in like their opinions.

But I'm curious like which

sort of mini culture clashes

you've experienced by hiring someone new

and they look at this and it's like,

what is that?

How did you explain that?

And how did you sort of

like culturally assimilate them

to those ideas?

Well, I think we just have

to hire very good engineers

and good engineers with

experience usually can swallow.

Yep.

Oh, I mean, swallow not in a bad way,

but kind of like they have

seen things before, right?

It's not something crazy, right?

I would argue the crazy part

is we have like bash script there,

bash script with

different syntax of bash,

different flavor bash even, right?

And then we have

TypeScript, we have coffee

and then we have like,

maybe we use zest here and we did there.

That is the crazy part.

I would say that as we

grow the team, right?

We have tried our best to

if they tech debt, take

tech debt is widespread.

If they have to tech debt,

it widespread is unified.

So when we refactor, we refactor all of

them at the same time.

That's one key part of it.

If there is a certain framework we use,

we stick to it.

So like, for example,

right now we're using

a lot of our front-end,

our like React, right?

We know how to spend,

we are going to spin up a stencil,

you know, web component anytime soon.

The next one is probably will we react?

We'll stick to react for a while

until we decide, okay,

I think we, you know,

let's move everything, uproot everything

from react to stencil.

Sure, we'll have the

discussion, but not right now.

And then same with TypeScript.

The fact that we picked

TypeScript and forced everyone to,

hey, get used to it,

write script in TypeScript,

do everything in TypeScript

as much as possible, right?

That really forced us to

just keep on learning.

Even though they were

like from a Python shop

of GoShop or something else, when they

come in here, right?

I mean, sure, there's a resistant layer,

a resistant period where they're like,

how can I introduce a Go, you know,

the Go binary a little bit?

But then over time, right?

they start to get like used

to the TypeScript paradigm

because it's everywhere

in the code base, right?

You can have the AI agent look it up.

Also like Linter, we're using

biome right now for Linting,

plus Grid.

So we've been starting

adding a bunch of this Grid,

custom rules, which is very nice, right?

Because you can do like a

crazy regex on the code.

I'm using the same.

This is like, allows you

really nicely to define

some like, hey, this is

our way of doing things

from like all the 10 different,

TypeScript engineers

and they have 10 different

preferences of like braces there,

no braces there.

And then you can basically say,

this is the OpenRouter

way and run a command

and it gets linted and formatted.

Yeah, it is very awesome.

Like for example, we have a

rule that's strictly saying,

hey, if you have more

than three position argument,

just make an object.

Plus I feel like experienced engineers

usually focus on the problem

and the way we solve the problem,

rather than picking on the how,

in the sense of like

the thing you described,

that you use a result type for,

not gonna use result monad,

because monad is a term I don't wanna use

and not because I don't trust monad,

but because a monad would be an

abstraction on top of it,

which is the thing we're

trying to stay away from.

Result types are great

and they solve the problems you have.

They solve the nesting of try-catches

and so on and so forth.
So an experienced engineer that comes in

and sees the solution

might disagree on the syntax,

But the problem it's resolving,

it's clear they had it before,

they got tired of it

from other code bases

and they just digest

the current solution.

I wouldn't be opposed to

contribute to a code base

where there's a custom result type

and effect is not yet used.

I would say,

so that apply for like

a more senior engineer.

For the

slightly less senior engineer though,

I think the main thing is curiosity.

If they curious about why this was done,

they'll learn sure

learning faster on it too.

Because if you imagine you

can hire a very junior person,

but also very hard headed on some stuff

they've done before,

then they might not budge at all.

So I think for the less senior,

curiosity is where we strive for the most

and the way that we test

was curiosity is very simple.

They asked good question, really is.

So I think solving challenging problem

and then dig deep into it,

show that they have clear ownership

and clear digging, right?

The more they can dig, the better it is,

I think they show curiosity in a sense.

So taking a slight step back of like,

now you probably have a healthy mix

of relatively experienced engineers

and maybe some slightly more junior,

but very curious engineers.

And so you have right now,

a very sizable TypeScript code base.

Not sure, probably in the

six digits lines of code,

TypeScript repo, maybe even larger.

And you clearly have

Effect on your radar.

You've mentioned that some internal tools

are written in Effect,

but the main product is

not yet built with Effect.

And maybe you can just walk us through

and describe a little bit of like the

internal conversations

where you're considering,

hey, should we use Effect

for more parts of the core of the system?

Which sort of like

resistance questions you're getting,

how do people who are

already sold on Effect,

how they make a case for effect

and like just sharing your observations.

Well, the main observation

I have with the overall team

is almost every time

someone bringing a new technology

is a shiny object syndrome.

So very tough to sell, right?

in terms of handling errors,

we already have our own result monad

or result type that we already have,

which serve the purpose just fine.

and then we already have

this paradigm of writing,

make sure you write functions.

So when engineer, when

engineer look at Effect, right?

And the way it

orchestrating, you know, function, right?

A lot of time, the main question is,

why can't we use co-function then?

Why do we have to go

through Effect piping

or Effect matching when

a function work just fine

and an if and else work just fine too?

And if needed, we can

just make a new function,

do a switch cases with

complete check, right?

And plop it into the current,

functional flow, right?

And I mean, my response a lot to that is,

okay, what if you need to add a logger?

What if you need to add

a sidecar logger, right?

What if you need to add

observability or trace or span

on top of, you know,

this function call, right?

Well, the answer to a

lot of time would be,

well, I just add a new line, right?

Right before it.

Well, except that doesn't work for spans.

Precisely, right?

A lot of time, it doesn't work at all.

I think the main idea is

people don't really need

it at the moment

And so there's no huge push.

There's not, for example,

if we don't add Effect today,

we will still be around.

We will not be dying.

So that is why I would

say the main pushback,

because my team is

also very rational, right?

You know

they're also very busy, right?

it's not like,

it's spending time kind

of learning the namespace,

the Effect namespace, right?

Is already a drag.

Learning pipe is

already a drag, by the way.

So this is why the

first thing that I'm doing

is just introducing

small little usage of Effect.

We're using pipe almost everywhere now

when we composing a bunch of

function
in step by step, right?

And that helped a lot.

But the pipe function, by the way,

one of the main questions I

have for you guys actually

is a polymorphism of the pipe function.

A lot of engineers actually thought

that the pipe function is too flexible.

It's allowed you to

transform the type, right?

So it should have been called transform.

Transform pipe maybe, not pipe,

But to some engineers,

when we think about pipe

They assume it's

literally just a pipe through

where the type,

the input type should be

the same as the output type.

And it just

transformation step in between.

I'm curious what you guys

think about this, by the way.

What do you think about polymorphism?

Because essentially, the

this is the case where the

pipe type is not very scalable.

It only has 20 letters

You can only pipe 20 times.

I think there's reasons

why you don't wanna pipe

more than a number of elements.

And that's more along

the lines of limitations

in type inference

capabilities of TypeScript

and speed of type checking.

So we could easily add

200 arguments to pipe.

The actual implementation supports any

number of arguments.

The issue is TypeScript cannot deal

with the full polymorphism.

So you need to add specific overloads.

And before AI was very

tedious to add those overloads.

Now with Effect 4, I got Claude

to add the various pipe signatures.

I can easily tell Claude add another 200

so Louis can be happy.

that would be a solution.

But generally speaking about the

polymorphisms arguments,

pipe is generally applied to functions.

So you're piping the output of one

function to the input

of another function.

In that case, there's no

restriction about in and out.

You have data that flows

across a number of functions.

If it's understood in this

way, then it's not so confusing.

The confusion that I also had,

and at some point I was evaluating this,

is should the Effect

pipe be tied to Effect?

But then you have

cases where you might want

to convert an Effect to a stream.

You might want to repeat an

Effect

So you're having like slight

differences between the types.

So, I think putting an

arbitrary restriction

doesn't really help

in the understanding.

Now it is a good practice in your pipes

to not change the type

from the beginning to the end.

Otherwise while you

read, you lose context.

Like if you change the

type, most likely the best idea

would be to slice those

into two different pipes

and have variable

names that clearly state

what you're doing.

So this, I think it's

sort of best practices

and so on and so forth.

And I think with

touching on another point

that you mentioned, it's

the familiarity concept

and people's brain is wired into

imperative programming.

By imperative

programming, I mean the style.

Not the fact that

operations are actually executed

in an imperative sense.

I simply mean developers are used to

write normal functions.

Are used to write if statements.

They're used to write while.

They're used to write for.

They're used to write all of this.

With promises at the beginning,

we didn't get async await

And it was very tedious to write programs

because you had to think

in terms of this data flow,

this sequential nested data flow.

We escaped callback hell

to enter a venable hell

because then you had to access the

property from before,

then async await and

everything flattened out beautifully.

That you don't actually

get with a result type.

With a result type,

you almost end up writing

what they call Golang in TypeScript,

which is you call a

function then you have,

if you don't wanna end up in

pipes

what you end up doing

is you call a function

then you have if there was an error,

return the error, otherwise keep going.

If there was an error, well, in

JavaScript and in TypeScript

we have generators, which we rely heavily

in Effect to make code look

in the way that developers

are used to work with.

The mental model should be the same.

So my personal stance is that

pipe should not be the

default way we do things.

We should write code in a way that

everybody can understand.

And if you use a

generator, like switching mentally

from yield to await

it's not rocket science.

After you've read 10 generators,

you're gonna understand that you have a

direct mental mapping

that yield equals await, that's it.

And you apply your knowledge.

You can use it in a for loop,

you can use it in a while loop,

you can use it in an if condition.

You can do all that you want.

And if you want to access the specific

result of something,

you can say, okay, yield result of that.

And then you have

your if else that checks

for the exact

pattern that you would have

with a result type.

So I think like 90% of code

should go in that direction.

The rest of the code is

higher order behavior.

Like I have a

computation, I wanna retry it.

I don't wanna write a for loop.

I don't wanna deal with exponential

back off and everything.

Like you are integrating different APIs.

I think at some point

you have some retries.

At some point you have

to decide to back off

from the retries

because maybe open AI is down

and you all wanna keep

hitting the endpoint.

But those elements are

really not imperative.

They're not business

logic, they're not core.

They're just behaviors

that you add to the function.

And for that, I like to

do .pipe(Effect.retry).

That's it.

An LLM can understand it,

but a user doing

review can understand it.

And even a person

that doesn't know Effect

can read the function.

Okay, I understand the business logic

because it's all if else A equal B

and if these then that

while these conditions

through keep going.

And then they read pipe effect retry

while the error is network error.

Do not retry if the

response is malformed.

Cause if you call an API

and they change the response,

your decode logic will

keep failing anytime you retry.

So you might retry 500,000 times,

not getting anywhere.

So like to me, I

really wanna make it simple

for developers to read the code

and use pipe and use generators as tools

to really express business logic and

higher order behavior.

And I think like you are

in the very good direction

with the code base that you have.

And adding concepts one by one

is the right way to do it.

And from our private

discussions in the past,

I also see for example, you

use a lot of pattern matching.

Why the code becomes readable,

much more readable than

a sequence of if statements.

I would say that I actually consider

building everything using generator

but the code base started two years ago

and generator did not

work very well with async.

Async generator was on

a thing two years ago.

And you should not use async generators.

Yeah, you would not using it.

Now I would love to hear this by the way.

Why?

Why shouldn't you not

use async generators?

You should not have two

different representations

for sync code and async code.

That's madness.

That's actually also the

reason why I actually look up

by Effect by the way.

Oh, actually I look up what is it?

Function JS.

Because we had the result

and then we have async result.

Yes.

And you cannot escape from that.

So I think just to zoom out a little bit

and just to kind of like

observe the reflection here.

I think what makes your current code base

and your current programming styles

a little bit distinct from others

is that you went into this

with a very strong opinions.

Like, hey, we're not doing

try catch in this house.

And so you went like

this proper error handling.

And I think with that,

you're like way ahead

of where most TypeScript developers are.

most Effect adoption

I think happens to like

wherever the pain is most acute.

Maybe going to the doctor,

like you say like, oh my

gosh, this hurts so much.

But like while you're at the doctor,

you also realize like

you have those other things

that need treatment, but like one thing

gets you to the doctor.

And so the error handling thing,

like that pain like you've already

relieved yourself of that.

Yes, you have

appreciation for these other things.

Most people come to

Effect for error handling.

Also a lot of for other things,

but I think now

with the patterns

that you've like ended up with,

like just by talking through it.

Like I think we've like already uncovered

a couple of things where you say like,

well, yeah, actually that's not ideal,

but like, you know, it's like we have

good error handling.

But what Mike calls sort

of like the Golang style

of writing TypeScript.

I have done that myself as well.

And like this works

until it no longer works

or like until it just gets very annoying.

And I think the, what will

probably be the pain point,

like group of pain points

that gets you to the doctor

might be like better

retrying, might be observability,

might be like what

you call it like logger,

maybe metrics, et cetera.

And I think this will

make it actually easier

for you to adopt Effect

because you don't need

to rewire your brain.

So a lot of time we have been

incrementally adopting

some Effect, right?

Cause you know, lodash is a bloat

And I already added, I

already use my authority

to add Effect to the code base, right?

As I'm saying, we added Effect.

You're still

retaining your CTO authority.

Yeah, by the way, title wise, I'm not,

I don't claim CTO.

I'm just a co-founder

engineer title wise.

Yeah.

Perhaps there's a

better CTO out there for us.

We will see.

But I don't care too much about title,

but I care more about like

the dictator title for sure.

I'm a dictator for like BDFL for sure.

Yeah.

I can push the man by the way.

So like, for example,

You don't call a CTO, but

you get called a dictator.

Yeah, I'm a DFL.

And then, I mean, I was

saying like last week,

last week for the OpenAI dev day,

they were launching

the eval product, right?

And then right the Monday before,

they were paying a slack and saying,

hey, how come the

responding API don't have cost?

I just came right on and just, you know,

like push a thing on

man, two man in 30 minutes.

Shut up, right?

Get it over with, you know, like,

and that's the kind of thing I think is,

as a co-founder you

kind of have to do, right?

Is you gotta hold it by, you know,

you gotta hold it by

your throat and shove it.

Yeah, you have to know

when to skip the steps

and you have to know when

to impose a long-term vision

that is not yet apparent.

And sometimes you're wrong, like,

I do the same with Effect.

Sometimes I'm wrong.

I think I'm wrong and now, by the way,

there's one thing that

I might have been wrong,

which is like, I'm too ho-ass

into this idea of not using try-catch,

but then when it

comes to like the logging

or the tracing server,

you kind of have to use try-catch

so you don't allocate a new variable.

Because that way it's very easy to make a

copy of the variable

or when you do JSON parsing,

you make a copy of the variable

of a very deep nested

object of every value,

out of memory very easily.

Exactly.

And I think this is the thing where like

when people are sort of like in the,

the denial phase of Effect

and sort of like they, they

like justify to themselves,

like, we don't need Effect,

like we can do this thing like that.

Okay, yes, you can do

that thing like that,

like you do for error handling,

but like what if you need the,

if you wanna do the other thing,

if you wanna add that as well.

And like then, like all of those things,

they kind of like as you introduce them

on top of each other,

they don't seem to like

linearly add complexity,

but that's, I guess

like one of the unfortunate

emergent situations about TypeScript

is like the more you

add those requirements,

like observability,

better error handling,

dependency

management, like context management,

that doesn't make your code

like linearly more complex,

but exponentially more complex,

also visually more like visually,

exponentially more unwieldy.

And I think this is

where Effect comes in.

We've been adopting effect

in a bunch of like our retry

and like backup handling logic as well,

because the API elegant, right?

That's the whole idea.

So why would you write your own

when you can just pop this thing in,

throw a fetch and it's done, right?

And to review,

I don't think anyone

should adopt Effect in one day,

like in any reasonably large code base,

it would be absurd to

think one would rewrite

the whole code base.

we call effect a good virus,

in the sense that it tends

to spread when you started,

because now you have this retry,

but then you call a lot of APIs.

Obviously you are an API

consolidation provider.

So testing is essential.

You have to do integration testing,

you have to do end-to-end testing,

you might wanna do some unit testing.

And then you are in a

madness of Jest mocks

or Mocha mocks or whatever framework

you're gonna use Vitest mocks.

But mocks are really a very

complex thing to deal with,

because all your code is eager.

If you call it in the wrong place,

you have one piece

that is using one module,

another piece that is

using another module.

And so you might wanna elect,

use dependency injection.

I have seen how Epic Fabric

and their dependency

injection would help with that a lot.

Yeah.

Very good to use it, but you know,

I mean the team has

been using Vitest mock,

do mock and stuff for a long time.

By the way, we switched from Jest to Vite.

And that was a feat.

I know, I mean it's faster,

it's like the test runs

10 times faster, right?

We switched from prettier to biome.

I'm saying

we are not foreign to adopting new thing.

But it truly is all because,

it's pushed come to shove, right?

For example, if the test

take about five minutes to run,

oh my God, okay, let's

try to shave this down

and stuff like that.

Yeah, and to be frank,

I think like for your specific use case,

I think one of the

highest leverage points

could actually be the observability.

Because you already

referenced to debugging troubles

in the past of those,

once you have a hundred

of different functions,

the stack traces becomes unreadable.

Yes, they are.

They're much less unreadable

if you have spans at the right places

and you have a full dashboard

that tells you if something goes wrong.

The one thing with

observability and OpenRouter

is that because we run

the thing on the edge

and the worker runtime,

we don't truly have a span server

to continuously collecting the span.

So right now we're actually hooking

into the Node.js diagnostic channel,

which is not very good, I would say.

At least on the worker runtime

because the memory is very limited.

So we have to be very

careful with what we trace.

Well, I wish at some point,

we're gonna get to agentic behavior,

long-running processes

at some point in time

in this discussion.

Yeah, so are you familiar with the ECS or

the entity component

system in game design?

Yeah, I think the reason the actor model

in general is very popular in game design

is because in games you usually have a

lot of parallel entities

and they do share messages with each

other. It's a very

interactive world. Basically, again, it's

a world with entities that both some

entities can be movable, some entities

can be movable, some speak, some don't.

But even if you have something like a

room with a door, you have a character

that hits the door, the door is open and

All of those are sort

of unstructured. It's not very

deterministic. The player can take

different ways and so on and so forth.

And the actor model is perfect for that

case. And if you think about it, the

reason the actor model was even invented,

as far as my knowledge goes, we go back

to Erlang and we go back to the 70s when

Ericsson had to model telecom networks

where, again, you have very

undeterministic and unrelated entities

that communicate with each other.

I can phone Louis, New York, I can phone

Johannes in Germany, they can phone each

other. You have race conditions when two

people try to call the same

It's very similar to what you

would get in a game.

And in my opinion, it's very similar to

what an agentic system

looks like to a very high degree.

Have you thought about

agentic system that's steerable?

So when I was doing game design,

we do a lot of steer algorithm.

And steer algorithm is

all about managing a crown

Let's say you want to model

a particle system, right?

You have the particle

system, it's actually,

and each of them could

be a bird, by the way.

The particle system can be represented

by a bunch of bird

actors within this cap.

It's kind of a point

cloud of a bunch of birds.

And now let's say you

swarm this bird, right?

You swarm this bird through

an obstacle in the middle.

Now they all have to

independently dodge that ball

and fly around that thing, right?

Because the whole idea is when

you steer this flock of bird,

you just want to pick a pointer

and you move that

pointer to a certain location

on the 3D map and then the flock of birds

will just keep on following.

They keep on

following that point in space.

They both try to steer

away from each other,

but they all kind of act in tandem to

move to your pointer

and then they dodge popsicle on the way.

How do you think about steering agent

or in that same vein?

Have you thought about that?

To be frank, I have not.

I can apply some of my

knowledge from mathematics

in the past,

especially studying how birds

actually end up doing that.

And it's all about local communication

between one bird and the other.

There's no overall concept.

Basically the steering is almost like

one tries to steer a little bit,

the other follows the one

close and so on and so forth.

And I think even in that case,

it's all about the connectivity

between sorrounding entities

the neighbor flock

Each of the entity

they should have knowledge of

the entity right close to it.

So you put a small little boating block.

on each of them

so more like small clusters of

entities that sort of have

deep communication between themselves,

but almost not communicate

in the far distance one.

And also to quickly throw in here

to answer your initial question,

I've asked you to

chat GPT and asked how,

chat GPT how ECS

compares to the actor model.

And it says that they

overlap conceptually.

And the TLDR is that

ECS is about what you have

in terms of data and what operates on it,

like which systems optimized for

throughput and composition.

And I think composition here

is really interesting, right?

In a game you compose like a big scene

of like smaller things.

And the actor is about who does what

optimized for isolation concurrency.

So I think we can basically

build an overall system here

with both and they're

just like have sort of like

different aspects, like

what they're concerned about.

Like composition is interesting.

And the other one about concurrency,

like ideally you have both,

but both needs like this

different kind of concerns.

So I just wanted to weave that in

as I think it fits really nicely

the sort of like ball flock of birds

kind of scene we've been describing.

The kind of difference is more

at the level of the API they expose

rather than at the level of the runtime.

Because they're still both actors.

It's just that the API

focus is more on one aspect

or the other.

For example, even in

actor systems like Akka

you have different ways

of creating actors.

And even in a fat cluster,

which is our actor solution,

you do have different

ways of creating actors.

You can focus on the behavior,

which would be more along

lines of data in data out.

The kind of messages that the actor

can respond to, can send to.

Or you basically get a fiber with a cue

that you pull on and

everything you do is custom.

So you have more

flexibility, less structured API.

I think like the idea of a

distributed actor system

is very, very essential to

distributed systems in general.

And my point is really that

agentic behavior in

applications is a distributed system.

And it's just so happens that nowadays

everybody builds AI applications.

So they hit

the distributed system problems day one.

rather than facing them

further down the path.

And it's also why companies like Temporal

is getting renewed traction.

Because before you'd only use Temporal,

if you actually have a lot of customers

and what used to be an edge case,

like I always make the example of an

onboarding procedure

where you put in

username, you put in password,

you put in email, the

system has to record those

in a database and it

has to send an email.

Like those operations

happen within 100 milliseconds

of each other.

The probability of your server failing

between the two operations near zero

if you have queued two users that are

registering in a day.

But if you have 200 users that

registering the same seconds,

it's almost guaranteed that in a month

you will get multiple

instances of the issue

of the server failing in

between the two operations.

So that's when you usually

would reach for solutions

like Temporal or like Effect

Cluster.

The difference with AI is that the

average response time

is much longer than used to be.

Now we call an Open AI,

API to OpenRouter and it goes through

two different layers of proxying,

but it has to stay

alive for like 30 seconds,

the same connection.

The model can reason

for more than 10 minutes.

A lot of time, deep reasoning

can go for a long, long time.

And a lot of time during

these deep reasoning steps,

the model might not

produce anything at all.

So, OpenRouter, we actually,

a lot of time we set

to standing this,

the comment, like it's

actually in the SSE spec

is that you have to send

back comment to keep alive.

The way they see the connection alive,

otherwise either

the client would not know

if we're still alive or

The DNS server, the DNS layer would

actually cut us off.

And even any proxy that is in between,

because even when you call open AI,

you're not just calling

straight up to the server,

you have layers and layers

of content delivery networks

And each would hit HTTP timeouts

So like either you do web socket stuff

where the ping is in the protocol itself,

or you have to go on top and do a ping.

But the point is here, the average length

of an interruption with a service

went from being

milliseconds to being minutes,

if not days when the

other end-to-end reasoning

could be that we have still a pattern

where there's humans

in the loop of agents.

So sometimes an agent has to

stop and call another agent,

which is called a human that has access

to human intelligence

that is less artificial,

but they work exactly in the same way.

So humans are entities that share

knowledge via messages.

Currently we have three

entities in this call,

one entity is in New York,

one entity is in Italy, the

other entity is in Germany.

Yeah, absolutely right.
And we all communicate

with, we're sharing messages.

You know what's funny though?

I just saw the

premiere of the Tron movie.

And in the Tron movie,

so Tron has a bunch of program,

which is all humanized.

The program in Tron

communicate with the human outside

through MCP.

It's a fun detail in that movie.

I was like, "Ooh, cheeky."

I wanna play back the

same question to you.

So you asked Mike how he thinks about

how multiple AI systems could be steered

through this sort of

flock of birds, ball analogy.

How are you thinking about this?

Like just in terms of my own experiences

about a steerable AI system versus a non

steerable AI system,

actually two coding

agents I use on databases,

Codex and Claude Code.

Well, as of October 13th, 2025,

maybe a new thing drops

tomorrow and I'll switch.

But as of today, I am using Claude Code,

which can be steered and

I make a lot of use of it.

And therefore I'd use it

differently than Codex,

where it's much more like binary,

where I feel like if it's off track,

I like interrupt it and

say, "No, no, no, no, no,

please do this."

And I guess it is sort of

like similarly steering it,

but it feels much more like

stop and go and more forceful.

So I'm curious like

for you to extrapolate

where this is going.

And we're talking

about multiple AI systems.

So how are you thinking about this?

Yeah, one of my main pain points,

we're using Claude code or even like,

you know, Cursor or so on

Is that you set a goal or

you say, "Hey, fix this thing."

The problem is, right?

That's the goal already.

The flock could just find the goal.

I shouldn't have to like say,

"Oh, there's an

obstacle, there's an obstacle."

The obstacle is in

the environment itself.

go back to my college day

when I was listening to this system,

an autonomous steering system,

self-driving is the same idea, right?

You need sensor.

You essentially need a

way to emitting a bunch of

either event, which if we're using ROS,

it will be very simple,

ROS for barring over system.

It emits an event system,

when you're just sending into the system

a bunch of different light sensor

to tell you about the environment.

So first I think

every actor in the system

kind of have to emitting

some kind of sensory system

that tell the whole system,

"Hey, here's the

state of the environment."

And secondly, I think

that through those sensor,

you pick up some kind of weight.

allowing the system to

like course correct.

Automatically, instead of

having the end user have to like,

"Okay, we hit a roadblock now,

the type is not checking here.

What do you remove,

let the satisfy?"

So humans still have to right

now steer somewhat manually.

I would love for the end goal system is

I put a pointer in

the space in the world,

meaning a goal, right?

To say, "Hey, fix this unit test."

The swarm of bot

should just be able to say,

"Okay, I'm going to

use the browser agent,

which is like top tier browser agent.

Let's read the documentation

for the test, for example."

And then, "Okay, I know that you guys,

I'm using the test with the set timeout,

the set timeout set too low."

That's one thing that

it might see in the test,

or in the output.

And then from the dock of

the Vitest agent, right?

It will send back to my agent,

and my agent will then be like,

"Come on, okay, the set timeout too low.

So let's change that."

Just keep on doing that

loop over and over again

with our other agent,

or it figure out like what sensor it

needs from the world,

from the system to

steer its action as well.

And I think so far,

I mean, Claude Code is

occasionally doing recently well,

I would say.

It has a browser inside,

and like the chain of thought

that it has to like

solving a certain problem,

seems pretty decent so far.

here's like what I'm

seeing of the whole system,

like here's the data of the system

that can help me steer myself.

And I think what's

also interesting is like

that the entire ceremony is

like all sequential, right?

And so that therefore it makes it slower.

And so I think where

I'd like to see this go,

and I'm sure it will

go in this direction,

is that it's more of like an orchestra

performing a symphony.

And right now it's just

like solo, solo, solo, solo.

And while you're

encountering a problem,

like that might be a signal,

I was like, you know what, like,

let me go off like a little

bird just goes to the side

and like studies the docs more deeply

about like a system.

So it's like, hey, I

found something interesting,

like we're on the wrong track.

And like you could imagine multiple

of those like things happening.

And right now it's like

everything's just like happening

sequentially.

And yeah, I think this is very exciting

to see how this is unfolding.

And I like your guys steering analogy

and to look at it through like the ECS

and an extra system lens.

I have one observation

that I recently learned myself.

to take a step back,

I've been very late to the

AI coding game in general.

Johannes has pushed me for a while.

I developed the ability to ignore

Johannes for enough time,

but at some point sort of

penetrates the protection

when you're more

vulnerable and you're like,

okay, then I have to try.

And since Claude Sonnet I

think 4 or 3.5 came out,

I've been heavily invested in AI coding

and I really wanna make it work.

And what I've realized is

that I think we're putting tools

either on the wrong place

or we're not putting

tools in enough places

in the sense that

currently we rely on the models

to maintain some sort of loop.

But the models are

terrible to do looping.

They can't even properly assign tasks

They are exceptionally good

when called with a specific

prompt and a specific task,

then they're done.

The task might not be

done, but the LLM is done.

So what I found extremely

helpful in approaching tasks,

this is not really like AI coding

in the sense of the AI

assisting you while you code.

It's rather a task like, okay,

I give you the Effect repository,

create JS docs for every

single function in the repository.

Go, use 1000 parallel

agents to achieve the task.

I don't care.

Ready to spend 10, 20K

to do it, just do it.

we are approaching

the point where I think

there are ways to make it work

and even with normal like Claude plans

with $200 per month,

I'm completely fine

running multiple agents

and so on and so forth.

But what I've realized works best

sort of goes in the

direction of steering.

Instead of letting, for

example, the model calls tests,

calls TypeScript to get type

errors and so on and so forth,

instead of having the model do that

and assigning the model the

responsibility to do that,

I have those in a deterministic loop.

And if I have a type

error, I call the model again,

I have a system which is LLM based

that scores a specific example.

And if that is not happy, I

call the first model again,

saying, look, my Oracle is

not happy for those reasons.

I call it a deterministic outside loop

that ends up providing those events

and this feedback

inside the single agents

that are steering them

towards the right goal.

And that works exceptionally well.

Like well to the point

where you can leave it going

for a day, come back, you

have 95% of the stuff done.

And now that multiple

times, because I wanna improve,

I'm testing these on Effect 4,

because I don't wanna

break everything in Effect 3.

And to the point where the

quality is going up and up

and the automation is

also going up and up.

And I'm almost there

where it's almost better

than me writing example manually,

if I let it go long enough.

I have never seen that

with a normal coding agent

where you don't have this external loop

that steers it in the right direction.

So I think I didn't

understand the answer at first,

but actually I'm doing stiffing myself

now that I understand it better.

So I think this sort of setup

and system you've described

is becoming more and more mainstream.

And I think there's

multiple ways to achieve it.

Either it's already the de facto behavior

of like some coding agents,

or also this is where

those like coding agent SDKs

come into play.

Like I think Claude Code was

probably one of the earliest

ones that provided that.

Now, Codex has that AMP,

I think also just release theirs,

where you can like build

your own coding agent

and like express the sort of like system

and workflow that you want.

Converting a big repository

from having not no JS

doc to having JS doc,

which can be massively run in parallel.

That's a great candidate,

but doing that is very

different from building

whatever like a single new module

that you maybe have no

idea what the exact goal is.

And it's like, it's

much more exploratory.

Yeah, there's much more human in the loop

in the other direction.

Exactly.

So yeah, Louis, I'm curious

like how much you're already

using those sort of like patterns as

you're building systems

and maybe your thinking is more refined

in advance on this.

Not that refined yet, by the way.

We use a lot of AI model, AI agent.

So we use the Devon a

lot, you know workload.

And I think right now

the best way to steer Devon

is to have a lot of knowledge.

The knowledge truly helps steering it.

And Devon is selectively like it's select

the right knowledge per task as well.

So that actually easily have a lot.

I mean, this apply to

also, you know what,

I think Cursor and CLI

and all the other coding agent as well.

The more you can add into the memory,

the better they adapt to your code base.

in terms of having an orchestration layer

to like either use

multiple of them at the same time

or like picking the right

one for the right job though.

We still haven't like

think too much about it yet.

Yeah, right now it's all

just my workflow.

Maybe a related but

different question about this.

Given how Mike has described his

coming to terms with this new future

and he at his own pace

started like discovering

and like exploring, experimenting,

see what works well for him.

And I think that

fundamentally changed Michael's workflow.

I had the same process

and I think everyone does.

But I think what is

maybe interesting to explore

is how you as OpenRouter as a company

try to create like a higher

level process around that.

So for example

maybe a stupid way to go

about is say, hey, it's

the first of the month.

And we're again spending

three days of like going

from scratch,

exploring the best practices,

what it means this month

to do agentic coding.

Or maybe you have

like a team that is known

for being super cutting edge

in every first of the month.

They do like a presentation of sharing

their best practices.

Or you basically say,

we're not trying to orchestrate anything.

We just let emergence happen.

So I'm curious how much

hands on or hands off you are

of like fostering, like

embracing the productivity

and efficiency benefits

you get from agentic coding.

I would definitely

encouraging using agents

in almost every aspect of the code base

and also in the business as well.

I think the encouragement

is like the key part, right?

asking our team

to use it as much as possible to see,

essentially the idea is to

for them to learn the best way

to use it, right, to leverage these tool.

But the one thing I found though,

oh, okay, with everything,

right, there's a drawback.

The drawback is reviewing the code.

The key sin is when someone, you know,

they rely too much on

the bot and they say,

hey, do this and then they

just send it off for someone

else to review without

them reviewing themselves.

That's like a huge sin

that one day I will eradicate

it somehow

we have a way for

you to eradicate that scene.

Oh, awesome, I love it.

It's called Effect

Well, I'm telling you, so occasionally

the bot is very smart.

The bot, the agent is

smart actually, I don't know,

like a lot of time it

will actually change

either the TS config or it

will actually add an ignore

to the test setup or something like that.

Or sometimes it mark the whole module.

So the test looked like it

actually testing something

but it looked, you

read deeper into the test,

it literally marked the

module you're supposed to test.

Holy shit, it did.

So even, let's say, even

if we use Effect, right?

If the bot mark Effect, what can we do?

Just to take this

slightly more dystopian,

like maybe if you want to turn this

into a Black Mirror

episode, you could imagine

sort of like the AI

modeling like a mock human

where like the

instructions always you need

to get permission from the human.

So it models, it's mock

human that always says,

yes, go for it.

I have

experienced those problems.

Of course, everybody doing

AI coding has sometimes the,

yeah, I made the test fast.

You check and you added a deed dot skip.

What are you doing?

But I feel like most of the time

when the AI agent is doing that,

it's because it

really has no understanding

of the structure of the code.

And in my experience that often comes

from the agent

working toward a low level.

So it almost focuses too much on the

implementation details

versus what it has to do.

And that's why for higher level,

for example, application development

as opposed to library development,

like the AI is doing very poorly

to implement the internals of Effect.

It's doing exceptionally well to

implement the tests,

the examples

on the effect repository.

The reason is that

Effect almost produces code

that is closer to the human

versus being closer to the machine.

And LLM's being trained

on tons of human language.

They can sort of

reason better in human terms

versus that in code terms.

Like if you ask

Chat GPT a human type question,

very rarely it ditches the question,

there's not TS ignore at the human level

in a sense because the

language is higher level,

it's richer and expressing concepts

in a richer language is much easier

than boiling down to the very

details of the implementation.

And that's where I see the key shift,

the key difference when I build

application level code

and it uses Effect,

almost never I have a problem

like the AI ditched the task.

It's perfect to create schemas.

You have like, okay, I had these API

returned the structure like this one,

please create a service for me

that interacts with this API.

And it's fits down the full service

which almost always is,

I don't like the term one-shotted

because we're really not

here to one-shot things,

but close to

one-shoting the correct solution.

And I've seen that happen over a lot of

different code bases.

And it's where I think like

Effect can play a big role,

especially linking to

one of your concerns

slash questions that you raised

before we even started recording.

And I wish you will

use a different solution

that we are building

and where you will not

be memory constraints.

You will have spans

collected out of the box

and so on and so forth.

That'll be very interesting.

And then, well, I mean,

and you guys can

handle like caching, right?

So caching is another layer that's like,

caching at the DNS

layer would be interesting.

also no egress cost

if you can figure that out.

That part is hard.

Yeah, no, no, that is figured.

We don't have any egress cost

because we're building on bare metal.

So we don't pay for bandwidth.

There's no reason why to

charge people for bandwidth.

There's no reason why

to overcharge people

for CPU prices or go

5X what AWS gives you.

The only hard part, to be honest,

that we had to deal with is networking.

But networking in the

sense of private networking.

We got it figured,

kind of EPC layer stuff.

I had to write tons of

eBPF code in the kernel

to be able to figure that out properly.

But I think we're getting there.

And I hope that we're

gonna be able to provide you

with the right

primitives to deploy a platform

such as OpenRouter, which

is really what we're trying

to solve with Effectful.

Because with Effect, I think we solve

the production grade coding.

But at some point, code ends,

and you have to

deploy your code somewhere.

And especially with AI,

I feel we are hitting.

The real troubles were

if you were deploying

distributed systems in the past.

But you would only do

that if you were a scale up

handling millions and

millions of customers

Otherwise, a small

server, just a database,

would get you out of the troubles.

And the edge cases would not materialize.

Because the average request time was,

like what, 100, 200 milliseconds.

Now with AI and flows like

the ones you have in OpenRouter,

my guess is that

sometimes you have API calls

that takes minutes.

with minutes of

time, be sure that something

can happen much more

frequently than it can happen

in 100 milliseconds.

And so long-running processes

and less server-less pieces,

I think will be much more important.

And let me ask you a question.

How important is latency in a request

that takes five minutes to answer?

Not a lot, right?

Not a lot.

Maybe the handshake so that the client

is not easily, some

of them are very eager

to kill the request, but otherwise, yeah.

People don't really care.

Sure, but you might be,

like the important part

what might be to back off from a request

much faster to not spend money

versus actually letting the process run.

So I think for these kind of use cases,

and I've been discussing

this with some other people,

it's much better to have

reliable infrastructure,

maybe deployed

slightly outside of the edge,

so it's not close to your home,

but it's cheap, doesn't

have network costs whatsoever,

has very cheap compute

costs, very cheap storage costs,

and you can actually run a server

not being constrained by memory

because I feel like

being able to collect traces

is much more important for the business

than saving, what, 20 megabytes of RAM?

like RAM is cheap, or 20

milliseconds of request time?

or maybe like

100 milliseconds, yeah,

which is doesn't really do much.

By the way, the thing

that you might think

is just internal tooling,

but we have a lot of

internal tooling, just so you know.

So, which is why Effect is

actually being used a lot.

because we are

TypeScript everything, right?

So, instead of running a Python eval

suite on the market,

there's a bunch, we write our own, but it's

using Effect, right?

So, we actually have an eval suite

and also a bulk test suite,

that's all using Effect with Effect retry

and so on and so forth.

Yeah, so it's a very

sophisticated system.

I'm just saying is that if we use,

you might think internal

tooling is like some toy,

but no, we're actually using

it almost on a daily basis,

it's actually production load.

Reason why we don't use it in the core,

because while the core is working,

don't touch it.

But we do have a lot of internal tooling

to do testing out all

the endpoint, right?

Which is a crucial part of our operation.

We have, well, I

mean, almost every respond

to transformation is using the pipe now.

And then also I'm

introducing more and more generator.

So, down the line, we finally will,

it will be making it

easier for us to transitioning

over to Effect as needed.

by the way, to the point,

I'm actually still using async generator.

And the main reason why

I'm using async generator,

because inside this generator,

we have some async

process, which unavoidable.

No, no, my prior

comment on you should not use

async generators is

because you should not have

two representations of code.

But if you're using

promises with result types,

100%, please use async generators

so that your developers

don't have to unwrap

every error manually at every point.

But it's still a much

better user experience.

I just meant that if you

have one single representation

of code, you don't need

to use different types

of generators

Plus, adding like one

of the key troubles I had

in the past was, okay, I

have a very synchronous

code transformation, like these takes

an API, does this

this, this, this, that, okay.

Now I wanna emit a Kafka event.

Not now, not at the

beginning, but a month later,

I would write an event in Kafka.

Look at what's the writing to Kafka

is an asynchronous operation.

So I stitch it at the

end, and I have to bubble up.

Everything becomes async.

That's a problem.

Now it's still a much

better problem to have

versus writing callbacks.

Because callbacks don't

have function coloring.

Callbacks are fine for sync and async.

They are a unified

representation of computations.

It's continuation passing

style that we ended up calling

callback passing style in JavaScript

because we just like to

invent new terminology

so that when somebody looks at history,

they don't see the

theoretical papers from the 70s.

Otherwise they understand

we're just stealing ideas

from the 70s and making

them production ready.

But there was a unified

representation of code,

which leads to, boom,

boom, boom, boom, boom,
of callbacks.

Now with promises and async

await, you no longer have that.

But you do have the two different

representations of code.

And that's much better

than reading the code

like you described before.

You want to review on the phone.

There's no way you're going

to review 100 nested callbacks

with your phone screen.

By the way, no way

you're going to read that

in a normal computer screen.

You might need Johanness' set up

with exceptionally large

screens and so on and so forth.

But even there, I

think you should optimize

for a different problem.

So I think the final solution, which

is I believe what we have with Effect

is a unified representation that allows

for sequential representation.

But still, the intermediate point

of having two different ways of doing

things that all look

good is perfectly viable.

And especially in your

code base where you're

using a custom result

type, all the time go for that.

It's amazing.

Yeah, this is the thing

that I was looking for,

which is the way to represent in both

async and synchronous

process

in the same container, essentially.

that's something I'm excited

about most, about Effect, right?

It blurred the line between async and sync.

And you can now

orchestrate them in the same flow.

So it seems like you're well on your way

to introduce Effect more and more

throughout the entire system.

And it's very interesting

to hear how you've basically

introduced mini versions and

little pieces of Effect

already to make it

more and more swallowable

by the rest of the engineering culture.

So I feel there's plenty of stuff

here that we need to unpack

probably in another future

episode to hear how your

Effect journey is continuing.

Before we're wrapping up, though,

we talked before about that as part of

your onboarding culture

to ask a new employee

what's the latest rabbit hole

they went down on.

So what's been your latest rabbit hole

that you barely emerged again from?

The most recent one-- I recently moved to

New York, by the way,

two weeks ago.

Sorry, two weeks ago was two weeks ago.

So that was a month ago.

Oh, my god.

And that led me to hold rabbit hole

buying stuff for my

apartment, essentially.

Well, there are two rabbit holes.

One is on buying a bed,

and one is on apartment

finding in New York.

Luckily, I didn't--

I was on a time crunch to find an

apartment in New York.

So that

hole was a bit shallow.

But the one about finding a bed, though,

a good bed for your

back, is it was very deep.

And thankfully-- so I'm

working off the Marlin.

It is a very bougie cooking

space down in Flat Island,

Manhattan.

And right next to this place, there are

three mattress stores.

Like right next to me is the Sattva,

which is one of these

very high-quality hotel--

mattress.

Down the block, there

is a mattress firm store,

which contains a bunch of

variety of mattress store.

And then on the other block is Avocado,

which is this another

very bougie, very expensive

mattress.

And I got to go in and try

all of them, spend about a day.

And it is fascinating.

The final choice I made

was to get a purple mattress.

This is not a purple

mattress app, though.

I just tried all of them.

So the best-- I'm

going to say the best thing

that you can do when buying a mattress,

you just try them out

yourself, tug it in, go on all side.

You've got to try all side

of your sleeping position.

Yeah, I roll it all over

and try to tug it in to see

if your spine can get enough support.

And of all of them so

far, for me personally,

the purple mattress has been the one

that I can actually support my spine.

The second option was the

Sattva, the Sattva Classic,

which is somewhat soft, but

also decently firm in the bottom.

And yeah, the purple

mattress is kind of interesting.

It's not one of those

put traditional bad.

He's going to send

over the affiliate links,

and we're going to put

them in the description.

It's going to be a podcast first.

There was no affiliate, but

thanks to a very good friend

of mine.
He was my co-founder of

my previous company, too.

He went mattress

shopping together with me

And then we did the good

cop, bad cop negotiation

at the mattress firm.

And we get $1,000

discount on that mattress,

which is kind of crazy.

$1,000?

$1,000.

$1,000.
A $1,000 discount on a mattress?

Yes, on a mattress.

That kind of suggests

that the mattress was

more than $1,000.

Yeah, I guess.

Yes, the mattress was very--

I mean, I talked to a friend of mine,

and he told me, you

spend a lot on your back.

You're going to need it.

So yeah, I think it was good spending.

Yeah, very comparable bet, for sure.

Unrolling the bed,

though, was insane, though.

It's very hard.

So recommend, if you're buying a bed,

just have two people.

Just have a friend.

Fair enough.

Otherwise, you'll ruin your back while

setting up the mattress.

That's no good.

Awesome.

Hey, Louis, this was an absolute treat

for us having you on.

for us having you on.
I'm looking forward to

having you on in the future again

and hearing more about how

OpenRouter is adopting Effect

and all the crazy

growth you're going through.

Also, a big thank you

for Mike for spoiling us

with his presence

throughout this episode, as well.

Very, very interesting conversations.

I've learned a lot.

So thanks a lot to both of you.

Awesome.

Thanks, Mike, for

putting me in on the podcast.

It was a big pleasure.

And I hope to see you soon

when we come to New York.

Absolutely.

I'll see you then.

All righty.

Have fun.

Take care.

Thank you for listening to the

Cause & Effect Podcast.

If you've enjoyed this episode, please

subscribe, leave a review

and share it with your friends.

If you haven't done so already, you can

join our Discord community.

And if you have any questions, feedback

or suggestions about this episode or

about Effect in general,

don't hesitate to get in touch.

See you in the next episode.