<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pavel Buchnev</title>
    <description>The latest articles on DEV Community by Pavel Buchnev (@butschster).</description>
    <link>https://dev.to/butschster</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F700609%2Fb9575f90-fa63-41e2-b2df-366a1f35cdb3.jpg</url>
      <title>DEV Community: Pavel Buchnev</title>
      <link>https://dev.to/butschster</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/butschster"/>
    <language>en</language>
    <item>
      <title>I Wanted Windows 95 in My Terminal. It Got a Little Out of Hand.</title>
      <dc:creator>Pavel Buchnev</dc:creator>
      <pubDate>Wed, 21 Oct 2026 10:18:42 +0000</pubDate>
      <link>https://dev.to/butschster/i-wanted-windows-95-in-my-terminal-it-got-a-little-out-of-hand-nem</link>
      <guid>https://dev.to/butschster/i-wanted-windows-95-in-my-terminal-it-got-a-little-out-of-hand-nem</guid>
      <description>&lt;p&gt;Hey everyone!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvdxijg7q21jy7csqlfbb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvdxijg7q21jy7csqlfbb.png" alt="Chicago's Welcome window on a teal desktop" width="627" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before we start, there's a short demo video at the end of this article, made by the same AI agents who helped me build Chicago. So if you're curious to see the whole thing in action, don't miss it :)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've always loved terminal applications. And since I started using Claude Code and Codex, I've been spending even more time in the terminal.&lt;/p&gt;

&lt;p&gt;But I also have a soft spot for Windows 95. I grew up with those old desktops, the gray windows, Start menu, little system utilities and all those applications we used back then.&lt;/p&gt;

&lt;p&gt;One day I was feeling a bit nostalgic and started thinking about recreating that experience.&lt;/p&gt;

&lt;p&gt;My first thought was to build it in a browser. There are already some really cool projects doing exactly that, and with HTML and CSS it seemed pretty straightforward.&lt;/p&gt;

&lt;p&gt;But then I thought, that's not really how a desktop works, is it?&lt;/p&gt;

&lt;p&gt;You boot your computer and eventually you're looking at a graphical desktop. You don't have to boot another operating system, open a browser and load a website just to see it.&lt;/p&gt;

&lt;p&gt;So what if I skipped the browser entirely?&lt;/p&gt;

&lt;p&gt;What if I could open a terminal, run a command and find myself inside a Windows 95-style desktop?&lt;/p&gt;

&lt;p&gt;And what if the terminal itself rendered the whole thing?&lt;/p&gt;

&lt;p&gt;That sounded much more interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can a Terminal Actually Do This?
&lt;/h2&gt;

&lt;p&gt;I started looking into how terminals render graphics.&lt;/p&gt;

&lt;p&gt;At first I experimented with regular terminal output. Characters, colors, borders, all the usual stuff you can do with a TUI.&lt;/p&gt;

&lt;p&gt;But I wanted something closer to an actual desktop. Windows you can move around, icons, images, fonts and eventually a mouse-driven interface.&lt;/p&gt;

&lt;p&gt;Turns out modern terminals can do quite a bit more than just display text.&lt;/p&gt;

&lt;p&gt;There are graphics protocols like Kitty and Sixel that let applications render pixel graphics directly inside the terminal. Some terminals support them, others don't. And of course Windows terminals have their own capabilities and limitations too.&lt;/p&gt;

&lt;p&gt;I spent some time figuring out what was supported, how input and rendering worked and what we could realistically build on top of it.&lt;/p&gt;

&lt;p&gt;Eventually I got the first desktop running.&lt;/p&gt;

&lt;p&gt;Nothing fancy yet. A background, a taskbar at the bottom, a Start button on the left and a clock on the right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without Kitty&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb40eupz1f3lng5hj1ltd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb40eupz1f3lng5hj1ltd.png" alt="The Date/Time window drawn with text characters, without Kitty graphics" width="617" height="744"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And after some time with Kitty&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ybjk0f70oov0oz9skbh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ybjk0f70oov0oz9skbh.png" alt="The same Date/Time window drawn in pixels with Kitty graphics, with a calendar and an analog clock" width="474" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I got windows moving around.&lt;/p&gt;

&lt;p&gt;And that's when it started getting really interesting.&lt;/p&gt;

&lt;p&gt;Because once you have a desktop, a taskbar and movable windows, you immediately start thinking about what else you could put there.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Little About Wippy
&lt;/h2&gt;

&lt;p&gt;Before going further, I should mention what I used to build all this.&lt;/p&gt;

&lt;p&gt;I've been following &lt;a href="https://github.com/wippyai/runtime" rel="noopener noreferrer"&gt;Wippy&lt;/a&gt; for over a year now.&lt;/p&gt;

&lt;p&gt;You know what I really like about it? It's a runtime born in the AI era, designed with agent systems in mind.&lt;/p&gt;

&lt;p&gt;The runtime itself is written in Go, while applications are written in Lua. It uses the actor model, so you get isolated processes, message passing, supervision, clustering and quite a few things that would normally require additional infrastructure.&lt;/p&gt;

&lt;p&gt;AI agents can even modify running applications through MCP without redeploying everything.&lt;/p&gt;

