In the 1990s there was a company called Be. Be created an operating system called BeOS and tried to compete against Macintosh and Microsoft. Obviously this didn’t work–but we got a very clean design for a desktop system out of it. That heritage continues as Haiku.

The Slow Lane#

Despite being written in and for C++, Be is basically running the Smalltalk object model. Objects send messages to handlers. Handlers then take them in to their mailbox in some suitable fashion and process them as they can. It’s a very standard model that SDL and Allegro have also used for games since the 2000’s.

Message passing does not used fixed schemas like the game engines or Smalltalk dispatches do. In 2026 terms its more like using JSON-RPC internally, everywhere.

The upside is that this is extremely clean and consistent. Remote messaging (either across processors, the local network, the whole internet) falls in to the same neat pattern. Even adding scripts (where users can write their own ways to automate tasks) forms a pretty simple “throw messages over there” pattern.

So how does it work?

Boxes#

Typed boxes store a type code, the size of the box, and the box contents as raw bytes. This is a Type-Length-Value (or “TLV” encoding)1.

Be & Haiku reserve a handful of these codes for itself for very common types: integers, strings, lists, dictionaries, messages stuffed inside of messages. Then certain ID ranges are set aside for applications to interpret however2 it wishes.

Boxing is the process of writing out some value to one of these boxes. Unboxing is something you do to make internet videos when you’re out of ideas the opposite:

  • Read the type code,
  • Dispatch to a reader,
  • Decode the value,
  • Return it

It’s a very lightly structured serialization: Be owns the type code and length and you own whatever bytes are inside the fence.

Messages#

Messages are a bag of named slots. Each slot is a box. Parameters for the message are packed up in to boxes and assigned a named slot.

There is also a stack of selectors for specifying what the intent of the messages is and for whom. This handles sending “check milk level” to a fridge–if you sent it to a fridge. But if you sent it to the kitchen it would not understand that. With the selector stack you can state “for the refrigerator” followed by “check milk level.”

Handlers will look at selectors to decide if they can handle them or route them somewhere else. Its the intended mechanic for flinging messages at specific peices of a system you are trying to talk to. You don’t send directly to the component from the outside–you just ask the app to figure it out.

Handlers#

The last part of Be’s event infrastructure are the handlers. Handlers abstract talking to another job running at the same time as you, a job waiting for its turn, and a job running on another computer entirely3. You simply send messages at a handler and don’t worry about it much. They’ll tell you when they’re done.

The Fast Lanes#

Message passing isn’t fast by machine standards. Creating a message involves writing down all the field names and values just to hand them off. If you ask for the temperature of a fridge–you get a whole self-describing token about it. That’s great for future proofing and debugging and slow in a control loop trying to tightly regulate the fridge’s temperature.

Mouse and keyboard input falls in to that. They happen very frequently, interactively, carry a relatively fixed amount of narrow state and need to be replied to very fast. For this Be actually doesn’t do anything all that elegant.

You just fall back to the standard “postbox” format–same as the game engines.

How does that work?

Very similar, but a lot more hard-coded.

Events#

Events tend to get a unique type code (like how boxes do.) This is still just a number4 because speed is essential. Handlers are often just running “is the code in this range -> jump directly to this indexed offset” because its very fast.

This pre-arranged layout is called a schema. Everyone has to agree to use the same one or it doesn’t work. The flexibility of before is lost.

Event sources#

Sources are things which send events, just like you would send a message. There isn’t much special about these. They simply have a list of sinks that would like to know about any events they happen to generate.

Sources send a single type of event. You “subscribe” to the ones you want to hear about.

Event sinks#

Sinks are where events actually end up. When a source has something to say it sends a copy of the event to each sink. Messages pile up in the sinks until its that module’s turn to peek at the mailbox and start pulling events.

The fast lane tends to be designed around machine level concerns. They might use bump allocators5 to manage the mailbox’s storage. There is probably some fixed maximum size of a mailbox6 set to “hopefully good enough.”

Postscript#

Be & Haiku show that only a small amount of very important messages need a rigid, real-time delivery model. The rest can use a loose form of messaging7 and everything will generally work out.


  1. TLV encodings come in and out of popularity. They provide forward-compatibility by having a structured way to add new data types without breaking old documents. People celebrate this property–then lament that you pay for it when decoding. Then they swear off the encoding for something more lean and efficient. Inevitably they just reinvent TLV encodings again–like Google’s Protocol Buffers and CBOR. For some reason people actually listened this time and let machines generate the code for Protocol Buffers after ignoring that with ASN.1’s BER. ↩︎

  2. Abstract Syntax Notation One (ASN.1) is an attempt at a universal data serialization standard from the 1970s. It had a lot of growing pains but got the problem essentially correct. Like Ada a lot of projects are just reinventing small slices instead of just going for the spec that already did it. ASN.1 had the notion of “protocol” and “private” type codes. Protocol codes are meant to be declared by the spec for the system exchanging messages. Private codes are a no man’s land where you can do whatever you like. This often ends up reinvented as “all numbers below N belong to us.” ↩︎

  3. Remote messaging to Be/Haiku machines uses the same machinery as talking to something on your desktop. You just throw a message and let the framework figure out if its going across the network or not. Applications don’t know the difference unless they want to. ↩︎

  4. CLAP, a music plugin standard, uses strings for versioning access to capabilities. Nostr, a messaging framework, should have used them for this too. In CLAP you ask a factory for “some.plugin/V32” and the plugin is able to determine if it can acquiese. ↩︎

  5. “Arena allocation” is where you have a big box to store things in. The box has a needle that moves forward whenever putting things in to the box. You can move the needle forward (“allocating” space in the box) or backward (“freeing” the space) but you can never have holes. Its highly useful and efficient for “scratch space” where you’re going to throw the whole intermediary steps out anyway. ↩︎

  6. Real-time computing tends to pick limitations. If you always have a set seat at a table then even if you don’t fill the whole space with dinner–you still have somewhere to eat. It makes less efficient use of the table, but it’s fast and reliable↩︎

  7. OS X was heavily written around the Objective-C programming language. As a weird hybrid of C and Smalltalk, the fast and slow dispatch lanes worked much the same. Though the actual way they wrote it all is not as elegant. ↩︎