The Actor Model, Behind the Scenes
Watch on YouTube (JSConf)
What is this talk about?
Let's set the stage. You have a complicated application with many parts that need to work together, and you want to avoid making your code an unscripted mess. Enter the Actor Model, a very old concept that is relevant now more than ever. In this talk, you'll learn how the actor model orchestrates tasks, ensuring each actor knows its part, communicates effectively, and remains isolated from potenti (From the video description.)
“Based on messages sent between other actors, and this really set the foundation for, just the concept of an actor model.”
“The actor model seems a little bit simpler than state machines.”
From the opening of the talk.
Key takeaways
- Based on messages sent between other actors, and this really set the foundation for, just the concept of an actor model. 2:00
- Is Orleans and Xat, which I'm going to talk about, actually implements the actor model as well. 12:00
- At least in our version of the actor model, and so with promises, it's essentially you're initializing the actor the actor is. 18:00
- In an even more exciting example, we're also using the actor model and hent architecture to have the agent act as an intelligent assistance, for the user on an application that's already using state machines and XState. 28:00
- Overall, the actor model, at least in my view, is an intuitive way to model complex systems. 31:00
Quoted from the transcript.
Full transcript
From YouTube auto-captions. Punctuation and capitals were restored automatically. Technical terms were normalized; some errors remain. 5,385 words.
0:00 Introduction
Testing, testing, testing, one, two, three, all right. Thank you so much. Stages for the intro. I feel like I just want an Emmy or an Oscar. Like to thank the academy. I'm an actor, you're all actors. We're going to be talking about the actor model, and we're going to be going behind the scenes on just exactly what the actor model is. So, first of all, let me tell you about myself. My name is David Khourshid, not David. KP. Piano, piano is. I'm. I'm not Italian, but I just like playing the piano. Like T just said, I am the founder of a company called Stat. where we work on visual software modeling. And also, I love coffee, and I love coffee. Yeah, okay, lots of coffee lovers here, so I love coffee, especially more now because I don't have to drink. From one of these things. Like, all right, I, I. I don't understand why this is a thing, but maybe some of you can explain it to me later.
All right, let's set the scene. When we're dealing with complex logic, as you heard about in the last talk. Sometimes it can be very hard to understand just what's happening in your app. You have some things changing, other things which might affect, you know, other things down the line, and you have a bunch of used effects. Calls chained together. Really, really bad situation. You end up with basically a Rub Goldberg machine, which is not my favorite type of machine. We'll get into that in a minute, but you end up with just this mess and it's very hard to understand how all of the disparate parts of your application are actually talking to each other. And you end up, you know, trying to explain to your developers and your teammates just exactly how all of the microservices work together. If you haven't seen this video, I highly recommend it. A lot of you sound like this and you're not willing to admit it, all right?
So let's talk about the actor model. Where does the actor model come from? Well, there was a seminal paper titled A Universal Modular actor formalism for artificial intelligence, and this was back in 1973. So this paper defined what an actor was basically a unit of computation that, could make decisions. Based on messages sent between other actors, and this really set the foundation for, just the concept of an actor model. Alan key took these ideas. He's the one who you could blame for object-oriented programming, which is now embodied by Angular. And, uh. So he is the one who actually realized that this whole message passing paradigm was really, really useful. And so, even though he's not using actors when he, worked on things like small talk. It was heavily influenced by that earlier work of the actor model. And then there's G. AGA, who actually took the actor model and provided a strong theory, theoretical, and practical foundation for how actors can be used in, in our everyday applications.
Now, the actor model seems a little bit simpler than state machines. When you take a look at, the Wikipedia article, and I promise you, this is actually an easier concept than you may think. In fact, the actor model, uh. Even though it's not been explicitly named, it has been mentioned multiple times throughout this conference. We had to talk earlier about Authentic A, where there were sequence diagrams being shown. The last talk even showed sequence diagrams, and we had a community talk upstairs, which, talked about microservices, which are essentially the actor models. So, just like state machines, this is one of those things where you might have been using it without even knowing it. So let me take a practical example. And let's actually do it in human terms. There's one of my favorite things at a conference is when there's Baristas, when there's people making coffee for you, it's fresh coffee, it's absolutely delicious.
4:00 Let's actually talk about the actor itself
Now here's the thing, though, how do we communicate between one human in another? We would say something like, I would like a coffee. You might be thinking it, but people cannot read your mind, right, so you have to actually communicate using words. Well, or the bir is a good start. Because you're probably staring and being like, please read my mind. I want a coffee. They would be like, what would you like? And you would say, I would like a coffee, please. And then there's a transaction between you and the barista, where the barista would say, here you go, and then you would say thanks. So this communication happens between what are known as actors. Now, of course, in TV and performances, actors are people pretending to do things, but in programming, actors are objects that could send messages to each other. And we'll get, into that a little bit more.
So in the real world, we express actors via sequence. Just like those lines of communication that you saw again in this previous slide and in previous talks too. So, for example, someone might ask for a coffee and then you might have multiple actors involved. So the cashier might be asking the barista, hey, this person just ordered the coffee? Can you start making it? At the same time? The cashier will say, I. I couldn't find the emoji for huff so or Hungarian, sorry. FLORen FOREN sure, yeah, um. And and so they might request money. Why don't you use Euros? Sorry, if that's touchy, uh. And then the cashier would ask for a receipt, and then the coffee would be delivered, probably handed to the cashier, and then the cashier would deliver it to, the person ordering the coffee. This, just like you saw before, is known as a sequence diagram, and it makes actor communication visual and especially clear so that your entire team can understand what's going on.
Okay, so let's actually talk about the actor itself. Actors have a few rules. First of all, all computation is performed inside of an actor, so that means when an actor receives an event. It's not, you know, like, it's basically a self-contained thing. So just like you're using Redux ETC, everything happens within that store. Same thing with actors, everything happens within the actor, which is why the actor is a computational unit. Actors can also communicate only through messages. So again, I can't read your mind, you can't read my mind. So we have to talk to each other to get a feeling for you know just what's going on. And in response to a message, an actor could do one of three things first of all, it could change its behavior. So, for example, if I'm sleeping and the alarm goes off now I'm awake, that's my behavior changing. I received a message or an event that, you know, there's this loud sound, I have to wake up.
I have to finish my talk and now I've changed my behavior, so I'm going to act differently. Depending on what messages come in, actors can also send messages to other actors. And this is a core part of the actor model, this communication model between actors. And of course, actors can also create other child actors. Because, just like one ant is no ant, one actor is no actor. You have to have an actor system of these parts, actually talking to each other, and whether you believe it or not. Every single application you make is actually a distributed system, it's many parts talking to each other. You might not think so, but if you're talking to a database, if you're talking to an external API, you're basically working on a distributed system. Okay, so how an actor works? Actors send and receive messages, they can change their internal states, they can spawn child actors. But let's get into actors behavior, behavior. So, you know, you didn't escape the state machine parts?
I am going to be talking about state machines, and that's because an actor's internal state can basically be described as a state machine. When an actor receives a message or an event, I use the two interchangeably. It could change its states. So, for example, if I'm a barista or a cashier, and I receive an order for a coffee, I'm going to change my states to making the coffee. And so that means when I'm in the state of ordering, or, sorry, of making a coffee and someone else tries to order it, I'm going to. I'm not in the state where I'm accepting that event, so I'm going to ignore them. I'm going to continue making the coffee, and, you know, eventually, when it's made, I will deliver the coffee and then I'm ready to make more coffee or receive other orders. Something that can also be expressed in an actor's internal state is this idea of data, so other data that's relevant to the actor. So, for example, I might have a QU of orders.
I might have to keep track of inventory, such as how much milk I have, how many beans I have, and the sales. So, you know, how much the coffee costs? So, like I said, actors need to work. Together, you can't just have one actor, so this is known as an actor system. So, still, going by the coffee analogy, you could probably tell I really want coffee right now. A cafe might have two managers, right? So we always have to have a root actor, uh. In actor Frameworks, this is known as the Guardian actor, and so a manager. Each manager might be in charge of different employees. So actor systems naturally form this hierarchy where they could talk to each other. And also there's something called mailboxes, and this is how actors receive events, so more on that later, uh. But also actors have addresses, too, which is how you could tell what part of the system an actor is in.
Now. One important thing about the actor model is that you cannot communicate with an actor that you don't have a reference to. So, just like, I can't talk to you if I don't have your. I have your email, Kevin, but I can't talk to you if I don't have your email address. Same thing with actors, you can't talk to an actor if there's no way of communicating with them. Now. Actor mailboxes are an important part of an actor, because, you know, you might get into race conditions. So, for example, let's say that actors receive events or messages directly. So so, you know? They might receive a message and they would change their state, they might receive another message, process that change their state. They might receive another message, and while processing that message, they might receive it again. While they're still processing the original message, so they haven't changed their state, you're going to end up with a race condition, and I have proven this many times.
I love going on web forms and just smashing the submit button many times, especially if it's not disabled and seeing just what happens. Or those pages where it says, please do not refresh or reload or close this page. I do it anyway, I see what happens. So that's why we have actor mailboxes, and mailboxes are essentially cues for actors. So just like you have an email inbox. You would go through messages one at a time. So that means that you will pull from this queue one message at a time. And if? You receive another one while you're processing a date, it gets add onto the queue so that you can guarantee that you will not have any race conditions. And so this is a great part about the actor model, so how do we actually implement the actor model? There's lots of Frameworks for this and languages, too, Earling is sort of like the OG original language for implementing the actor model.
And AA is one of the most popular Frameworks, or probably the most popular framework in Java or Scala. There's also a AA. JS, for implementing the actor model. So another native language is Pony Dapper is a framework. C, and other things, that use the actor model. And so is Orleans and Xat, which I'm going to talk about, actually implements the actor model as well. And Elixir is built on top of the Earling VM. And there's a lot of actor model related things there. There's lots of applications of the actor model, both in the back end and in the front end. So imagine that you're working on a music production CTIOn application. You would probably want to have the part of your doll. Your digital audio workstation respond to the knobs twisting and button pushing and pressing of the keys. And you would want that to be immediate.
All of those are actually events or messages or signals, whatever you want to call it, events sent to and from, that workstation. And then when you're actually processing music, that might be a backend task, so that's just an example, but that can also be modeled using actors. All right, so let's actually dive into some code. We're going to make an actor, and this actor, we're going to, it's going to have the ability to send a message. Change its own state. And you know, if we get to it, we'll be able to spawn actors and everything, too. Actors are, like I said, they're not complicated, so basically this is the ideal interface that we want. An actor needs to be able to send a message, and so when it receives that message, it's going to process it. Change its internal state, and an actor should also be able to. Or, at least for convenience, you should also.
Be able to subscribe to an actor so that an actor can share with you some of its date. If it's, if it's willing to. And so if you squint, you could really look at this like you're sending it. An event that says, please subscribe me to your state updates, and an actor can say, Okay, yeah. So for all of the actor refs who are subscribing, I'm going to send them state updates. But that's unimportant. Basically, actor. Subscribe is just a convenience in XState to make actors actual, reactive streams. All right, so let's create a function that creates an actor. Of course, we need a send method and so we have that and so we could send in a message. It doesn't really do anything useful yet. Remember, an actor has to have internal states, so we're going to put it in the function closure. Let's just say we're creating a an actor that can count, and so when we send it in event that's increment, we're going to increase the count.
And so this actually works pretty well, uh? But let's say we actually want to not hardcode that logic, we want to customize it, so instead we should pass it a transition function, and this transition function will say, given the current state and the event that I just received, here's what the next state of the actor should be. It's a pure function, and that, abstraction actually works pretty well. But now we have to implement subscriptions, so things get a little bit more complicated. So with subscriptions, it's not as complicated as it may seem. You need to, set up a set of observers, and then subscribing needs to be a way to register. The observer to add them, and then, of course, you need a way to unsubscribe or delete that observer. And so basically, whenever you have a state update, you're giving the Obers the ability to observe that state update. Of course, things get more complicated.
Because, you know, we. We have to have custom logic, and we could actually wrap all of that logic inside of a, this logic object. Over here, let's see if I can scroll down to it, and so you see that, logic. We have our transition function and we have an initial. Context so this would be our initial states, so we could say, hey, the initial state of this should be, counts of zero. So again, it's getting a little bit complicated. And now we also CU actors need to, of course, send me messages to other actors and actually perform effects so that they're useful. And so the way that we could do this is by passing the actor itself to the actor. Logic. So you could see that we're doing that over here, we're passing the actor to the logic. transition function. And so that allows us to do things like call a promise, and then when the promise resolves, we could send oursel an event like resolve.
And we could get that data and then we could do something useful with that data. So, now for a guest appearance. XState really abstracts all of that away from you, but at least you know how to make a basic actor. You know, from this, I'm, I'm the creator of XED. I created it about 9 years ago and originally it was for state machines, until I discovered that XState and state machines are actually really useful. In the concept of, the actor model, so XState provides this create actor function, where you provided some logic, which could be a state, machine or other things. And then you have the ability to, of course, subscribe to the actor, start it and send it events. So this type of logic, can be a function. So the reducers that you're used to, such as this transition function where we get the state and the event and we could just increment the counts when we have an increment event and we have an initial state over here.
And then you could see here that we're actually sending that, that event. And that's going to affect the actor. We could also provide it input, and you could do this with all the other actor logic types. So just wanted to demonstrate what that looks like, and you could also make actors from promises, because, remember, in an actor can, you could send it events, you could subscribe to it. At least in our version of the actor model, and so with promises, it's essentially you're initializing the actor the actor is. Doing something asynchronously, and eventually the actor is giving back a value, and so that's exactly what's happening here. And you also have some input. I know I'm breezing through this, but please talk to me afterwards or see the documentation to get more in depth. I don't expect you to pour over every line of code. Like the last talk showed you, you should not do that immediately. Start at the higher level, uh.
But with state machines in Exate, you can also invoke actors, so state machines and XState provide this framework for you to actually have actor orchestration. Basically, in a states, you could say, spawn this actor or invoke this actor. And then that is something that I could communicate with, and I'll show you an example of that soon. But, over here, I might be invoking counter logic and then I could send events to it or subscribe to it. And then once we've reached a Max count, we could, go to this final state. And that actor is automatically stopped, so that's a lot to say, but an actor? The basic interface of it in XState is just these four methods. We have a way to send events, we have a way to subscribe to the actor, we have a way to get the actor. Snapshot, which is basically a casted version of this.
It creates an implicit subscription. So we could see what was the last date the actor had, and of course, we have a way to starts the actor, but what I find is more interesting is visualizing actors. So, regardless of whether or not you use XState, I want you to really use diagrams. Again, just like the last talk, I want you to use diagrams. And actually communicating how the logic works in your application. So sequence DIAG s are an amazing way of doing this.
20:00 Parts, we have the card reader, so this coffee thing…
One of my favorite tools for this is swim., there's also app map, and Stately also has a built-in inspect package too, and so this allows you to inspect actors that, are created with XState. And so, just to demonstrate this again, don't read all this code. But this is a, uh. It's a coffee machine, and this coffee machine, it allows you to basically dispense coffee. And so we have a few moving. Parts, we have the card reader, so this coffee thing, you need to give it your card information. And there's also a dispenser, which is just this robotic device that will communicate back with the coffee machine. Saying this is how much coffee was dispensed, and if it's done dispensing, so it goes through a few steps, you know, first the or, yeah, first it's idle, and then when the user inserts the card and the card is red, we're in the card inserted states and hopefully the card is valid. It allows the user to select what they want to drink.
And then, you know, it might error and then goes back to idle if it errored, uh. And then if it's ready to dispense, we're waiting for the dispenser to actually start dispensing, and then when the dispenser is done, we reach the finished date. And eventually we just go back to the idle. State. Now, instead of reading code, I'll show you a diagram instead, but this is essentially, what I just described. We have a state machine in the middle and we have a sequence diagram on the right. Updating live as this dispenser is working. So, again, this is Stately inspect and you could go to GitHub.com AI inspect and start playing with it. So there's a couple of advantages to actors, first of all, you have location transparency. So because you have that interface of actor. send actor. subscribe, it actually doesn't matter where that actor lives.
22:00 Workload to another actor, so actor systems are actually really robust…
So, traditionally, in Java ETC, we might think of multi-threaded architectures ETC. But you don't really need to think about that with the actor model, because that's all abstracted away. In fact, actors can live on the same thread, they create what's sort of known as virtual threads, but basically it's abstracted away. It doesn't matter where the actor lives. It could be in memory, or it could be some remote service that you're calling to, because the interface is exactly the same. And also, especially with distributed systems. Actors have this, this notion of false tolerance. And so what that means is that if one actor fails, the parents actor can decide what to do. It could decide, let me try to restart that actor or revive it, or it could just pass off that. Workload to another actor, so actor systems are actually really robust and false tolerant. There's a bunch of pros and cons to the actor model.
The pros are. It makes it easy to scale. We saw in a community talk upstairs how microservices really allow, companies to scale horizontally and move a lot faster. They're false tolerance, and they have location transparency, like I just talked about. They have no shared state, so you can't mutate the state of another actor. It makes things very predictable because they're events driven. The only way that you could affect an actor state is by events. And so some of the cons are, of course, there's microservice. Hell. There's a little bit of a learning curve to using the actor model. And it does introduce a little bit of indirection, which can be a good thing and a bad thing. You do have really nice logging, so you're not just making method calls, you're actually understanding what events or messages caused the actor to reach a specific state. And, the actor model, at least for now, might be unfamiliar to your team.
24:00 I actually want to talk about agents, and actors
And hopefully, you know, I'm proving that wrong. By having the audience, you learn about the actor model. So now I actually want to talk about agents, and actors. And AI agents, as alluded to in the original paper that was, was written in 1973, where it was using actors for intelligent or artificial intelligence, agents can use the actor model really well. So, I have 5 minutes, I'm going to go through this briefly. What is an agent? an agent? Which is all the R right now in AI? It's something that performs tasks in order to accomplish a goal, such as this robot trying to kick the ball in. It's also something that observes the environment and learns a model of how the environment works. It also receives feedback so that it could improve over time. It could realize that it made a mistake in doing one action, so when it tries to do that again in a similar state, it will avoid doing the bad action.
And agents are actors, so when it comes to message passing, just like with LLM agents, these are the prompts that we give it, and also the. Observations that these agents receive in terms of internal state, this could be the short and long-term memory of an agent. And, for spawning actors, sorry, that's just multi-agent architecture. Take a water break, I don't know if you're clapping for the water or. I released Stately agent beta a few days ago, so you know, if you want to try it out, uh. But the examples I'm going to show you use the actor model in and eates with agents. And specifically, we're using the Versel AI SDK for this, and it's completely open source.
26:00 Hopefully you could see, in this state, in the guessing state…
By the way. I just have to finish writing documentation, but you could take a peek at it now. The examples are actually pretty, pretty cool. Because they provide guard rails for these, for these agents, and they provide an easy way to model agents as an actor system. Because it uses XState, so in this example, not sure if you could see it that well, but we have a game and this game. We're providing a word and we're trying to have an agent. Guess what the correct word is? And so hopefully you could see, in this state, in the guessing state, an agent can do one of two things it could guess a letter or it could guess a word. It only has 10 guesses for the letter, so once it's reached that limit, it goes to the final guess date and the agent can only guess a word. So we're, and this is how it works, using, of course, a sequence diagram.
So again, it could guess a letter or a word, and eventually it will guess a word, and, hopefully it gets the correct word. So let's actually do a demo, here, and this is the state machine, by the way. Just open this. Wow, you cannot see that, but it's the same one that I showed you. And this is using the VS code extension by Stately. Someone give me a word, coffee, that's a great word, okay? So, enter a word and the agent will try to guess it. So what's going to happen is based on the state of this state machine that the agent is working in. It is going to be sending events back to it, based on which state. It's in so, again. In one state, it could guess letters, but when it makes a maximum number of guesses, it can only guess the word. So let's see if it gets it right.
So it's guessing E, and it's also explaining why it's doing it. Of course, it's going to be guessing common letters, but eventually it's going to reason about or reason. The best that LLMs can do. It's going to try to figure out strategies for getting the full word all right. So it said it hasn't guessed L yet. Based on the letters. Guessed, it's guessing m. D is another common letter B, so notice it's actually not repeating letters either. And why W? It guess it correctly. So there we go, in, I. I actually recorded an example in case my thing failed. And it failed to guess Budapest because it thought that the word was, can you guess breakfast? Oh well, I'm glad I didn't use that as an example, but that's okay. And so, in an even more exciting example, we're also using the actor model and hent architecture to have the agent act as an intelligent assistance, for the user on an application that's already using state machines and XState.
And so the way that we're doing this is first, we create an agent, and this is an overview of what Stately agent is. We provide it a model such as open, OpenAI, GP40, and we give it events. And so this is just Zod, if you've used Zod before, and you, give it the payload of the events and you describe what those things actually do. And then, this is an endpoint, so we're using an API, endpoints in Versel. And, uh. The agent has to decide what to do next to achieve this goal, and then eventually, the, the agents will come up with the plan. So again, here's a sequence diagram for how that works the user shares a goal and that goal state is sent to the SER server. And so this endpoint is going to figure out what events does it need to send to the client in order to make that goal happen?
30:00 I had over here to finish the JS comp Budapest talk…
And then, the clients will basically ingest those events and send it, to. Itself. And because we're using a state machine, we could read that exact machine both on the client and the server to determine what to do next. So hopefully this works. This is my to-do app. I don't have enough time to go through the code, but let's say that going exit those. I really need a coffee soon, so it guesses the intent, and it says, get a coffee, I really need a coffee soon. And I also thought it was going to rain today, so I'm going to say, it's my rain today. Might need an umbrella and so it will add another to-do, and it does know the difference, hopefully, between a to-do being done and a to-do being unnecessary. So we could say, actually, it won't rain today and I never needed an umbrella. Let's see what happens. Okay, cool, it's going to delete that. And I had over here to finish the JS comp Budapest talk, today, but I, finished it.
It's not done yet. Don't worry. I finished my talk and so hopefully it will mark it as done. There we go. So all right, I'm glad that worked. So overall, the actor model, at least in my view, is an intuitive way to model complex systems. Even if you don't use XState, or you don't use actors directly. Just using diagrams is going to help a lot, especially sequence diagrams to, describe how actors communicate with each other, and state machines to describe the inner behavior of actors. The actor model has been transformative for how I think about software, and hopefully it's transformative, for you too. I also want to say that, my first International talk was at CSS COMP Budapest in 2016, and so it's, it's bittersweet that this is, you know, the last JS Comp Budapest. You know, we've come a long way and this has been the conference that really started it all. So, let's go have a coffee and scene, thank you.
Related talks
-
Everything is an Actor
React Finland Mini-Conference #2 (2020). 44:49, 11K views -
Putting the "Act" in Reactive: From Observables to Actors
RxJS Live! London, 2021. 38:31, 1.6K views -
Webinar: Get started with XState v5
Stately webinar, 2023. 1:13:11, 11.3K views -
Webinar: State Machines and Actors with XState v5
Stately webinar/stream, 2024. 2:42:16, 7.5K views