&lt;p&gt;And here's a number I really like: &lt;strong&gt;a Lua process in Wippy has a baseline memory overhead of around 13 KB.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With just 1 GB of RAM, that's theoretically around 80,000 processes worth of baseline overhead. Of course real processes need memory for their work, and the runtime itself consumes memory too.&lt;/p&gt;

&lt;p&gt;Each process has its own isolated Lua state, communicates through messages and releases its memory when it terminates.&lt;/p&gt;

&lt;p&gt;When you think about how a desktop works, with all its applications, windows, background services and events, this architecture starts to make a lot of sense.&lt;/p&gt;

&lt;p&gt;So I decided to use Wippy as the foundation.&lt;/p&gt;

&lt;p&gt;But there's another important part.&lt;/p&gt;

&lt;p&gt;Wippy has a platform called &lt;a href="https://github.com/kicksideai/kickside" rel="noopener noreferrer"&gt;KickSide&lt;/a&gt; that already provides user management, authentication, a registry, MCP integration and the ability to update components in a running system.&lt;/p&gt;

&lt;p&gt;And I started wondering...&lt;/p&gt;

&lt;p&gt;What if we took the functionality that KickSide already exposes through its web interface and gave it a proper desktop UI instead?&lt;/p&gt;

&lt;p&gt;A lot of its functionality maps surprisingly well to the classic Windows experience.&lt;/p&gt;

&lt;p&gt;Managing running processes? Task Manager.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr93od3obr5dbtspa8wbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr93od3obr5dbtspa8wbz.png" alt="Task Manager, Performance tab, with goroutine and memory graphs of the running runtime" width="719" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Installing and removing Wippy modules? Add/Remove Programs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0d1nx0936bnygph8mbcs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0d1nx0936bnygph8mbcs.png" alt="Add/Remove Programs listing the installed Chicago modules with their versions" width="719" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Browsing the runtime registry? Registry Editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwi8ixnam98n4074lyrwv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwi8ixnam98n4074lyrwv.png" alt="Registry Editor with the runtime registry as a tree of namespaces" width="769" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Managing users, services and scheduled tasks? Those could all be desktop applications too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpqm2rvqz6m142gig7rj1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpqm2rvqz6m142gig7rj1.png" alt="The Users window listing accounts with their groups and status" width="774" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So instead of building a desktop with a few fake windows, I could actually make those windows useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Application After Another
&lt;/h2&gt;

&lt;p&gt;I started with a few simple applications to test the idea.&lt;/p&gt;

&lt;p&gt;A calculator, desktop wallpapers and a Task Manager.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpn0afa4hhz665lc32u6j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpn0afa4hhz665lc32u6j.png" alt="Display Properties with wallpaper choices next to the Calculator" width="765" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal was to make sure the window system worked properly, applications could run independently and the desktop could handle multiple windows at once.&lt;/p&gt;

&lt;p&gt;Once that worked, I started adding more of the KickSide functionality.&lt;/p&gt;

&lt;p&gt;Registry Editor, user management, services, scheduled tasks and other system utilities.&lt;/p&gt;

&lt;p&gt;At some point I realized I'd already given a graphical interface to roughly half of the KickSide modules I was using.&lt;/p&gt;

&lt;p&gt;And I still had more ideas.&lt;/p&gt;

&lt;p&gt;Remember ICQ?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkkpzvybtg7zteulxwe40.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkkpzvybtg7zteulxwe40.png" alt="The aICQ contact list with people and agents" width="317" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That little messenger was such a big part of the early internet for me. And since Wippy already supports clustering and messaging between processes, I thought, why not build something similar?&lt;/p&gt;

&lt;p&gt;That's how aICQ appeared.&lt;/p&gt;

&lt;p&gt;The idea was to let users communicate with each other, but also with AI agents connected to the system.&lt;/p&gt;

&lt;p&gt;So you could have regular users and LLM-powered agents in the same contact list.&lt;/p&gt;

&lt;p&gt;And if you connected multiple Wippy nodes into a cluster, the messaging system could work across those nodes too.&lt;/p&gt;

&lt;p&gt;Basically a little piece of internet nostalgia, running inside a terminal desktop.&lt;/p&gt;

&lt;p&gt;Of course, I couldn't stop there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Logging In?
&lt;/h2&gt;

&lt;p&gt;Remember the classic Windows login screen?&lt;/p&gt;

&lt;p&gt;Well, KickSide already has an authentication system. So why not use it?&lt;/p&gt;

&lt;p&gt;I added a login screen to the desktop, connected it to KickSide's user management and suddenly the desktop had actual user accounts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc34ifzlen8qkgelb4spn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc34ifzlen8qkgelb4spn.png" alt="The Welcome to Chicago logon dialog asking for a user name and password" width="567" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Different users could log in and get their own desktop sessions.&lt;/p&gt;

&lt;p&gt;And then I had another thought.&lt;/p&gt;

&lt;p&gt;We're already running everything in a terminal. Terminals have SSH. Why not connect directly to the desktop over SSH?&lt;/p&gt;

&lt;p&gt;Not SSH into a server and then manually start the application.&lt;/p&gt;

&lt;p&gt;I mean connecting to a port and immediately getting the graphical desktop.&lt;/p&gt;

&lt;p&gt;So we extended Wippy Runtime with a built-in SSH server.&lt;/p&gt;

