JUNE 26 2025
How we built this: durable, serverless agents in Modus v0.18
Modus v0.18 brings stateful, crash-proof agents to Hypermode’s WebAssembly runtime. See how we added persistence, actor-model scaling, and GraphQL-SSE observability for production-grade AI flows

At Hypermode, we believe AI agents should be as reliable as the infrastructure they run on. That’s why we built Modus, a production-ready agent runtime designed to make advanced, agentic workflows accessible and robust for every developer. But as our community pushed the boundaries of what agents could do, one challenge kept surfacing: durability.
In earlier versions of Modus, agents were stateless by default. They could reason, act, and connect to hundreds of tools, but if an agent crashed or the environment restarted, all context was lost. For simple tasks, this was fine. But for real-world, long-running, or mission-critical workflows, it was a dealbreaker. Developers needed agents that could persist state, recover from failure, and pick up right where they left off.
With Modus v0.18, we’re excited to introduce durable agents: a new foundation for building resilient, persistent AI systems. In this post, we’ll take you behind the scenes on how we designed, architected, and shipped durability in Modus.
Introducing stateful agents
Today, we’re releasing Modus v0.18 and introducing full support for stateful agents. This is the next step towards Modus v1 and the future of building with AI agents.
Serverless foundations
Modus started as a runtime for building serverless functions. We optimized it for rapid development of AI-enabled microservices.
- You write a simple function in Go or AssemblyScript, and the Modus SDK compiles it to a WebAssembly module.
- The Modus runtime loads your compiled code into a secure sandbox, automatically generates an API for your functions, and provides easy connectivity to AI models, databases, and external APIs.
Functions within Modus are highly performant and scale out in less than 10ms. However, as serverless functions, they are limited by being stateless. For each function call, Modus creates a new instance of your WebAssembly module, and assigns it a dedicated memory space to operate in. Each call is a fresh start. You cannot simply set a variable and expect it to persist between function calls.
This is highly beneficial from a performance and scalability perspective, but can make it difficult to orchestrate context within agents.
Now with state persistence
With Modus v0.18, we’ve added another building block: agents. Modus agents are stateful and work differently than functions.
- You write an agent as a struct or class using the base types provided in the Modus SDK. In your code, you also register that agent to make it discoverable.
- You can then use the Modus SDK to start or stop an instance of an agent, and to send messages to it. You can do this either from a regular Modus function or directly within the agent.
- You control how an agent handles the messages it receives. You can do almost anything, including:
- Read or write the agent’s state variables
- Invoke a large language model
- Call an external API or query a database
- Publish events on your agent’s eventstream (more on this below)
- Your Modus app, including functions and agents, compiles to a WebAssembly module just as before. The security and performance characteristics of WebAssembly underpin stateful agents in Modus.
- When you start an agent, the Modus runtime creates an actor for your agent. Each actor you start gets a dedicated instance of your WebAssembly module, including its own memory space. This is similar to functions with one key difference: the memory space is retained for the lifetime of the agent. This allows your agents to be stateful!
Simple example
Here’s a very basic example of an agent that counts:
- It keeps track of a
count
local state variable, as a field on theCounterAgent
struct. - It implements
GetState
andSetState
methods, which Modus uses to suspend the actor and restore it as needed. - It responds to
"count"
messages by returning the current count. - It responds to
"increment"
messages by modifying the current count, publishing an event, and then returning the new count.
type CounterAgent struct {
agents.AgentBase
count int
}
func (a *CounterAgent) Name() string {
return "Counter"
}
func (a *CounterAgent) GetState() *string {
s := strconv.Itoa(a.count)
return &s
}
func (a *CounterAgent) SetState(data *string) {
if data == nil {
return
}
if n, err := strconv.Atoi(*data); err == nil {
a.count = n
}
}
func (a *CounterAgent) OnReceiveMessage(msgName string, data *string) (*string, error) {
switch msgName {
case "count":
s := strconv.Itoa(a.count)
return &s, nil
case "increment":
if data == nil {
a.count++
} else if n, err := strconv.Atoi(*data); err == nil {
a.count += n
}
if err := a.PublishEvent(countUpdated{Count: a.count}); err != nil {
return nil, err
}
s := strconv.Itoa(a.count)
return &s, nil
}
return nil, nil
}
Registering this agent is simple. In Go, use the init
function like this:
func init() {
agents.Register(&CounterAgent{})
}
You can then write Modus functions to start and stop your agents, and to interact with them through message passing.
func StartCounterAgent() (agents.AgentInfo, error) {
return agents.Start("Counter")
}
func StopAgent(agentId string) (agents.AgentInfo, error) {
return agents.Stop(agentId)
}
func GetCount(agentId string) (int, error) {
count, err := agents.SendMessage(agentId, "count")
if err != nil {
return 0, err
} else if count == nil {
return 0, nil
}
return strconv.Atoi(*count)
}
func UpdateCount(agentId string) error {
return agents.SendMessageAsync(agentId, "increment")
}
You can find the entire Go agents example in the Modus repository on GitHub, fully commented for your reading pleasure. There’s also an equivalent agents example for AssemblyScript.
Just imagine the possibilities when you consider tracking context, tool calls, and other interactions with large language models. Or better, read our accompanying blog posts (on event streaming and on chat agents) and documentation!
Agents and the actor model
We built Modus agents with an implementation of the actor model. It is a tried-and-true software engineering pattern that has been used in countless high-quality, enterprise-grade systems since it was developed in the 1970s.
The fundamental ideas behind the actor model are:
- An actor is single-threaded, thus does not require locking mechanisms.
- All interactions with an actor are done via message passing. Each actor has its own mailbox and queue, so you can send it as many messages as needed, and they are processed in the order received.
- An actor has a lifetime. It is started, performs some amount of work, and ultimately is stopped. It may also suspend and resume. When managed by a framework, the lifetime of an actor can be short-lived such as seconds or milliseconds, or long lived such as days or weeks.
- When handling a message, an actor can read and write local state, send messages to other actors, or create new actors. It can also interact with its environment by making API calls such as HTTP requests or database queries.
While the basic actor model is a relatively simple pattern, often it’s implemented by frameworks that can provide more advanced capabilities. Internally, the Modus Runtime uses GoAkt-an actor framework for Go inspired by Erlang and Akka. GoAkt provides Modus with advanced capabilities including cluster-based scale-out and topic-based “pub-sub” messaging, amongst other capabilities. As Modus develops further, we plan to take advantage of GoAkt’s scheduling capabilities, to execute agent tasks at specific dates and times.
We believe that the actor model provides the ideal foundation for AI agents, primarily due to their single-threaded interactions with large language models. This is most evident by conversational chat-based agents that we’ve all become accustomed to, but it also holds up for agentic flows beyond simple chat.
Built-in agent observability with eventstreams
Much like humans, agents shouldn’t act in a black box. Communication of progress is key for building engaging, transparent agentic experiences. That’s why Modus agents include a native eventstream for each agent instance.
The example agent shown earlier publishes a single countUpdated
event, each time it updates the count. A more complex agent could publish many events throughout the process of handling any message. The key point is that events are published from agents and can thus be listened to on a per-agent instance basis.
- When you start a new instance of an agent, the ID of that instance is returned.
- When you publish an event from a Modus agent, the underlying actor publishes that event to a topic isolated to that agent instance.
Since Modus was already designed with GraphQL in mind, we decided to use GraphQL subscriptions for the subscriber side. After some experimentation with the available transports, we went with GraphQL-SSE, which is very easy to consume from browsers and most modern GraphQL clients, as well as from web developer tools such as Postman. Unlike WebSockets, SSE (Server-Sent Events) doesn’t require advanced networking configuration. You can read more about the differences from our friends at Wundergraph.
Coincidentally, SSE is how most large language models deliver streaming text responses. In a future release, we’ll leverage our GraphQL-SSE implementation paired with an SSE client in the Modus SDK, to support streaming chat from AI models. You can follow this GitHub issue for updates.
Having support for SSE subscriptions provides some very nice user experience capabilities. Your UI can start an agent, subscribe for events, and then send it messages. As the agent responds and publishes app-specific events (such as tool calls), your UI can update in real-time.
Conclusion
Durability is more than a feature—it’s a foundation for building AI agents you can trust in production. With Modus v0.18, we’ve made it possible for agents to persist state, recover from failures, and maintain context across sessions, all while preserving the performance and security benefits of WebAssembly.
By combining the actor model with stateful persistence and native eventstreams, Modus agents are ready for anything you want to build. Whether you’re orchestrating complex toolchains, building conversational agents, or managing autonomous processes, you can rely on Modus to keep your agents resilient and observable.
This release is a major step toward our vision of making advanced agents accessible and robust for every developer. But we’re just getting started. We’re excited to see what you build with durable agents, and we’re eager for your feedback as we continue to evolve Modus toward v1 and beyond.
Ready to get started? Check out the examples on GitHub, dive into the docs, or join the conversation in our Discord.