&lt;p&gt;Now you can run Chicago on a remote server, expose its SSH port and connect directly to the desktop.&lt;/p&gt;

&lt;p&gt;Users can add their public SSH keys to their profile. Once the key is associated with their account, they can authenticate with it and open their desktop session without going through the password screen.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 2222 your-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it.&lt;/p&gt;

&lt;p&gt;You're inside a Windows 95-style desktop. In your terminal. Over SSH.&lt;/p&gt;

&lt;p&gt;No browser, no HTML, no remote desktop client.&lt;/p&gt;

&lt;p&gt;Just SSH.&lt;/p&gt;

&lt;p&gt;I remember the first time we got the whole thing working together. It was honestly pretty wild.&lt;/p&gt;

&lt;p&gt;You open a terminal, connect to a server and suddenly there's a desktop with movable windows, a Start menu, applications and all the little details I remembered from the 90s.&lt;/p&gt;

&lt;p&gt;I started showing it to developer friends and colleagues.&lt;/p&gt;

&lt;p&gt;A few of them had a hard time believing it was actually running inside a terminal. They thought it was some kind of web UI or remote desktop streaming.&lt;/p&gt;

&lt;p&gt;But nope. The terminal was rendering it.&lt;/p&gt;

&lt;p&gt;That was probably my favorite moment of the whole experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  20 Agents, 3 Teams, 1 Week
&lt;/h2&gt;

&lt;p&gt;There's another part of this story.&lt;/p&gt;

&lt;p&gt;Around that time, I had just built the first version of &lt;a href="https://harnsy.dev/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=YYYY-MM-DD-devto-1&amp;amp;utm_content=chicago-article" rel="noopener noreferrer"&gt;harnsy&lt;/a&gt;, a tool for managing teams of AI agents running in terminal sessions.&lt;/p&gt;

&lt;p&gt;I wanted to test it on a real project and see how well agent orchestration actually worked.&lt;/p&gt;

&lt;p&gt;Chicago seemed like a perfect candidate.&lt;/p&gt;

&lt;p&gt;I set up 20 agents across three teams.&lt;/p&gt;

&lt;p&gt;One team was working on Wippy Runtime, another on the desktop UI, and the third on the core system and integration.&lt;/p&gt;

&lt;p&gt;And to my surprise, they worked like clockwork.&lt;/p&gt;

&lt;p&gt;I delegated the work and the agents figured out how to implement it, communicated with each other and kept moving forward.&lt;/p&gt;

&lt;p&gt;Watching the UI team was particularly fun.&lt;/p&gt;

&lt;p&gt;They were studying screenshots of the original Windows 95, comparing them with what they had built and trying to get the interface looking as close as possible.&lt;/p&gt;

&lt;p&gt;Which wasn't easy at all. Terminals were never designed to render desktop operating systems.&lt;/p&gt;

&lt;p&gt;Sometimes they got surprisingly close. Sometimes the results looked pretty weird :)&lt;/p&gt;

&lt;p&gt;But they kept experimenting, fixing things and improving the UI.&lt;/p&gt;

&lt;p&gt;I got so carried away that what started as a small desktop experiment grew into a much larger project.&lt;/p&gt;

&lt;p&gt;We even started experimenting with Wippy clustering and remote desktops, where multiple nodes can work together and users can access remote desktop sessions, somewhat like RDP but inside a terminal.&lt;/p&gt;

&lt;p&gt;And all this happened in about a week.&lt;/p&gt;

&lt;p&gt;I managed to burn through three weekly AI usage limit cycles during that time. With 20 agents working in parallel, apparently that's not very difficult.&lt;/p&gt;

&lt;p&gt;Here's roughly what the communication between agents looked like over those seven days.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcdp7xk6c5ggw6xjxzjge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcdp7xk6c5ggw6xjxzjge.png" alt="A graph of the agents as dots and the messages between them as lines, over seven days" width="800" height="764"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some agents finished their tasks, others ran out of context and handed over their work to fresh sessions. New agents picked up where the previous ones stopped.&lt;/p&gt;

&lt;p&gt;And this kept going for seven days.&lt;/p&gt;

&lt;h2&gt;
  
  
  3½ Floppy Disks, But as Installable Program Bundles
&lt;/h2&gt;

&lt;p&gt;One of my favorite little details in Chicago is the &lt;strong&gt;3½ Floppy&lt;/strong&gt; drive.&lt;/p&gt;

&lt;p&gt;I really wanted that old feeling of putting a disk into the computer, opening it, finding a program on it and "installing" it. So I added floppy disk emulation to the desktop.&lt;/p&gt;

&lt;p&gt;But instead of emulating floppy disks just for nostalgia, I made them actually useful.&lt;/p&gt;

&lt;p&gt;In Chicago, a floppy disk can act like a small bundle of applications or features. You insert a disk, open it, run its setup and it adds new functionality to the system. New programs appear, and the desktop behaves as if you had just installed something the old-school way.&lt;/p&gt;

&lt;p&gt;And of course I wanted the reverse too.&lt;/p&gt;

&lt;p&gt;If you eject the disk, the software can disappear from the system again.&lt;/p&gt;

&lt;p&gt;That part was especially fun, because it turns the floppy into something halfway between old removable media and a lightweight plugin system.&lt;/p&gt;

&lt;p&gt;So it looks nostalgic, but under the hood it's really a modular delivery mechanism for desktop apps.&lt;/p&gt;

&lt;p&gt;I love that kind of thing. It feels like an old Windows experience, but it also fits naturally into Chicago's architecture, where applications are modular and can be attached or removed dynamically.&lt;/p&gt;

&lt;p&gt;It's one of those features that started as a joke and ended up being surprisingly practical.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frfc1ha5y7dz3kb7wox5k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frfc1ha5y7dz3kb7wox5k.png" alt="The 3½ Floppy window with a list of disks to insert and an Insert Disk button" width="640" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  You Can Build Your Own Applications
&lt;/h2&gt;

&lt;p&gt;One thing I really wanted from the beginning was a modular system.&lt;/p&gt;

&lt;p&gt;Chicago applications don't need to live inside one huge repository.&lt;/p&gt;

&lt;p&gt;Each module can have its own GitHub repository, versions and releases. Wippy resolves the dependencies and loads the modules into the application.&lt;/p&gt;

&lt;p&gt;We also built an SDK for writing desktop windows in Lua.&lt;/p&gt;

&lt;p&gt;And the nice thing is that your AI agents can use it too.&lt;/p&gt;

&lt;p&gt;You can ask an agent to build a small application for Chicago, publish it as a GitHub module and install it into your desktop.&lt;/p&gt;

&lt;p&gt;There's even an MCP workshop that allows agents to create windows inside a running desktop without restarting the whole application.&lt;/p&gt;

&lt;p&gt;So if you want your own dashboard, a custom system utility or just another silly retro application, you can build it.&lt;/p&gt;

&lt;p&gt;And you don't have to touch the desktop core.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4vjkcmjxokaoqab81akw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4vjkcmjxokaoqab81akw.png" alt="Claude Code running in a terminal window inside the Chicago desktop, asked to create a new application" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One Last Thing
&lt;/h2&gt;

&lt;p&gt;And of course, I couldn't recreate the Windows 95 experience without the most important screen of all.&lt;/p&gt;

&lt;p&gt;Remember when shutting down your computer was a two-step process? First you asked Windows to shut down, and then Windows politely told you it was finally safe to turn off your PC.&lt;/p&gt;

&lt;p&gt;I couldn't leave that out :)&lt;/p&gt;

&lt;p&gt;So yes, Chicago has that screen too.&lt;/p&gt;

&lt;p&gt;It's completely unnecessary in a modern terminal application. But honestly, that was half the fun of building this thing.&lt;/p&gt;

&lt;p&gt;Anyway, Chicago is open source. Give it a try, break something, build your own module or just enjoy a bit of nostalgia.&lt;/p&gt;

&lt;p&gt;The GitHub repository also includes AI agent skills for setting things up, working with the desktop and building your own modules. So you can even let your coding agent help you get started.&lt;/p&gt;

&lt;p&gt;I'd love to see what you build with it!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chicago Desktop:&lt;/strong&gt; &lt;a href="https://github.com/chicago-desktop/app" rel="noopener noreferrer"&gt;https://github.com/chicago-desktop/app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wippy Runtime:&lt;/strong&gt; &lt;a href="https://github.com/wippyai/runtime" rel="noopener noreferrer"&gt;https://github.com/wippyai/runtime&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;harnsy:&lt;/strong&gt; &lt;a href="https://harnsy.dev/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=YYYY-MM-DD-devto-1&amp;amp;utm_content=chicago-article" rel="noopener noreferrer"&gt;https://harnsy.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fywd56mxo9cafctindabd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fywd56mxo9cafctindabd.png" alt="Orange text on black: It's now safe to turn off your computer." width="799" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ydvBRCCz1-k" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It's now safe to turn off your computer.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>lua</category>
      <category>opensource</category>
      <category>claude</category>
    </item>
    <item>
      <title>Building true distributed systems with RoadRunner and Laravel</title>
      <dc:creator>Pavel Buchnev</dc:creator>
      <pubDate>Sat, 10 May 2025 16:35:20 +0000</pubDate>
      <link>https://dev.to/butschster/building-true-distributed-systems-with-roadrunner-and-laravel-4eh1</link>
      <guid>https://dev.to/butschster/building-true-distributed-systems-with-roadrunner-and-laravel-4eh1</guid>
      <description>&lt;p&gt;I've been developing PHP applications for over 15 years now, working with various PHP frameworks. Throughout this journey, I've witnessed many projects that began as monoliths eventually need to evolve into microservices. That's when interesting architectural challenges emerge.&lt;/p&gt;

&lt;p&gt;While I'd successfully used RoadRunner (RR) with Spiral and Symfony before, I wanted to bring these same capabilities to my Laravel projects. Yes, Laravel Octane does provide RR integration but only for HTTP. I needed the full ecosystem of plugins that make RR truly powerful for distributed systems.&lt;/p&gt;

&lt;p&gt;If you're looking to build microservices where components communicate using gRPC, implement durable workflows with Temporal, or integrate your application with services written in other languages, you may have encountered certain limitations. Laravel excels at building robust applications, but when it comes to distributed systems with services written in multiple languages, additional tools can enhance its capabilities.&lt;/p&gt;

&lt;p&gt;Laravel is an exceptional framework, and to extend its already impressive feature set, I wanted to create an integration with RR that goes beyond what Octane offers. This integration has transformed how I approach microservice architecture with Laravel, allowing me to build truly distributed systems while continuing to use the framework I often need for client projects. With dozens of services written in Go and Python, I've been able to seamlessly communicate with them from services written in Spiral and Symfony. Now, we can also use Laravel in this same ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge of building distributed systems
&lt;/h2&gt;

&lt;p&gt;Before diving into solutions, let's understand the core challenges of distributed systems and microservices:&lt;/p&gt;

&lt;p&gt;Laravel's queue system uses PHP's serialization under the hood, which means jobs serialized by Laravel are designed to be deserialized by PHP applications. This creates a consideration to address when building a microservice architecture with services written in different languages like Go, Python, or Node.js, as these services can't directly consume messages from Laravel queues without additional adapters.&lt;/p&gt;

&lt;p&gt;Laravel's approach provides excellent developer convenience. It allows you to serialize virtually anything - objects, closures, functions, and complex structures - without having to think about format conversion. This is tremendously convenient when working within a PHP ecosystem, as you can queue complex objects with minimal effort. However, when communicating with non-PHP services in a distributed architecture, additional approaches can be beneficial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance optimization opportunities
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Traditional PHP implementations of queue systems often have certain characteristics to consider:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Connection Management&lt;/strong&gt;: PHP queue workers establish their own connection to the queue broker. The more workers you have, the more connections you need, increasing the load on your queue broker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task Distribution&lt;/strong&gt;: PHP workers independently poll the queue broker for tasks, essentially competing with each other. This leads to inefficient task distribution and wasted resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling Considerations&lt;/strong&gt;: As you scale up PHP queue workers, the connection overhead and competition for tasks grows proportionally, limiting effective scaling.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;RR takes a different approach to queue processing that can offer performance benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Centralized Connection Management&lt;/strong&gt;: RR establishes a single connection to the queue broker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent Task Distribution&lt;/strong&gt;: It fetches batches of tasks from the queue and efficiently distributes them among available PHP workers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight Communication&lt;/strong&gt;: PHP workers communicate with RR using pipes rather than connecting directly to the queue broker, creating a more efficient system.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Let's dive into RoadRunner
&lt;/h2&gt;

&lt;p&gt;At its core, RR is a complete application server written in Go. Where it truly shines is its plugin ecosystem that enhances PHP's capabilities while maintaining the development experience you already know and love.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg28yvqaquk7woc6k14j0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg28yvqaquk7woc6k14j0.jpg" alt="RoadRunner plugins" width="800" height="681"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Get started&lt;/strong&gt; with RoadRunner by visiting the &lt;a href="https://docs.roadrunner.dev/" rel="noopener noreferrer"&gt;official RoadRunner documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's explore some of the key plugins that make RR particularly valuable for developers:&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 HTTP Server
&lt;/h3&gt;

&lt;p&gt;The HTTP plugin is usually where developers start with RoadRunner. It provides a high-performance server that can handle traditional HTTP connections, HTTPS with automatic TLS certificate management via Let's Encrypt, and HTTP/2 support.&lt;/p&gt;

&lt;p&gt;What I really like about this plugin is that it totally removes the need for Nginx or Apache in the stack. I don’t have to mess with setting up Nginx or adding it to my docker-compose anymore — just one less thing to deal with.&lt;/p&gt;

&lt;p&gt;When integrated with Laravel, this alone can provide significantly faster request handling compared to traditional servers. This performance boost comes from RR's core architecture: it creates long-running PHP workers that don't terminate after each request. Instead of bootstrapping your entire application for every request (loading the framework, config files, service providers, etc.), RR simply clears the application state and handles the next request with the already running process. This eliminates the substantial overhead of PHP's traditional request lifecycle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1:8080&lt;/span&gt;
  &lt;span class="na"&gt;ssl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;:443&lt;/span&gt;
    &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;acme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;certs_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;certs&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user@site.com&lt;/span&gt;
      &lt;span class="na"&gt;domains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;site.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Read more&lt;/strong&gt; about the HTTP plugin configuration in the &lt;a href="https://docs.roadrunner.dev/docs/http/http" rel="noopener noreferrer"&gt;official RoadRunner documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  📬 Jobs (Queues) plugin
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bwyolw83imvmxy9mplo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bwyolw83imvmxy9mplo.png" alt="RoadRunner Jobs plugin" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It provides an efficient queue system where:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queue brokers like RabbitMQ or Kafka handle the message transport&lt;/li&gt;
&lt;li&gt;Messages can be consumed by services written in any language&lt;/li&gt;
&lt;li&gt;All connections and heavy lifting are handled by Go, not PHP&lt;/li&gt;
&lt;li&gt;You don't need to install and configure PHP extensions like php-amqp, or any other broker-specific extensions since all communication with queue brokers happens on the RR side&lt;/li&gt;
&lt;li&gt;If you need to switch queue drivers (for example, RabbitMQ or Kafka), you only need to change RoadRunner's configuration. Your PHP code remains exactly the same, with no changes required. This level of abstraction provides flexibility and future-proofing for your application architecture.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;consume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;emails"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;emails&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;addrs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis:6379"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;prefetch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;memory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Read more&lt;/strong&gt; about plugin capabilities in the &lt;a href="https://docs.roadrunner.dev/docs/queues-and-jobs/overview-queues" rel="noopener noreferrer"&gt;RoadRunner Jobs documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🌐 gRPC Support
&lt;/h3&gt;

&lt;p&gt;In a distributed system, service communication is critical. REST APIs work well for many use cases, but gRPC offers some additional benefits for service-to-service communication:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A native Go implementation with excellent performance characteristics&lt;/li&gt;
&lt;li&gt;Strong contracts through Protocol Buffer service definitions&lt;/li&gt;
&lt;li&gt;Automatic code generation for type-safe client/server communication&lt;/li&gt;
&lt;li&gt;Efficient communication through binary protocol
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;grpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tcp://127.0.0.1:9001"&lt;/span&gt;
  &lt;span class="na"&gt;proto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users.proto"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders.proto"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows you to write your service definitions in proto files and implement the services directly in your application, with all the heavy lifting handled by Go. More importantly, other services written in different languages can communicate with your application using the same protocol and contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="c1"&gt;// users.proto&lt;/span&gt;
&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;UserService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;GetUser&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetUserRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;CreateUser&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateUserRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;GetUserRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;CreateUserRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Read more&lt;/strong&gt; about implementing gRPC services with RoadRunner in the &lt;a href="https://docs.roadrunner.dev/docs/plugins/grpc" rel="noopener noreferrer"&gt;gRPC plugin documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ⏱️ Temporal workflow engine
&lt;/h3&gt;

&lt;p&gt;This is perhaps one of the most powerful yet less known plugins, and it's a game-changer for building distributed systems.&lt;/p&gt;

&lt;p&gt;Temporal is an enterprise-grade workflow orchestration engine that addresses some of the complex challenges in distributed systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Durable Execution&lt;/strong&gt;: Workflows can run for days, weeks, or even months with automatic resumability after failures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distributed Transactions&lt;/strong&gt;: Coordinate actions across multiple services without traditional 2-phase commits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business Process Modeling&lt;/strong&gt;: Express complex business processes directly in code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durability&lt;/strong&gt;: Workflows can survive application crashes, server reboots, and even network partitions, resuming exactly where they left off&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distributed Execution&lt;/strong&gt;: Activities can be executed across multiple services and servers while maintaining workflow consistency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saga Pattern Support&lt;/strong&gt;: Built-in compensation mechanisms for distributed transactions that need to be rolled back&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sophisticated Retry Policies&lt;/strong&gt;: Configurable retry strategies with exponential backoff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Language Support&lt;/strong&gt;: Workflows and activities can be written in multiple languages (PHP, Go, Java, TypeScript), allowing different services to participate in the same workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioning&lt;/strong&gt;: Support for workflow code versioning, ensuring that running workflows are not broken by code changes
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;temporal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1:7233&lt;/span&gt;
  &lt;span class="na"&gt;activities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;num_workers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a basic example of what a Temporal workflow might look like in your Laravel application. This workflow handles the entire lifecycle of a user subscription, from welcome email to trial period management to recurring monthly billing - all in one coherent piece of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[\Temporal\Workflow\WorkflowInterface]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SubscriptionWorkflow&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$account&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Workflow&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;newActivityStub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;AccountActivityInterface&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;ActivityOptions&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withScheduleToCloseTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DateInterval&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;createFromDateString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2 seconds'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="na"&gt;#[\Temporal\Workflow\WorkflowMethod]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$userID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$trialPeriod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Lower period duration to observe workflow behavior&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Workflow&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DateInterval&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;createFromDateString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'30 days'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$trialPeriod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sendEndOfTrialEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nv"&gt;$trialPeriod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;chargeMonthlyFee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sendMonthlyChargeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CanceledFailure&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Workflow&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;asyncDetached&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;processSubscriptionCancellation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sendSorryToSeeYouGoEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;More workflow examples you can find &lt;a href="https://github.com/temporalio/samples-php/tree/master/app/src" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Stop at this point and look at the code again. Notice what's missing? This workflow doesn't need to store state between executions in a database. There are no cron jobs checking for expired trials. All the logic is coupled in a single, easy-to-read class that serves as a clear starting point.&lt;/p&gt;

&lt;p&gt;Now, think about how you would implement this same subscription process without Temporal:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You'd likely need:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A database table to track subscription states&lt;/li&gt;
&lt;li&gt;A cron job running every day to check for trial expirations&lt;/li&gt;
&lt;li&gt;Another cron job for monthly billing&lt;/li&gt;
&lt;li&gt;Transaction handling to ensure billing and emails stay in sync&lt;/li&gt;
&lt;li&gt;Error recovery code for failed charges&lt;/li&gt;
&lt;li&gt;Complex restart logic if your server goes down during processing&lt;/li&gt;
&lt;li&gt;Logging infrastructure to track the subscription lifecycle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Temporal, all of this infrastructure complexity simply disappears. The workflow will survive server restarts, application deployments, and even service failures. The Temporal server manages all the complexity of distributed execution while allowing you to write straightforward, procedural-looking code.&lt;/p&gt;

&lt;p&gt;The Temporal UI gives you a visual representation of your workflow's execution history:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Freczxl7zllee8eqysglz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Freczxl7zllee8eqysglz.png" alt="Temporal observability" width="799" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this screenshot you can see the execution history of a subscription workflow over time. Each bar represents a 30-day cycle where the workflow was paused, and you can see events like "trialFinished" and "withdraw" (monthly billing) occurring exactly when scheduled.&lt;/p&gt;

&lt;p&gt;Unlike traditional debugging where you're piecing together what happened from logs scattered across multiple systems, Temporal gives you a complete, visual timeline of your workflow's execution - even for processes that span months or years.&lt;/p&gt;

&lt;p&gt;For PHP developers building distributed systems, this is powerful. It allows you to express complex multi-service workflows in simple PHP code while leveraging the full power of distributed systems architecture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Learn more&lt;/strong&gt; about Temporal in the &lt;a href="https://temporal.io/" rel="noopener noreferrer"&gt;official Temporal documentation&lt;/a&gt; and specifically for PHP in the &lt;a href="https://docs.temporal.io/develop/php" rel="noopener noreferrer"&gt;Temporal PHP SDK documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🔑 Key-Value (Cache broker)
&lt;/h3&gt;

&lt;p&gt;The Key-Value plugin provides a PSR-16 compatible caching layer that works with Redis, Memcached, and in-memory storage. This gives you a high-performance caching solution managed by Go rather than PHP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;kv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;in-memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;memory&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;addrs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost:6379"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  📡 Centrifuge - Enterprise-grade real-time communication
&lt;/h3&gt;

&lt;p&gt;For real-time applications, the Centrifuge plugin integrates with the Centrifugo websocket server, enabling scalable real-time communication between client and server.&lt;/p&gt;

&lt;p&gt;This integration exemplifies RR's core philosophy: rather than reinventing the wheel, RR provides seamless integrations with battle-tested, production-ready third-party systems. Centrifugo is exactly such a solution - a high-performance, language-agnostic real-time messaging server with a large community and production deployments across various industries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It gives you:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scalable websocket connections (supporting millions of simultaneous connections)&lt;/li&gt;
&lt;li&gt;Pub/sub messaging with flexible channel configurations&lt;/li&gt;
&lt;li&gt;Client connection authentication and authorization&lt;/li&gt;
&lt;li&gt;Automatic reconnection handling with message recovery&lt;/li&gt;
&lt;li&gt;Cross-platform client libraries (JavaScript, iOS, Android, etc.)&lt;/li&gt;
&lt;li&gt;Horizontal scaling capabilities for large deployments
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;centrifuge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;proxy_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tcp://127.0.0.1:30000"&lt;/span&gt;
  &lt;span class="na"&gt;grpc_api_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp://127.0.0.1:30000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Learn more&lt;/strong&gt; about Centrifugo and its capabilities at the &lt;a href="https://centrifugal.dev/" rel="noopener noreferrer"&gt;Centrifugo documentation&lt;/a&gt; and about implementing services with RoadRunner in the &lt;a href="https://docs.roadrunner.dev/docs/plugins/centrifuge" rel="noopener noreferrer"&gt;Centrifugo plugin documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  📈 Metrics (Prometheus) - Industry-standard observability
&lt;/h3&gt;

&lt;p&gt;It allows you to monitor your application performance by collecting metrics that can be visualized in Grafana.&lt;/p&gt;

&lt;p&gt;As with all RR plugins, it integrates with Prometheus - the de facto industry standard for metrics collection and monitoring in cloud-native applications. This integration gives you immediate access to a mature ecosystem of tools and practices that DevOps teams already know and trust.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collect application-specific metrics (registered users, transactions, etc.)&lt;/li&gt;
&lt;li&gt;Monitor system performance (memory usage, request latency, queue depths)&lt;/li&gt;
&lt;li&gt;Create custom Grafana dashboards with real-time visualization&lt;/li&gt;
&lt;li&gt;Set up alerts based on metric thresholds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach demonstrates RR's consistent philosophy: leverage proven, widely-adopted industry solutions. By integrating with the Prometheus ecosystem, RR ensures your Laravel application can be monitored using the same tools and practices employed by companies like Google, Digital Ocean, and Shopify.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost:2112&lt;/span&gt;
  &lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;registered_users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;counter&lt;/span&gt;
      &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;registered&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;users."&lt;/span&gt;
    &lt;span class="na"&gt;money_earned&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
      &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;earned&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;money."&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Read more&lt;/strong&gt; about metrics collection in the &lt;a href="https://docs.roadrunner.dev/docs/logging-and-observability/metrics" rel="noopener noreferrer"&gt;RoadRunner Metrics documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🚢 Kubernetes (k8s) and container orchestration
&lt;/h3&gt;

&lt;p&gt;RR is designed from the ground up to be Kubernetes-ready, with features specifically tailored for modern container orchestration:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Health Checks&lt;/strong&gt;: RR's Status plugin provides a simple HTTP endpoint for monitoring the health of RR workers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1:2114&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These endpoints can be used directly as k8s liveness and readiness probes, allowing the orchestration platform to automatically restart unhealthy pods, ensure proper service discovery, and manage rolling updates safely. This built-in health monitoring brings enterprise-grade reliability to your PHP applications with minimal configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Graceful Shutdown&lt;/strong&gt;: During deployments and scaling events, RR implements graceful termination. When a pod receives a termination signal, RR won't immediately kill the process. Instead, it enters a grace period where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It stops accepting new jobs/requests&lt;/li&gt;
&lt;li&gt;It allows all current jobs and requests to complete processing&lt;/li&gt;
&lt;li&gt;Only after all work is finished does it terminate the process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents work from being abruptly interrupted during deployments, avoiding data loss or inconsistent state. For long-running jobs, this behavior is particularly valuable as it ensures that critical tasks are completed even during infrastructure changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Management&lt;/strong&gt;: RR includes sophisticated memory monitoring and management capabilities. You can configure memory limits per worker, and if a worker exceeds its allocated memory threshold, RR will intelligently restart that worker after a graceful shutdown period. This helps prevent memory leaks from impacting your entire application and maintains system stability without administrator intervention. For PHP applications, which can sometimes experience memory growth over time, this automatic worker recycling ensures optimal performance with minimal operational overhead.&lt;/p&gt;

&lt;p&gt;These capabilities make RR an excellent choice for modern cloud-native applications, providing the reliability and operational features expected in production k8s environments without requiring additional sidecar containers or complex configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing the Laravel Bridge for RR
&lt;/h2&gt;

&lt;p&gt;After working extensively with RR, I decided to build a comprehensive integration for Laravel that extends beyond what Octane offers. The result is the Laravel Bridge package, which exposes the full power of RR's plugin ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key difference is in the approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Full Plugin Support&lt;/strong&gt;: Bridge enables access to the complete RR ecosystem: Jobs, gRPC, Temporal, Key-Value, and more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Worker Types&lt;/strong&gt;: It allows you to configure and run multiple workers for different RR plugins simultaneously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Octane Compatibility&lt;/strong&gt;: It reuses Octane's SDK for clearing application state, ensuring compatibility with all Octane-compatible third-party packages.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Installation and basic setup
&lt;/h3&gt;

&lt;p&gt;Getting started with the Bridge is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require roadrunner-php/laravel-bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Get started&lt;/strong&gt; with the Bridge by visiting the &lt;a href="https://github.com/roadrunner-php/laravel-bridge" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, you'll need to create a &lt;code&gt;.rr.yaml&lt;/code&gt; configuration file in your project root. This configuration file is the central control point where you enable or disable RR plugins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's an example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2.7'&lt;/span&gt;

&lt;span class="na"&gt;rpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp://127.0.0.1:6001&lt;/span&gt;

&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;php&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;vendor/bin/rr-worker&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;start'&lt;/span&gt;
  &lt;span class="na"&gt;relay&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pipes&lt;/span&gt;

&lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0:8080&lt;/span&gt;
  &lt;span class="na"&gt;middleware&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;static"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gzip"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;static&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public"&lt;/span&gt;
    &lt;span class="na"&gt;forbid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.php"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.htaccess"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Jobs plugin for distributed queues&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;consume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notifications"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;notifications&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;memory&lt;/span&gt;
    &lt;span class="na"&gt;orders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kafka&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# gRPC plugin for service communication&lt;/span&gt;
&lt;span class="na"&gt;grpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp://0.0.0.0:9001&lt;/span&gt;
  &lt;span class="na"&gt;proto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proto/users.proto"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proto/orders.proto"&lt;/span&gt;

&lt;span class="c1"&gt;# Temporal plugin for workflow orchestration&lt;/span&gt;
&lt;span class="na"&gt;temporal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;temporal:7233&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you've installed the package and configured RR, you can start your application with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./rr serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Learn more&lt;/strong&gt; about configuration options in the &lt;a href="https://docs.roadrunner.dev/docs/general/config" rel="noopener noreferrer"&gt;RoadRunner Configuration documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RR creates a worker pool by executing the command specified in the server configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;php&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;vendor/bin/rr-worker&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;start'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3eq2yu2gji5b78rit4g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3eq2yu2gji5b78rit4g.png" alt="Worker selection" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When RR creates a worker pool for a specific plugin, it exposes the &lt;code&gt;RR_MODE&lt;/code&gt; environment variable to indicate which plugin is being used. The Laravel Bridge checks this variable to determine which Worker class should handle the request based on your configuration.&lt;/p&gt;

&lt;p&gt;The selected worker then listens for requests from the RR server and handles them using the Octane SDK, which clears the application state after each task (request, command, etc.). This ensures that your application remains in a clean state between requests, preventing memory leaks and state pollution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What makes RR truly exceptional is that it lets PHP developers access first-class tools that were previously challenging to integrate: Temporal for durable workflows, Centrifugo for real-time communication, gRPC for efficient service communication, and many more. You get all of these capabilities while maintaining the Laravel development experience you're familiar with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3xgl51pwz6jqzbhbz2yo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3xgl51pwz6jqzbhbz2yo.png" alt=" " width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whether you're scaling an existing application or building something new, RR gives you enterprise-grade tools with PHP-level accessibility. It's not about replacing Laravel—it's about supercharging it to thrive in today's complex application landscape.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>temporal</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
