Showing posts with label mosdca. Show all posts
Showing posts with label mosdca. Show all posts

Saturday, February 28, 2026

Half-MAD: A Shorter-Term Vision

 I think that one of the reasons why it has been difficult to enunciate exactly what it is I want project MAD to accomplish, is because to an alarming extent, I want to do things that we are not doing in a modern machine, but extended to encompass all of several machines at once.  Thus to my chagrin, I end up effectively making a list of demands that might be considered absurd if they were to happen in a contained, well-understood environment, and then adding “…to everything, everywhere” at the end of the list.  And to be clear, Computer Science as a discipline is well versed in problems that do not scale well.  Proposing something difficult and then demanding that it scale is, itself, extremely arrogant.

The only reason why it doesn't approach being farcical is that I am proposing each sub-project with methods already in mind, if not in hand.  Ultimately, when it comes to actually implementing the ideas, new problems will crop up that we have never experienced before.  But to better understand why those projects are worth the problems they cause, it's worth discussing some of them in a smaller, more general context.

What is a Device and What Do They Do?

I talk a great deal about how Project MAD is designed to let you access devices or resources or capabilities of multiple pieces of hardware.  This is a strange topic for an application programmer; if you haven't already delved into microcontrollers and peripheral interfaces, you may wonder why anyone would care about all the devices attached to a system anyway.  When all you are doing is building some random bit of application logic and wrapping a user interface around it, using fixed and well-defined OS concepts or third party libraries, well, if you end up needing to talk about drivers for literally any device then someone has done their job wrong.  And indeed, if I wanted the average, user-facing application developer's life to change, I would probably be making their life worse, not better.  Part of the design of MAD/ADA is that, in fact, they don't need to worry about devices.

But also, devices will absolutely be used to control how their application works and how it is distributed across the system.  That's all fine, assuming they don't have to worry about it - but of course, eventually, a professional developer will run into every bug that comes within a mile of their code, let alone the ones actually inside it.  So if they do, technically, need to worry about devices, it's worth asking: what are they, why do we care, and what do the changes I'm making actually do for us?

Obviously, at its simplest, a “device” either changes the world outside the computer, or produces data based on the world outside the computer, or else it makes some data-to-data transformation task easier.  Those are, basically, the only things a device could even possibly provide: output, input, or services.  If you want one of those three things, you are going to be touching a device, even if that's only with an OS-standard ten foot pole mounted to the nearest wall.

An application developer's life is usually pretty simple: if we need input, we take it, if we need to output, then we output, if we need some service, then we talk to it.  Usually, someone else simply handles these things, in fairly standard ways.

The trouble starts whenever the OS itself either doesn't care about a device, or makes faulty assumptions about how you want or need to use it.  Say you have a TV across the room and you want to cast video to it. Modern OSes tend to only do two things with monitors: extend the desktop onto it, or copy some other monitor's output onto it, and neither of those may serve your needs well.  That's why in 2013 Google put together a tiny little dongle with a surprisingly important feature: you could just send video to it.  It took input and produced output, entirely separate from the normal desktop (or phone) metaphor that we had become familiar with.  And while I won't say that has or hasn't changed the world, I for one am grateful for such a device.

You can understand devices like that with a simple, generic model.  They have some requirements (the Chromecast needs internet and video out), have some capabilities (it will display internet video streams on the TV), and have some way to control it (a remote, or network packets).  When I talk about devices, I pretty much mean anything that fits that, extremely broad definition of input, output, and control.

So it may surprise people who don't look at how OSes work that an average programmer probably doesn't know how to get a list of all devices present on a given machine, can't tell what a random device is capable of, and can't figure out how to control that device.

On all major operating systems today, the device model can generally be broken up into two categories: if the OS itself uses it, then the OS takes care of it and you live with the OS model for better and for worse.  Otherwise, it may be technically available but you'd better find a library or application that knows what it is and how to use it, because they ain't touchin' it.

And that's… that's kind of it.  There isn't a whole lot of nuance here.  Either the OS takes care of it, or you're on your own.  There are some cases that are better or worse, but broadly… yeah.

Why?  Well, that's not hard to understand.  Someone has to invent a way to deal with it.  If that someone works for Microsoft, then Windows can handle it; if they work for Apple, then Macs can handle it; if they enjoy open source projects, Linux can probably handle it.  If they are part of some other company, such as the manufacturer of some device, they will provide an application and/or library that works and consider their job done.

Unless the device is really, mind-bogglingly important (and/or you convince people that it is), nobody is going to reshape how the OS works in order to help your device fit into the ecosystem better.  And… it has been that way since the dawn of computing.  Generally speaking, everyone involved would rather make small changes than big ones, not least because if you make big changes, consumers may not like it and you may be out of business in anywhere from two months to five years.

Something like MAD's OS project doesn't necessarily need to reinvent the wheel so hard that it changes the way the world itself turns.  I may end up sounding like that's what I'm doing, because I want to paint a picture of what MAD is capable of, but you could just… add some of MAD's features to existing OSes, or as a third party structure on top of them.  To wit, devices.

But first: IPC.

What is IPC/RPC and Why Should I Care?

Inter-Process Communication and Remote Procedure Calls are both fancy ways of saying that two programs talk to one another.  Generally, IPC is happening among two programs on the same computer, and RPC is the same but going between machines; there are some differences there, including important ones, but let's set that aside for the moment and just talk about how computers work in general.

I said in the last segment that you can divide device support into “The OS Does It” and “You're on your own”.  Categorically, the same is true with talking to other programs.  If the OS does it (and by it, I mean, making some given program do something specific for you), then there is a function, one that probably works very similar to every other OS function that you have had to use while learning to program, which talks to that system program and then gets back to you.  If the OS doesn't care about that specific thing, at best you will find a library or programming language that makes the task of talking to some specific programs just as easy.  (There is an aside here for scripting, but that requires a whole ecosystem to set up and so, like OSes themselves, can be difficult to make pivot if a change is necessary)

However, it is at least as likely, if not more, that you will have to do a whole lot of research into how that specific program works and how developing IPC/RPC mechanisms work in general and then you do a whole lot of work to make the mechanism talk to the specific program, and oftentimes, talk to a specific version of that specific program, because that might change.  And this is why we have kind of standardized on IP and web services nowadays - everywhere you see them pop up, there are a whole lot of tools that almost everyone involved did not need to invent.  Reinvent, sometimes, but not from scratch.

IPC/RPC mechanisms, however, do still need to be made.  If you have a program, say, one that takes your company's new fancy keyboard with per-key LED lights, and exposes the light-output capabilities of that keyboard to the user (letting them make all the colors their own brand of pretty), you will probably not design a server into that program which lets other programs talk with it.  And why would you?  That sounds like a lot of work!

But look at it from another app developer's standpoint - if they want to make a key flash when a notification comes in, say, they have three general options: learn how the keyboard driver works (which may be, if I may summarize, very custom), interact with the manufacturer's application (which we just ruled out), or interact with some third party library which can do one of the other two options for them.  If nobody has or wants to make that third party library, they're also out of luck.

MAD takes a tact that may be very alarming, which is that all programs should by default have some IPC mechanisms - it's part of making them distributable across multiple pieces of hardware, after all.  But you could also… you know, just, use that same mechanism to expose some functionality.  That exposure doesn't need to be reckless; can maintain some controls, make sure the user is involved, something like that.  But if we decided that IPC/RPC should be easy, maybe we should expect you to interact with other applications more often.

And the point of deciding that it should be easy, is that having made that proclamation, there ought to be a very standard way to do RPC, such that it is just as easy to work with as when the system takes over handling that task for you, because you're talking to a system component and they know how to do that.  If we want programmers to take it at all seriously, you have to make it easier for programs to interact fairly regularly.

And if you're going to interact more often, it might be best to have standards for communication.  And if you wanted, perhaps, to make that OS/DIY dichotomy even less severe… then OS IPC should work in the same way general application IPC works, adhering to the same standards.  That way, people who are forced to learn OS methods when they start programming, don't need to learn a whole new separate thing when they start dealing with new and more interesting tasks.

Which brings us back to devices, or rather, device drivers.

Making Devices Accessible: The System API Directory

Again, this is all stuff I've talked about before, but let's dial it back.  You have devices on your machine.  How do you interact with them?

Well, generally, every device you'd like to talk to exists on the far end of some kind of connector, and what is exposed at the very bottom of the CPU/Kernel system is the near end of that connector, not the device itself.  Many of these connectors require a little bit of finesse to use properly, so there may be a relatively complex driver just to ensure that any message you send across that bus gets where it's going, meaning you will be using a slightly higher level API that accounts for the bus itself.  Only once you are in contact with that other device can you talk about, you know, talking to the device itself.  Now, modern CPUs aren't just big-banging busses (meaning the CPU itself isn't hot-looping to make sure that every bit gets sent at the correct time, or anything similar); they use support chips, microcode, and other conveniences that might be relatively specific to the processor/motherboard combo you are using, but there are standards and firmware to sort of even out the complexities of making that part of the system work.

Once you can talk across a bus to a device, you have to know what language it speaks.  If you know exactly what device you're talking to, this shouldn't be hard - the manufacturer should list all the commands, what they do, what the return data means, and so on.  A few manufacturers prefer to keep some secrets… but let's not get into that.  Some things that you will be interacting with on the other side of a bus are themselves programmable, and you may be sending code to them (which is a whole other thing that is only partly covered by MAD/ADA), but frequently, you are just issuing commands that make it do whatever it is you want to do, and listening to the bus for both expected replies and updates caused by an event.

The software which converts requests from a programmer to commands that arrive at the device, and responses from the device to normal software events and function returns, can be understood as drivers.  This is where the OS/DIY dichotomy comes into play: the OS will keep track of devices it cares about, and ensure that there are very standard ways to interact with drivers it cares about.  Devices that the OS doesn't care about are not well kept track of, and the ways to interact with them are not terribly standard - at least, not outside of the specifications of whatever bus they are on the other side of.

The MAD/SAD proposes to tackle these two problems at the same time.  It asks device drivers to expose a standard API and an implementation library; programs that utilize the API can link to that library and they will be interacting with the device itself.  Then, all device drivers are listed in a filesystem directory, indexed by a key representing the API, and linking to that library.  If more than one device implements an API, even if they use the same library to do so, the implementations are listed separately and configured differently, so when your app links to a given device's library, you will be talking to that specific device when you make API calls to it.

That on its own represents a massive difference to how libraries and devices exist in any modern operating system.  Today, libraries are just things that may or may not exist, and devices are just things that may or may not exist.  You can't just link to a library and be talking to a device - you link to a library and then it will let you ask the system if any devices exist, because the library itself isn't configured, but is instead generic.  Likewise, you can't just point to a device and get a library that interacts with it.

When I talk about “You can just add shit from MAD”, I'm talking about shit like this.

But I go a little further with MAD/SAD.  I mentioned the libraries are indexed by an API key (in the filesystem, but you can ignore that for the moment), and if you've read any of my articles, you know that the API keys are in a tree, corresponding to an inheritance model.  And since I'm trying to explain all this simply, let's touch on that for a moment.

The API itself promises that the library attached to it will have certain functions; if you call those functions, you will get certain results, and the API makes all that very clear.  That doesn't mean that those are the only functions in the library - it's only a promise that if you go looking for certain ones, you will find them.  So naturally, a library may implement more than one API, each time exposing different functions.

Likewise, an API may provide different levels of what amounts to the same API.  It may provide a dirt-simple one, and a slightly more complex one, and then a far more complex one.  Generally, the simplest one gives you the fewest ways to customize it, but it also means you need to know the least about what kind of specific device you're using; the most complex one requires you to know exactly what the device is, but may essentially give you direct access to everything the device is capable of.  Since you choose the API when you write the program, you get to decide how specific the API you're targeting is.

Thus, in the MAD/SAD API tree, the folders closest to the bottom of the tree contain very simple APIs that require you to know very little about the devices, and which apply to far more devices, but which also expose very few features - and then there are more complex APIs that require you to know what you're asking for, but give you more control, until eventually you get to a library which may provide you direct access to the device with no hand holding whatsoever.

But in an inheritance model, you don't necessarily hide the API that came before - the parent and child APIs coexist, and the child API is obliged to expose every function the parent does.

Suppose you have a device that acts as a source of character data - that may be, for example, a keyboard, but it also describes files, network streams, and some busses like serial ports.  In any of those cases, the API will provide you with a generic “read” function that provides you with a “firehose” of data sent directly from the device, and probably also a generic “write” function to shout character data back down the pipe to be heard at the other end.  Questions like “What does the data mean?” and “What should I say to this device?” are not answered at all by this level of API.  If you use that level of API, you will not know what device is on the other end, or if there even is a device at the other end, not when you are writing the program itself - the user may know, but that's not your business.  Maybe the ‘device’ is not even hardware; maybe it's software, or maybe it's a black hole that says nothing and eats all input (aka /dev/null).

That same low-level API can be more useful to a programmer that knows what the device is, but when you know what device it is, you can usually also provide better service than just “read” and “write” functions.  Not always - some things really are that dumb and simple - but usually, you can provide an extension to that character device which provides new functionality.  Under MAD/SAD, that extended API will only be presented if the device promises that the library, and the device, implement those functions.  It will also still be listed among the low-level, raw character device drivers, because the extension adds to the lower level, instead of replacing it.

Suppose the character device in question your fancy backlit keyboard, for example; it may have three or four levels of API just in the ‘character device’ tree, to say nothing of (eg) a ‘raw usb device’ tree.  First, your keyboard is a generic character device; second, it is a keyboard (the output should be understood as keys, a keymap may be involved, and you can send commands to turn on capslock and so on); third, it may implement some standard “backlit keyboard” API, if there is one, and fourth, there may be a vendor-specific “This is our backlit keyboard” API that has some juicy extras the generic API does not.  This last API extends the other three; the third API extends the first two; the Keyboard API extends the character one.  But if you really want, you can just open the keyboard as a character device and listen to every keystroke that comes in down the line, and shout random character strings down the line just to see what happens.

Of course, the OS will recognize keyboards as something special, because the OS knows that most apps will care about keyboard input in one way or another - so it will make use of that second level of API (char/keyboard) automatically.  In fact it will probably create a virtual keyboard device that collects input from all keyboards (which should be a standard itself) and uses that instead of any specific device, because then the user doesn't need to change how anything is configured when they plug a USB keyboard into their laptop.  But the real point is, we have made the generic way to reach devices good enough that the OS can just use this tree to do what the system normally does, instead of making very unique and specific ways for the system to make critically important devices accessible, ways that kind of clash with how we program other applications.

Sounds nice, right?  Maybe a little boring.  But the way I introduced this topic was about APIs and libraries - it has nothing specific about devices in it.  Any persistent application that wants to provide an API and library can do so, even if it's not a driver or even a background service - and that application's APIs will be listed in the directory, same as any other.  So… if you install a new database service, say, you will be able to find it in the list of all databases on the system, even if that database is a foreground application for some reason.

Now, here's a funny little twist (which I've already talked about in another post): when you use IP and ports to connect to running applications and services, you normally locate services by a standard port number, because that's how it was when the internet was created.  Since more than one service can't share that port, other copies of the same have to have other ports; there isn't a standard for how to do that, so the user or service has to figure something else out.  You may be able to examine the other server processes and find out what ports they have open, or examine all opened ports to see if they sound like that service, but neither of those is a smooth, well-defined operation.  Under MAD/SAD however, if you know what API the service provides, you have a list of all of them that are running, and each item in that list is preconfigured to let you talk to it.

Moreover, you don't necessarily need to publish to the SAD globally.  Remember, I said the API keys are published in the filesystem; you might publish such a configured library file privately, if you had, say, an embedded database in a larger application, and you only wanted the parent application to talk to it.  Linking to that preconfigured library would connect you to the database; it need not be exposed to the network, or configured to deal with the network.  Presumably, an administrator or user could find it if they knew where to look, but that's about all the exposure it would have

The same mechanism that helps you find and utilize devices also helps you utilize things that are not listed?  Wow, it's almost as though it's just a better way to utilize things.  But what exactly is this mechanism?  I took it for granted that you can dynamically link to a library and it will be preconfigured to talk with a specific device.  That is… also not how things work.

Perhaps more to the point: Just before this I was talking about IPC/RPC and I never got back to it.  Why did I use that segment to introduce this one?  We already have dynamically linked libraries, even if they aren't preconfigured.  Making preconfigured libraries would be a fairly straightforward thing that has nothing to do with IPC/RPC mechanisms.

Well… the point with IPCs was that MAD argues every running application should expose IPC endpoints as default under MAD, meaning that if you write a service or application, you can interact with that application via another application directly.  Of course, if you want to interact with something via some IPC/RPC mechanism… you really should have standards.  Like an API.

What's the difference between calling a function embedded in a linkable library, and calling a function via an IPC mechanism directly?  Well… the linking bit, I suppose.  And it matters exactly what application and user is “running” the code, as far as the system is concerned.  But if the library mostly exists to provide a translation layer, between the published API and IPC endpoints exposed by a service… what if there just wasn't a need for a translation layer?  Or rather, what if the “translation layer” was a bog-standard system call?

What if your client application, the one that wants to use the API, can't tell the difference between linking to a library and directly talking via IPC with a service?  What if the programmer involved need never care?  What if they simply… use the API?  What if they do so by using a system call that does whatever is needed to reach the target function?

Bringing It All Together (By Taking It Back Apart)

Now… Does this all sound like hyperbole?  Does it sound like I think this is all easy?  Because this isn't all easy.  Let's look back at a list of things described in this chain of logic that are new:

  • Have all applications use detailed IPC hooks by default
  • System call that translates API call into IPC to a running service
  • Configuring linked libraries at runtime so that they point to a specific device or service, and using such libraries
  • System call that translates API call to loading and running a configured, dynamically linked library
  • The two aforementioned system calls are the same system call
  • Listing APIs in a systemwide tree
  • That systemwide tree is in the filesystem
  • That tree is an interface-inheritance tree (or some other semantic structure)
  • Allowing the API system call to be directed towards libraries that are not in the system tree
  • The system uses the API system call for basic functions (where feasible)

Now, we could quibble, and maybe I've missed some small points, but if I have an idea that requires making ten changes to the way operating systems work, some of them pretty freaking fundamental, then you certainly can't take all of those changes and tack on to the end,

“Now do this for a dozen systems linked together into a gestalt system.”

Yeah, that sounds a little crazy, huh?  About that.

Anything, Anyplace, All at Once

Funny thing about those changes.  One of the key themes is that it lets you address and control devices and services as though they were files.  (In the case of applications, though I've not talked about it here, there's also a big portability thing so that you can run them from anywhere, but same basic idea - an application being accessible means it being runnable when all you have is the “file”.  See MAD/SAD AF)

The point of them being accessible as files was that it's not hard to have a filesystem that represents multiple machines at once - you basically just make a folder with multiple machines in it, and list out the contents of each machine under them.  That's literally all it takes; you can list the entire filesystem, or just what you think is important, or both in different places.  Sticking trees on top of trees… is just how trees work.

The question becomes: what's the best way to make use of that?  UNIX already has the general philosophy of “everything is a file”, so in theory, you could make a distributed system where they simply… all use a distributed filesystem listing all files everywhere all at once.  But first, modern machines don't actually stick to “everything is a file,” and second, sometimes interacting with things at the file level is very… back and forth.  Which is something you may not want to do over a network, where every back and forth has significantly longer latency than a local call.  Like… thousands of times longer, maybe.  And also, you would need some universal addressing schema for this multi-machine directory so that path strings work the same everywhere and are unique.

But anyway, so you want to do some things locally, to avoid thrashing the network.  That means running a program on the machine that hosts some specific device.  How?

We already have some technologies to do this, and the one that most people will be familiar with is the “serverless” cloud infrastructure made famous by Amazon Elastic Compute Cloud (EC2).  The point of the serverless model is that a program can be loaded on any machine that has room, and you just keep track of where it actually is loaded, so that whenever someone goes looking for it, you can direct them.  If that program is part of a larger structure, have it keep track of where other pieces of the same program are, in case they need to talk.

And that's basically the ADA (it's not, but never mind).  It's serverless infrastructure designed for an operating system and home network.  All you're really doing, is running a program somewhere else, so that when it access a file that is actually a driver or service, it is doing so locally instead of across the network.

And that's it.  With that, everything works!  Victory fanfare, parades, we all go home.  Right?  Well, sort of.  No, not really.

Clear the Air (Once Again)

You see, part of the problem with the ADA is that if you do it wrong, people will hate to touch it, let alone depend on it.  They will avoid it, frankly, because people are lazy.  And while you could, maybe probably, have the system still work when programmers avoid it, by doing a bunch of extra legwork across the network, if developers being lazy means that the experience sucks for the user, nobody will want to be a user.

Even if I'm right about how this all should work, if we do a bad job on the implementation, that's it - game over, we lose, things to back to the comfortable old ways with minimal changes.  That's why I did a lot of extra thinking about how certain things should work, most of which sounds crazy out of context, because I'm planning for a future where OSes work very differently than they do today, and then on top of that, computers are combined in some way that developers don't yet understand.  So if I were, today, to ask a developer to operate under MAD rules, or to just assume that my OS will work the way I say it does… they would be totally justified in telling me to piss off.

They'd have to be mad to believe in MAD, before it's proven.  And that's kind of the point, at least of this post.  Some of the things that make the MAD system work, might be implemented on top of a normal operating system.  The API inheritance tree doesn't have to be the only way drivers work; it doesn't even have to be the canonical system way that drivers work.  But if it's there in a form that people can use it, then other things I've said might start to slot into place.

Likewise, you can have an IPC mechanism that application use, which is separate from the normal system IPC mechanism - it would just have to use some higher-level mechanism, like an IP:Port interface, with extra code wrapped around it.  This IPC mechanism could let applications and services publish APIs into the same tree, and you could talk to them the same way you talk to drivers.

That thing about using a system call to reach the tree… well, if it's not part of the system, you'd simply have to use a standard server on each machine.  Aside from that (and it would be inconvenient), things could work about the same, with it farming API requests off to the appropriate source.

And the distributed, agent-oriented MAD/ADA programming model?  Part of its genius is that you don't write drivers and services to listen on the network - they all expect to be contacted by a process on the same machine (IPC, not RPC), which is the same way they would be used if the ADA doesn't exist in the first place (well, mostly - there's user authentication, security and so on that needs to be addressed, but forget that for now).  Meaning you could design and run such services today, if the rest of the ecosystem existed.

Is all of this still a lot?  Hell yes!  But for all of that, it's still only half-mad, because it's less than half of MAD.  Still feasible, still important, still better.  But easier to understand, less intrusive, and I presume, vastly less intimidating.

Well, we'll have to see.

 

Monday, January 12, 2026

Why MAD?

This is another blog post that I've ended up banging my head against, writing and then discarding multiple, relatively long revisions.  Unlike the last several, however, this is not about describing a technical component of Project MAD; it's about the opposite.  The truth is, when you distill a distributed applications model down to its essence, it sounds so simple as to be irrelevant. And indeed, the model - absent proper support - might do little enough.  It really needs to exist inside of a context that makes it more valuable.

And the funny thing is, a properly supported model for distributed applications is insanely valuable.  There are thousands of examples, spread across hundreds or thousands of different domains, where we are having difficulty writing applications that combine the resources of two or more computers together.  In the world today, we are expecting and hoping that companies and open-source projects will each create solutions to each of these examples on a case-by-case basis.

Those are what I call a special-case solutions to the problem of distributed applications.  What I am searching for is the general-case solution, one that fits, if not all of these examples, most of them.  A single technology would make it easier to create solutions for these problems, but alone, a distributed application model still expects - nay, demands - that programmers solve the hard problems involved in making applications work across long distances.  Those hard problems are the kind of thing that need to be solved centrally, on an operating system level, so that each application programmer can go about doing the thing they are there to do.

But it's worth laying out the argument, as best I can, piece by piece.

Why Distributed Applications?

I said above that a properly supported model for distributed application would be insanely valuable.  It's worth running through some examples of things that are wrong with modern computing that come down to, essentially, the lack of general distributed applications.

There are a lot of examples of simply inadequate hardware.  My dear mother has a relatively recent laptop that isn't upgradable, and is now all but useless for a non-technical user.  (I'm sure I could install Linux and it would work fine, but she doesn't want it)  That's an example of something that's too cheap; but modern GPUs are getting too expensive because they are trying to get a single chip to do a massively parallel task all at once.  If someday distributed applications (generically, not counting special cases) can split their GPU load among multiple processors as easily as they can put it on a single one, a lot of people will be a lot happier.  Making applications distributed likewise helps with inadequate memory as much as inadequate CPU threading; you still need the same amount of memory, but today you may already have the excess, perhaps locked up in old laptops or desktops where there's no good way to use it.

It goes beyond computers that are poorly designed.  Smart devices, including TVs, watches, glasses, car dashboards, VR headsets, and so on, are not generally meant to run an entire generic PC for their user, but they have enough hardware to serve as a decent front end - but the process for making good use of them is spotty at best and terrible at worst.  It isn't even, to my knowledge, acknowledged that these are all examples of the same class of problem: finding a good way to make use of compute, display, and sensors distributed across many devices.  That's part of what I'm trying to solve, frankly.

There's also ownership of data.  Home cloud systems exist (I use one), but larger companies put a lot more development work into the proprietary versions, so those end up more advanced and user-friendly.  A large part of making these cloud-compute systems, however, involves solving the fundamental problems of distributed applications, if only in a specific case - finding a free compute node, authenticating users, deploying and updating applications, error handling, and so on.  If there is a general-case solution, and one that users can deploy at home or in small businesses with relative ease, then the value proposition of cloud computing isn't as strong.  This not only relieves end users, who don't have to trust the companies, but it takes companies out of the business of both safeguarding and somehow monetizing other people's data, which is an unfortunate contradiction to be in charge of.

The last example I'll point out for now is administration.  It's not trivial for non-technical users to manage home servers, not least because managing the server is simply a separate task from managing your PC, so it's something that home users need to explicitly remember to do.  Where your PC might (with varying levels of obnoxiousness) force you to update apps, drivers, and services, currently any machine that isn't in the foreground must either do things automatically or… not do things automatically, and leave the user to their own devices.  Distributed administration means that the state of the entire system is always in the foreground; if some part needs updating, it is the same whether that part is local or remote.

In the same vein, because the distributed application model that I'm proposing is about an application expanding to use remote resources, the applications are not split into separate client and server components that must be updated and managed separately.  A unified model means that local and remote pieces are understood as parts of the same whole, and that includes installation, deployment, and updates.  Between distributed OS updates and application updates, the entire environment that is being used - even though it is on multiple pieces of hardware - is all in scope at once, and all managed at once.

That, however, is all about distributed applications.  You could make a distributed application that manages existing operating systems. But MAD is more than just the app model, which raises the next question.

Why a Distributed Operating System?

When I originally created MAD, I took it as granted that we would need a new operating system to handle the management and operation of a distributed system.  Frankly, I merely assumed it; it's fair to wonder whether or not a distributed-first operating system is ultimately required.  Indeed, if you browse my blog, you will find a lot of concepts that seem to be unrelated distractions from the core concept.  And in some cases, that's probably exactly right; you'll never catch me saying that MAD is perfect.  Given the name, that'd be kind of silly.

The distributed operating system concept is not the distributed application model.  They are separate, and must be argued for separately.  And quite frankly, I can envision a future in which distributed applications are simply run on top of existing operating systems with little or no change to those operating system.  Frankly, managing distributed applications comes down to a software service; it doesn't require a clean slate.  I imagine that a lot of people, assuming they managed to figure out what I'm saying among all the stumbling explanations here on my blog, would make that point themselves.

There are two fundamental counter-arguments I'll make.

First, the distributed operating system is a collection of standards, and there should be a collection of standards that applies to distributed applications even if they are being run on top of existing operating systems.  To some extent, when I'm talking about the distributed operating system, I'm talking about the environment that exists inside of the distributed ecosystem itself, rather than the physical disk images on top of which the system itself operates.  It's important that applications and the systems onto which they are deployed, can agree on some terms, like what resources are available, what deployment constraints an application fragment needs fulfilled, how standard functions and hooks work, and so on.  It's astonishingly easy to imagine getting that wrong and creating two or more completely incompatible systems, or ones that will require serious shims and compatibility patches to be made to work later on.

The other argument to be made is that operating systems, even open-source and user-focused operating systems, have become bloated - not because of any technical failing on anyone's part, but because they were developed over time, and different components represent different ideologies regarding how a computer should be programmed.  One needs only familiarize themselves briefly with any one of the many arguments about Linux init systems to see that we are operating a complicated system on top of mixed metaphors.  I'm well aware that any OS I create or am involved in, will not be the final operating system, and assuming it survives, it will tend back towards the current state of chaos over time; thus, that itself can't be my argument.

But the various technologies I have placed generally under the OS category of Project MAD are all distillations.  For example the System API Directory and Application Folders frameworks, together unify files on disk with the abstract concept of data, as provided by drivers, services, and applications.  While I expect to have arguments about this framework with experts someday, the point (as I have made it in my last couple blog posts) is having a consistent metaphor that works for all data in a distributed system, from the smallest bits of application state, to LED light indicators exposed by drivers, to database sockets, to embedded applications and libraries.  One possible interpretation of the SAD/AF framework (not necessarily the one I'll push for) implies that any variable stored persistently in any part of any application is addressable (if not necessarily accessible, or even listed) via the directory, uniquely, so that it would be the same from anywhere in the system.  And at the same time, every compiled-in function would be not only addressable but callable from anywhere, complete with full type safety and crash handling.

It's not about actually doing that.  That would be a safety and security nightmare.  But if you have a data accessibility technology built into your distributed operating system that makes that level of access possible, it can handle anything less than that.  If you can go that far, then you can manage every application process on every node of a distributed system, you can bit-bang every GPIO bus on every piece of hardware, and you can read every last byte from every disk.  And critically, you can do it all from the filesystem, where everything accessible may also be listed publicly for you to peruse, so that you may better understand the system you're working on.

A single consistent metaphor that allows you to do everything that a distributed system can do - that's valuable, and it's a quantity that is not at all guaranteed in modern operating systems.  Building the right constraints on top to prevent abuse… I'm not going to say it's a secondary concern.  But I have some confidence that security experts won't have to strain themselves to find ways to block funny looking remote procedure calls, with suspicious senders and destinations.  If nothing else, IT departments are already wrestling with similar problems.

However, the SAD/AF framework only works for applications within the distributed OS's purview.  It is my concept that even a dedicated MAD OS image will have a number of services and drivers that are not accessible remotely under any circumstances, for security reasons - but if you simply have an ADA-compliant server on a random operating system image, almost none of the applications, services, or drivers of the host system will be exposed to the distributed system.  Rather than an intentional screening of things that do not deserve to be accessed remotely, you simply have the common and average fact of incompatibility.  And that's… fine.  It'll probably end up necessary, one way or another.

But if you want to administer systems, if you want to control all their hardware, then you want the operating system image to expose as much as possible to the network (for authorized and authenticated users, of course).  And there's a fairly obvious security hole - it's entirely plausible if you merely have a remote application service, which isn't well tied to the operating system, a remote application could start a local process which the service itself cannot regulate, or even detect, becoming an out-of-band attack vector.  And while you may be able to count on normal OS tools to discover and handle such an incident, and you can have distributed administration tools that handle those OS tools, the added complexity makes the situation more precarious.

Meanwhile, because of the way distributed systems work, many nodes in your network may be used for nothing more than hosting remote processes.  Modern OS images provide a large number of services and drivers that may not be needed or used; a compute-only network node could be trivially small, and have no exposed filesystem (or an immutable one) for remote applications to attack.  While it's possible to set up, eg Linux, systems to run at this rudimentary level, most systems are still expected to be general-purpose, and their OS images reflect that.  Systems set up for hosted compute only, especially if they have an out-of-band or self-update mechanism, may be all but untouchable to applications in a distributed system, in addition to being faster and simpler to operate.

The reason, of course, that most OS images today contain so many nuances is because the hardware they are running on top of can be so variable, and it's unclear how much of that hardware a given user might actually need.  And that brings me to my last question:

Why Modular Hardware?

When I first envisioned MOS/DCA, adding a processor to a system meant connecting little hardware devices together; the system was made of modules that would daisy-chain infinitely, and each module's hardware included absolutely nothing except its payload, and the backbone networking chip.  No general-purpose networking was involved here; it was dedicated pieces slotting together to make, effectively, a single desktop system out of hardware modules.

Hardware drivers under such a system, you understand, would be quite simple.  A CPU-only chip has no need for any drivers except the chipsets that support the CPU itself (including a fan and temperature sensor), and the backbone.  No generic networking, no printers or keyboards, no serial busses or parallel ports, no USB, no solid-state or spinning platter disk drives, no CD/DVD/Blu-ray drives.  A compute-only node not only need not do anything else, it could not.  No such accessory hardware existed.

In return for that simplicity, these modules were the very definition of commodity hardware.  I imagined they would be churned out by the millions.  In the modern world, I imagine them being built off of license-free technologies like RISC-V, driving the costs ever downwards; twenty years ago, I would have hoped for something similar, even if no such were commercially available at the time.  Nowadays, Raspberry Pi has proven you can deliver a full computer for $30, as long as you don't mind handling exposed pins and solder.

If you could make every computer you own work better with a single $30 purchase, would you really stop at one?  If plugging a raspberry pi into my parents' network was all it took to salvage my mother's failing laptop, she would have taken it in a heartbeat.  And the $30 raspberry pi comes with a graphics chip, HDMI out, and other accessories that a dedicated compute node may not need.  What really would be the ground floor, if you still want to end up with a unit that's good enough to make your distributed system better?

It goes beyond the compute only nodes.  Outside of general-purpose busses like USB, any task-specialized node only needs a few additional drivers.  Some will be complex, like a graphics unit with a GPU and multiple output ports, but if you only want to add, say, an AI accelerator chip, or a collection of data drives, the system image that runs that entire node has very few additions on top of the base layer; you will be certain that no other capability exists or is being used, because that's all that the system is.

If computer parts become that level of commodity hardware… well, I'll be honest, it will harm research and development of new chips.  That's a nice way of saying that chipmaking companies, which are already on rocky ground nowadays, could lose significant licensing revenue and sales, so long as people can get a cheaper alternative.  Some chipmakers, especially in the GPU space, are cagey about letting others understand how their technology works to preserve their competitive advantage - but commodity GPUs don't need to be individually powerful, if they can work together.  A no-nonsense alternative that's easier to program for and keep updated over the years, which gets to the same place with quantity instead of density, would certainly make that policy of stonewalling your own developers harder to maintain.

I'm trying hard not to paint those outcomes as purely good for the consumer - really, we've benefitted a lot from chip R&D over the years, in terms of increased capability and lower power consumption.  Without looking into it, I take it as given that there's graft and corruption at some levels in those companies big chip, and I don't care one way or the other.  Commodity hardware harms anyone who's trying to make high per-unit margins, whether they have good or bad reasons.  Likewise, commodity hardware comes with a massive fall in quality, inescapably.  My opinion, yours, theirs, or the average consumer's, all make no difference to market capitalism.

What holds us back from that possible future is the need to make and sell full, general-purpose computers, especially ones compatible with Windows.  As soon as China can make royalty-free, network-connected processors, that boost the power of your distributed system simply by existing, you'll be able to buy them in bulk.  If the drivers for those bulk processors are open source and can be maintained for decades to come, that will transform the computing space irrevocably.

Again - that's not an unqualified good, not if quality and R&D both nosedive.  But in our world where we already have oceans of unused hardware sitting around, perhaps it might be for the best that we start to make do with simpler, less wasteful technologies.

So Why MAD?

Everything I've talked about here is tied together by the ultimate goal of having software that expands into multiple pieces of hardware as needed.  If you can do that, you can use processors and memory and GPUs on other, even older, computers.  You can display the result on any random, network-connected display.  You can use any random piece of network-connected hardware with a keyboard as input.  You can access your home applications, not just your home files, from wherever you are.  Our existing models don't work to do all that.

The future in which they can do that is exciting.  The future in which you can control all the devices that you own, all the devices that you can reach, as though they were all in a single logical piece of hardware, that's exciting.  The idea of cheaper hardware, even if it is commodity, is exciting.  The idea of working or even playing like you're at home, whenever you have access to an internet connection, is exciting.

MAD will undoubtedly be wrong in some of its particulars.  It's probably got some fundamental flaws that we'll only discover in implementation, or on thorough review by experts.  And in the end, it's possible that MAD, being my concept alone for how distributed systems could work, will simply not be how things do work.  And I embrace all of that.

Because the future where we can do so much more, is worth a lot more than me being celebrated for writing some silly blog posts.

Wednesday, September 17, 2025

Programming MAD

One of the important things about the Agentic Distributed App model, as I've already said in a separate blog post, is that we need an application model that just anyone can jump in and create programs that span the gap between multiple machines.  There is, naturally, a lot wrong with merely saying that this is possible, simply leaving it dangling out there with no questions asked nor answered.

After all, when people talk about distributed computing research now, they are talking among other things about problems that would stymie novice programmers: things like memory coherency, race conditions, partition tolerance, reliability, security, and of course system topology.  I think it's fair to say that most programmers, to say nothing of new ones, prefer to think about and work with single-threaded logic and not address the various dangers and brain-benders involved in keeping memory synchronized between two independent machines, either of which might create a sudden shift in the status quo.

While I do have some thoughts about multithreading, and Project MAD's place in that morass, I'd like to start somewhere simpler.

Distributed Single Thread Programs

One of the things that might not be obvious from everything I've said so far, is that the ADA is supposed to among other things empower single-threaded programs that just so happen to be distributed across multiple machines.  Even if these machines and the software agents on them are executing code at the same time, it remains important to create applications with centralized logic, where all events pass through a central filter, ensuring that events never conspire to have two parts of the same program working at cross purposes.  This is not the only logical model that an ADA application can have, but it is the most reasonable model for many applications to take, or to put it more bluntly, it is the only route many application programmers will ever want to take.  Understanding and dealing with parallel operations can get complicated very fast.

If this still seems odd, understand that many Agents under the ADA model are nothing more than handlers for specific resources, translating internal application events to external events and vice versa.  If you want to process keyboard inputs, something needs to be waiting for those keyboard input events on the machine that actually receives the event, and perhaps some basic processing can be done there, but the active logic that responds to the event should be a part of the main logic loop, at least under the single-thread model.  Likewise, if you are performing GUI operations as part of your program, the functions that actually get placed in a GUI Agent may be little more than batch commands that draw on the screen and handle windowing events.  They may have little or no program logic involved; that may mostly happen in a centralized location, most likely the program core.

The ADA is not specifically about making multiple threads cooperating in parallel easy, much less trivial.  It is about making sharing of resources across device boundaries easy.  It is also about creating an application model that explains how you deploy an application into such a system, with minimal assumptions.  It is about handling edge cases like only having disk access in one remote location, while most of the computation and devices used are elsewhere.  It is about adding new resources in such a way that others how what you have and how to use it, even if that new resource is wildly different from everything you have so far connected.  None of that obliges an application programmer to take classes on networking and distributed systems.

Granted, the ADA is more useful when you are tackling parallel or parallelizable loads.  A single threaded app in a distributed system will be slower than the same on a monolithic system, but a massively parallel task can use the ADA model to natively farm out tasks to any and all processors within a system (or just to some of them) in order to make use of spare capacity that might otherwise be unused.  And while it would be unkind to suggest that the ADA simply will have a load balancer built in for parallel loads, it unquestionably has everything you might need to make a good load balancer.  Metrics like system load are important to consider when determining where you will deploy new agents, or which agents to deploy a workload to, and the ADA shall be able to dynamically add and remove agents anywhere in the system.  Thus, for things like supercompute workloads and web service load balancing, the ADA should give you everything you need for an ideal solution, whether that ideal solution comes built in or not.

It should be emphasized that this comes without needing to design and build custom hardware.  Many data center companies invest heavily in computers designed to pack as much computing power and/or storage into as small a chassis as possible, and while that remains a possibility, the MAD computing paradigm will let you create a machine that has the same capability as one of these advanced systems out of spare parts.  Where space or efficiency is critical, it still behooves one to invest in specialty hardware, but it sure would be nice to know that wasn't the only good option.

The App Streaming Model (For All Applications)

It's also worth noting that there are present-day use cases for the MAD paradigm that have little to do with parallel loads.  One, for example, is the game streaming model that several games companies have attempted, and to some extent succeeded at, in the past decade or so.  It is sometimes very inconvenient to have multiple game consoles tied to your television, or to only be able to play PC games while sitting at your computer.  The standard application model we all grew up with had no solution to this problem, and so several companies developed edge-case solutions to attempt to fill the gap.  Some, such as Google Stadia, collapsed relatively quickly, while others, such as the game streaming engine built into Valve's Steam platform, have continued.

Likewise, mobile app platforms have attempted over the years to design a “streaming app model” that doesn't require mobile applications to be installed, while still having all of the same benefits to the user.  This is designed mostly to remove the need for web apps, which are far from an ideal way to design and distribute applications.  Web apps persist even today because they are a model that is completely understood and completely under the programmers' control.  Web apps don't need much special training to create if you already have web programmers, and can flexibly handle different mobile operating systems and even desktops - but they are more limited than native apps.  At the same time, from a user's perspective, it's nice not to need applications persistently installed on your phone, especially if they insist on taking up background compute resources, need constant updates, or like sending you unwanted notifications.  It's nice to simply run an app without giving anything up to the people who created that app, and without going through a store and tedious install process.

And I'd be remiss not to mention interactive video peripherals such as car entertainment displays, or like Chromecast.  In a way, these solutions prove the fundamental premise of Project MAD: once you get used to the idea of simply connecting your phone to a thing, it becomes difficult to understand why this has been difficult for so many years.  Of course you can trivially stream video from a phone to a TV; of course you can use your phone's navigation in your car.  The problem isn't complicated, and the moment these technologies appeared, we wondered why it took so long for them to arrive.

But each of these solutions is a step to the side.  They are specific-case solutions to the general problem: Input, Output, and Processing are on different machines, but they are all part of the same application.  Trying to take one of these existing, specific-case solutions and turn it into a general-case solution will not work well, because the assumptions do not transfer.  Certainly, for the examples I've listed, you can understand that really only input, output, and processing are involved, but for example: where are your files stored?  In most cases, they are stored where you do the processing, but what happens when that isn't true?  For Chromecast, for instance, the dongle that attaches to your TV doesn't always stream from your phone; it may stream from the original source (Youtube, etc) and bypass your phone completely, which is good, but if you need to store preferences, that will end up being done on your phone, not the dongle nor in the cloud.  (Of course, the cloud service will automatically update your history in most cases, so there is storage there, too)

So now, the model must account for input, output, processing, storage, and media source.  That growing list only serve to change the question: what have we not thought of yet?  What else might be a resource that we need to deal with?  How do we deal with all of these existing devices, and how will we deal with any new ones?  Do they each need special consideration?  In which ways will we need to alter the fundamental model to incorporate each?

And that is the core advantage of a general case solution: the model doesn't change as you add new resources.  You don't need to treat adding remote inputs differently from adding remote outputs.  You don't need to treat adding writable storage different from adding media sources.  Each Agent that you add improves the capabilities of the Application, but that doesn't complicate the model.  The ADA handles game streaming, app streaming, chromecast, car displays, home media libraries, and many other edge cases with the same fundamental underlying model.  Products that were headline news when they first appeared become obvious applications of the underlying technology.

Similarly, the Internet of Things device paradigm has been waiting for a model where either software on your network takes charge of the peripherals, or the peripherals can seamlessly install a service or application on a piece of hardware you control, which you can then use to control it.  The former describes the ADA in general, while the latter is similar to app streaming.  In either case, software must bridge the gap between devices; the question is whether the user brings their own software (which controls the devices via ADA agents) or whether the devices supply it (your phone acts as an ADA server and runs an app stored on the IOT device).  As a bonus, the ADA as a framework would suggest that IOT devices with their own control apps could be controlled by ‘thin clients’ consisting of nothing more than an interface, such as a wall-mounted display.  If you already have an interactive display with a wireless interface, perhaps on your thermostat, refrigerator, toaster, washing machine, oven, shower, water bottle, toothbrush, hair brush, stuffed animal, or sex toy, that display could control your other IOT devices in exactly the same way that your phone or computer could.  This, too, has nothing to do with parallel computation; it only has to do with connecting devices.

Granted, I am being perhaps a touch sensationalist, but only because it is frustratingly difficult to do things that should be easy.  Without the perspective of MAD/ADA, I can agree, the problems look tangled and difficult.  Every addition to the model is something new, and each might add some new complication.  And as I've said multiple times, if my solutions turn out to be inadequate, I still believe that finding a general-case solution to the problems created by something like Project MAD will have most if not all of the benefits above.

One way or another, I hope for a future where we can just do fun things with our devices.

Thursday, August 21, 2025

History of the MOS/ADA Programming Model

 This topic is tricky to introduce.  One of the central tenets to Project MAD as it is today, is a whole new model for applications, and I've said that it was the last things that came together, and it tied everything together in a neat package.  What you won't know is that my early concepts of the MOS/DCA project also included a modification to standard programming models, and that legacy is still very obvious once you know to look for it.

It's difficult to ferret out exactly what I meant by it if you're just reading my notes from back then, but it was there: I wanted to expose the internals of executables and shared libraries so that, say, a shell command could execute an arbitrary function.  Important to making that work is having an interactive shell that can capture typed data - more like an interpreted language's REPL loop than something familiar like Bash or Windows Command shell.  And this has to be a structured tool, in some fashion; in modern applications programming, you pass nothing to an application when it starts except strings, and the application has to figure out literally everything from that.

This ties back nicely to my complains in the last couple blog posts about programmers being obliged to reinvent the wheel.

At the time, what I had wasn't anything like a plan.  At most, I had a complaint.  And that sparked a wish.  After all, especially within the GNU toolset, how many applications are actually just wrappers around library functions?  Complex wrappers, sometimes; that complexity is frequently needed so that this one tool can interact with a complex programming library function in multiple ways, or so that common data pipelines (piping a list of files through grep, as an easy example) operate smoothly and the way you would expect them to.  As with a lot in programming, things that are simple to say are frequently hard to do, and if you want to actually do them, there is a lot of nuance and complexity that must be navigated.

The other side of that, though, is that the nuance and complexity spawns more nuance and complexity.  Your complex tool needs to be interacted with in complex ways.  Add a third layer on top, that assumes access to tools that use libraries; then add a fourth layer and more, tools on top of tools.  As long as nothing changes, eventually you get a level of tooling where you can issue a one-word command to do exactly what you mean to.

But every level in between the core library and the final level of tooling can shift.  Needs to shift.  Every layer must be updated whenever any of its dependencies update, for security reasons if nothing else, and sometimes bigger shifts are called for.  To a certain extent, there's no getting away from that - but if it were possible to have the library function that you actually care about, be executable in its own right without an application wrapping it, there can suddenly be whole levels of redundant tooling that are at least simplified, if not eliminated.

And of course there will probably be several other layers of tooling that crop up around this new concept; I won't lie.  Ultimately, we create tools because we have specific needs, and as long as the tools fulfill specific rather than general needs, they will diversify.  But many of those pain points that we are removing are redundant ones.  Every application needs to transform text input into data streams.  Applications that read from and write to data files have to pick or create libraries to that work with the specific protocols and data streams.  And if you're creating a workflow that involves data files being processed, you need individual applications whose only role is to be a part of that workflow, applications that parse files and put them in a form other applications can take as input.

Many of these niche applications, that only exist to take one data format and make it available, or take text input and render it back into a data file, are at once important to someone's workflow and possibly security, but also are tools nobody wants to maintain.  Nobody wants to need to maintain them.  It's important that someone creates a tool that parlays between data files and workflows, but it's a thankless job, and a product nobody wants to pay for.

In short, a shell that understands data objects and types, and can use that to call a function built into an application or library directly, instead of needing to build one application after another to handle the workflow… that would solve a lot of problems.

The steely-eyed veterans in the audience are saying, “Well, we have exactly that with interpreted languages like Python.  In Python, you can open a library and call a specific function out of it.  If you have a python file that is meant to be an executable, you can also load that as though it were a library, and call functions out of it, assuming it's designed right.”  And this is, of course correct.  (For the record, I had these thoughts before I was introduced to Python.  Probably.  Around that time, at the latest.  Again, my notes are not great.)

The difference is having that be an assumption built into the entire operating system, into the fundamental programming method, so that the entire system is built on one foundation instead of a shifting mix of dependencies from various legacies.  That is, of course, making a virtue of a vice: it means starting over and doing everything from scratch, which is... painful.  And these initial thoughts of mine were missing critical parts of the picture, which is why for many years I set them aside as irrelevant and a waste of time.  At the time, all I was thinking was that, doggone it, we shouldn't have to reinvent the wheel over and over.

And in truth… there are still today whole categories of errors that only exist because library functions are sealed away.  For example, languages like Python, PHP, Go, and many others can't easily use functions buried in libraries for other languages like C; people have to write new versions of old libraries, and each needs to be maintained separately, by different people, using different language constructs and different dependencies.  Ultimately, it becomes such a different problem to maintain each of them that even if one person was willing to maintain every copy of the same algorithm, it would become untenable for some fraction of them.

All this despite the fact that ultimately, all you want is to pass parameters in and get results out.  If there were no worry about dependencies or languages, if all you had to do was call the function and capture the return, it would be simple to do from any language.  Of course, from a certain point of view… that's always been all you have to do, if your language is willing to parse library headers or code from other languages.  But the library formats in use, Linux's .so and Windows' .dll, neither comes with a human- or machine-readable directory of functions meant to inform programmers of how to use them.  If you want to know how to use them, you need specific files intended for developers to use.  That means if you have an entirely different programming language and they want to use existing library functions, they need to read those files, …despite them being in another language.

In short, there's a reason why we are where we are.

When I talk about Project MAD as a change in the entirety of computing, it can sound like I'm vastly overestimating my own cleverness - and well, maybe.  But these are real problems.  My first concept almost sounds promising on its own: just call library functions.  But that concept does nothing about compatibility and discovery problems.  It hardly matters whether or not the code exists on your system if you don't know that it's there, where it is, what it does, how to call it, what side effects to expect, and what the return values and errors mean - and that assumes that errors get propagated correctly.  And the details need to be exact; one bit out of place changes everything.

Just like there's a reason why we got here, there's also a litany of reasons why we haven't leaped forward.  Even if we all agreed on one bright idea that we would work together on, it would be a massive effort with a lot of problems that need to be solved.  And if the bright idea that we all agreed on happens to have massive holes inherent to it, because it's not as bright an idea as we thought… that's a lot of work for nothing.

That's to say, I do understand, have always understood, and will always understand, that none of this is simple, that I can be wrong, and that ultimately, even if my idea was core to anything, it would be the people who actually achieve it that deserve the credit, because it will be very difficult.

But also…

But also, various subsystems that are a part of the MAD concept (here, specifically, the Modular OS and Agentic Distributed Application model) are there specifically to solve these problems.  I've described the system directory in this post on the Unified System API, but not all parts of the solution came around at the same time.  Part of it, as I've just described in this post, was there from near the beginning.  Many years later (five to ten, maybe) I started thinking about the directory, and specifically, having types understood system-wide and having them be a central part of the system directory.  Before this, all I really had was “Call a function inside a library, so we don't have to reinvent wheels.”  But it didn't make sense, quite.

I said in my first post that the ADA came about in the process of writing blog posts about the MOS/DCA concept.  I was trying to explain, and things still felt incomplete.  I had deduced that functions and hardware drivers needed to be sorted by function, which fed into the type hierarchy in the system directory, and I was also thinking about embedded libraries and applications, and how that made application deployment and dependency management neater.  I was writing about wrapping functions with scripts so that some logic was happening on remote nodes, but that wasn't working for me.

The reason why I like the ADA goes beyond the model itself.  The model slotted well into the weirdly shaped hole that the whole “software model” side of the project had.  Yes, you could call functions directly instead of only being able to run applications - but why?  What made that so compelling, and not merely one person ranting at the sky?  Sure, it would be nice to have a directory of all the functions and types within a system, but what made that so critical to the operation of an OS that it has to be a fundamental component?

But within the ADA, you may be calling functions within your own application agents and within others'.  You will be deploying agents to hardware modules specifically because there is software on that module that is required by that Agent.  And it goes beyond functions and code (or, well, it doesn't, but it helps to think of this way): the ADA is also about exposing data so that applications can be monitored and debugged, but that requires types to be in some sense objective, even if that only means a type connected to the application and not a system-wide one.

Equally, within ADA, an Agent exists to handle the tricky problems of accountability.  There is an entirely reasonable question when you try to, for example, perform GUI API calls remotely.  The memory being used by your GUI application can't leave the display controller, or at the very least, there will be a copy of the output in the display controller, which must be managed by the GUI stack itself--while being owned by an external party.  Without an agent such as the ADA's, there are massive, massive questions to be answered about how this is not an absolutely terrible idea.  But if you can't do things like call GUI functions remotely, then... what even is the plan?

But perhaps most importantly, in the ADA, deploying Agents and routing messages is a system function, one attached to the ADA servers and not one implemented by applications themselves.  That means that knowledge of functions, data, and types needs to be standardized at a system level.  If the ADA model were implemented separately by each application, then each application would need to separately track and manage type and API version compatibility.  Implementing this as a system function makes things not only possible, but easy for programmers to handle, by doing the hard work ahead of time, in the operating system.

I like to, perhaps arrogantly, think that I am touching on fundamental truths with some of these analyses.  It's why I keep hammering, for example, on don't force every programmer to reinvent the wheel.  It is hubris to say I alone know best (I've demurred enough that you know I'm not that hubristic), but it's still satisfying to line up a whole bunch of problems in a row, pointing at them, and say, Example one, example two, example three.  Tying things up with a bow on top is satisfying, and the point of feeling genuinely excited about the ADA and Project MAD in general is that a stack of problems are placed in a box and wrapped neatly up.

It also feels good because this isn't all the work of a day, month, or year.  While this talk of modifying programming models may not have happened in the first year of the project (the first part was mostly the DCA, honestly), it's at least 15 years in the making.  Tricky problems that have tickled the back of my mind for years are coming together.  These are problems that weren't just tricky in practice.  They were tricky conceptually; it wasn't clear that there even was an answer.  It wasn't hard to imagine that I was wasting my time, because there was no clear vision of the end.

There's still a lot rough, and there's still lots of room for me to be wrong.  But it's astonishing how the last few pieces have made the puzzle come together.  At least from where I stand, it looks promising, like there's really an answer.  Not an easy one, but one that leaves us all much better off.

And even if I'm wrong, perhaps getting more eyes on the collection of problems I've been working through will help.

Tuesday, August 19, 2025

The Problem with Modern Computing

It's really hard to summarize all of Project MAD; it's one of the reasons why I didn't shy away from that particular three letter acronym.  Perhaps more important, it's hard to explain why I feel so darn sure that I have stumbled onto something meaningfully important.  My descriptions of things can get all tangled up, so it's easy to think that the core of the idea is equally tangled, equally lost.  But I genuinely, truly, believe that there is something fundamental here.

Our world has many more computers than ever before, but each computer we sit down at can feel just as confining as the last.  We're chasing after the feeling of being empowered by computers, the feeling that computers are the future, not merely the present.  I suspect that's part of why so many people are jumping wholeheartedly into AI fever; they've been looking for a long time for something that will make computers feel infinitely deep, again.  Like you can get lost in all the possibilities that a single computer can offer you.

In the era of the internet, it's become less important that a single computer feels like it contains infinite possibilities, because we can always gesture out the window and say, infinite possibilities are out there.  But that answer is less compelling than it seems, for numerous reasons that I don't want to get into right now.  A single computer can be an infinitely flexible tool, and we understood that from nearly the beginning.  Computers on wheels become cars; computers with cameras become security guards; computers with arms can weld or do other labor.  But many of these implementations moved away from depending on normal computers, by which I mean “the same as we use at home”, and with good reasons.  Security and stability, mostly, but also… also deeper reasons.

Computers are generally split, and again with good reason, into utility and user-facing computers.  Utility machines exist to get work done; user-facing machines cater to the person in control of them.  What is lost in this description is that any user-facing machine could do work, even if not every utility machine can be user-facing.  But that's not what we see, not what we feel when we work with user-facing machines.  Even though you might be part of a larger system with great capabilities, it doesn't feel like it.

The reason is because programmers need to reinvent the wheel, whenever they try to merge two machines into one.  It can be confusing to try to wrestle with the very concept of controlling one machine from another, not because that's difficult, but because it feels like it should be a solved problem already, and it isn't.  There are solutions, but… as my last post tries to explain, perhaps poorly, they are kind of poor solutions.  They were never meant to do what we really want them to do.

When I talk about remote procedure calls and the unified system API, what I'm talking about is controlling one program from another, one computer from another, in the same way that a programmer controls their own program.  If one of those remote computers, one of those programs, is in charge of handling hardware devices, then controlling one program from another means controlling hardware devices as though they were just your own bit of software.  If you use those remote computers to run software, to provide services, they should be as easy to interact with as if you were running software on your local machine.  The services should always be there, ready to serve you.

But most important, you should be able to write a program that just does stuff.  Not only doing stuff on your local machine, because as I just said, most user-facing machines don't actually do stuff by themselves.  There are other computers that do stuff, while your desktop, laptop, or phone just… well, provides you access, in theory.  In practice, because there are fewer standards than we'd like, figuring out how to control things that you own, even things designed to be controlled, can be an exercise in frustration.

Because the people who make those devices are all forced to reinvent the wheel.  Collecting all of the various hardware capabilities of a household's machines, or a user's personal stuff, into one gestalt system isn't a standard function built into computers, and neither is it straightforward to make good use of them once you have them collected.  If you want to connect multiple devices, you have to work with multiple vendors' proprietary and non-standard interfaces - and many, perhaps most, of these interfaces weren't meant to be controlled by another piece of software, but only by an interactive user.  That, too, is because they would need to invent that particular wheel.  They would need to secure it, and take responsibility for what happens when it leaves their warehouse and enters your home.  It's a lot of risk for little if any monetary reward.

The reason why these problems are unsolved, is that the alternative is reinventing all of computing.  That sounds extreme, but this is the argument I've laid out: software as we know it is not meant to connect multiple computers together, it is meant to be run on one single computer and nothing more.

This, ultimately, is the problem.  The big one.  It's the axle that must pivot if you want the world to change.  Run a single program that spans two or more computers - but it's not just about running it.  It's about teaching everyone who wants to program computers how to control two machines with one program.  Or three computers, or four.  Or an infinite number.

How do you simplify the process of making that program so that just anyone can do it?  So that Joe Shmoe, who has a day job and doesn't want to go to college for programming, can hack together a quick and dirty program that makes use of all the computers in his house?  What does a programming class look like, when you are teaching children to not merely twiddle with bits on a single computer, but to bring a motley assortment of machines into a room and unite them into a grand symphony, all orchestrated by a few words of text?

How do you let someone explore the infinite depths of possibility that exist within their computer?  Those depths exist, but we cannot explore them, not without reinventing wheels over and over. We shouldn't need to re-solve problems that others have good answers to, because our amateur efforts will be… amateurish.  Any skill we lack, and problem we can't tackle ourselves, derails a project.

Project MAD is all about making the capabilities of a computer available for you to tinker with.  It is about the capabilities of a single physical machine, but also, about all the capabilities of all the things you own, thrown in a pot and stirred.  It's about having a spare computer running in a closet that takes part of the workload that would be on your desktop, or your laptop, or your phone, and you don't have to set it up specifically to do those things.

It's about taking a computer that has worked fine for years and still making use of it even when you replace it with a faster and better one.  It's about dumpster diving and collecting random pieces of hardware and making a real workhorse out of them.  It's about buying an under-powered laptop and not caring, because as long as it's in a network with other machines, it will be just as powerful as any other.  It's about finding creative uses for old TVs and monitors, beyond being picture frames.

And from the corporate perspective?  It is, genuinely, about creating new products, products that people want, and it's about being able to just turn the crank and trust the process.  It's about creating an operating system image that you barely touch when you ship a product.  It's about shipping a hardware product and not meddling with software at all.  Imagine, not worrying about completely unrelated software that is only necessary for your device because you don't know how to let consumers control your hardware except with complicated software stacks running on top of full operating system images.

It's also, on some level, about going back to computer hardware being purely a commodity, able to be produced cheaply, and not needing to be shiny, chrome-plated best-in-class specs to be useful.  It means that when our need for computing power increases, we don't need to cram more into a single chip, with all the heat issues and power consumption that come as a result.  It means that the top of the line chip you buy this year will work together with the chip you bought two years ago, and the chip you will buy two years from now, instead of them all ending up in the garbage.

At the same time, it's about creating hardware that really is genuinely unique.  About creating new processors that are optimized for a specific task or problem, without needing to invent a new OS to run on them.  It's about creating hardware VPN devices that will let you connect securely to your home system while traveling, or your work system from home, just by plugging them into an otherwise unsecure system.  It's about creating software that is secure because the hardware it runs on can't be overwritten or even looked at by others in the network.

But it's also about taking your computing environment with you.  If you can expand one computer by connecting it to another, then have your phone really, genuinely be your computer.  Even if you aren't plugged in, massive processor-heavy tasks can be run on a machine that is plugged in, and all your phone has to power is the wifi.  But it's more than just power.  The system you sit in front of being the same as the system in your pocket, means that some important things aren't locked in one place or the other.  Your calendar and messages, phone calls and podcasts.  Your music and movies, your games and documents.  You shouldn't need someone else's permission to have things you bought accessible anywhere.

All of this - all of it.  Every last bit of this comes down to the idea that any program, written by just anyone, can unite multiple hardware systems, and do it as easily as we can write any other program today.  When you can do that, your system gets more powerful because other systems are available.  Your system gets more flexible when you have more options.  You can have a definitive “core" of your system that expands to fill any container you put it in.  Vendors can compete to be a thing worth adding to your system.

But most of all, once it's trivial to unite systems, we are no longer trapped within one.  In a world with the internet, it feels weird to suggest that we are trapped.  My mother bought a laptop just a few years ago that is under-powered and can't be upgraded, and all she really knew was that it was “slow”.  She hardly needs much from it - she writes, mostly - but it was still inadequate.  Someone sold a computer with so little ability that a writer feels limited using it, and there's simply no way to make it more powerful by adding to it.  You can't add memory, you can't even add disk space - not except SD cards, which doesn't help when Windows is complaining it doesn't have enough disk space for OS updates.

If she could just use that laptop as a dumb terminal for a session on a more powerful computer, she would be happy.  But she's not tech-savvy; me, I am, but I still struggle to combine my home server, main PC, assorted Raspberry Pis, and multiple laptops in any meaningful way.  I run some applications on the server as web apps (including the one I'm using to write this, before posting), and I store files on the server so that I can use them anywhere, but all those solutions are …weird.  If I tried to explain any of them to my mother, or frankly anyone else in my family, they'd dismiss the possibility.  Too much effort for a result that can't just be used.  Even I have to work around the limitations, get frustrated by the nuances and complications.  Even I chafe at making sure all my operating systems are up to date, especially when it's all redundant.  Even I, a tech enthusiast through all of my life, would like to have my entire tech world be part of a single, simple system.

It's one think to talk about the pie-in-the-sky dreams and hopes I have for Project MAD as being features.  I am reluctant to promise features because it feels like I'm trying to plate the project with chrome and gold, garnished with diamonds to give it that extra sparkle.  Clearly, from this post, I believe that the potential exists to do great things with the Project.

But it's more correct to say that I am constantly, constantly disappointed with computers.  I am disappointed with Windows.  I am disappointed with Linux.  I am disappointed with Android.  And while I haven't used Macs and iStuff in a long time, I don't think I'd feel much different about them, because it's not about the vendor.  We simply don't have solutions to some problems, currently.  We can make the best of what we have, but it's not the same as having some very real, very frustrating problems just be… solved.

Maybe Project MAD can't do that.  Maybe I can't.  Maybe all of my philosophizing and promises will turn up empty.  Who knows, maybe I'll get in a car wreck tomorrow and since there's nobody else on this project but me, it will all vanish into the aether and be forgotten.

But the problems are real.  The fact that we are working off of a flawed model is real.  MAD may not be The Answer, but it is an arrow pointing in the direction we need to go.  If it's never more than that, but if it succeeds in being that, then I'll be happy.  If I can never make the OS, if I can never make computers that snap together like legos, if my model for distributed programming is discarded and never looked at again… but if, if all of my efforts point the way to a solution later on, then that's fine.

Part of calling it Project MAD is that I'll understand if the MAD solution isn't the one everyone embraces.  But I know that there is something here.  I just… don't know how to convince anyone else.

Monday, August 11, 2025

IP: The Worst RPC Stack

 So I was originally going to write on another topic, and you know, I ended up going off on a side rant about something I otherwise wouldn't have really thought about, and I've come to realize that it deserves to be called out on its own.  That topic is: The IP-Port-Protocol stack that we use to communicate with servers is really just about the worst form of remote procedure call that we could have, isn't it?

Now, when I say that, keep in mind I'm an author at heart - I like to make inflammatory statements to get people riled up, especially when the statement is true at heart but knowingly wrong in its particulars.  You can always imagine, or find, worse ways to call a remote procedure, than this.  But I say it this way because most people wouldn't really think of IP:Port addressing as being a remote procedure call at all.  It is how you consume a service, though, which ultimately means that you are calling one well-defined (well, maybe) function from one program in your own program, frequently over a network.

Just on the merits of success, clearly the IP stack has done a lot for us.  But we already expect more from it than it was designed for, and that's before I start talking about the many and varied requirements that I am coming to expect from an RPC mechanism, after exploring Project MAD.

The IP Suite: A Partisan Overview

So when I say “the IP-Port-Protocol stack”, it's not necessarily clear exactly what I mean.  The Internet Protocol suite, which is the basis of literally the entire internet as we know it today, can be understood a lot of ways and with various levels of depth, but I'd like to break the topic up into two pieces: Addressing, and communication.

The part that I'll summarize as Addressing contains numerous mechanisms, some of which are barely relevant to the point I'd like to make, but broadly, they let you translate a name into a series of numbers (which can be summarized as a single number, if you prefer), and then all of the hardware in between you and the destination know what to do to connect you to that number, and connect others to your number.  There is a service, called DNS, which translates friendly website names into an IP number, which is the official canonical number you use to reach that website.  Depending on how complex the service you're trying to reach is, that IP address may actually send you to a service that distributes packets and queries to a number of different handlers, to ensure that nothing gets overwhelmed or to let you use a service more local to yourself instead of a single global server.  (And I may be getting parts of this a little wrong, because it's complicated)

But it's interesting to talk about IP addresses in a local context, because for many home users, IP addresses aren't reserved for a given user or machine; they're temporary.  And even when they aren't temporary, they're mostly arbitrary, meaning that unless someone tells you what an address is supposed to be, you have no context from the number itself.  But it's inconvenient and therefore rare, for local users to set up tools like DNS for their home network, so that you are dealing with descriptive names rather than numbers.

But the inconvenience goes a bit further: all network packets in an IP switched network are targeted not only at a machine, but at a specific port on that machine.  This port is simply another number, and although there are standards suggesting that several well-known ports should be used for specific protocols and applications, there's not actually anything that's stopping you from using the port normally reserved for websites, to do almost anything else you want.  Oh, it'll be inconvenient and confusing if someone tries to access that server from a webpage, but the system itself doesn't care.  This problem cuts both ways: You can use (almost) any port for (almost) anything you want, and you can't tell what a port is actually being used for just by the number in use.  This becomes an even bigger problem when we start trying to manage complex data spaces with IP address hierarchies, but I'll get back to that in a minute.

The second half of my description for the IP suite is “communication.”  Once you've established a data channel to a remote server, you then need both computers to speak the same language across that channel, and this is known as a protocol.  The Internet Protocol itself, of course, is one such language, one that is used to get your message to the right place; but once your message is in the right place, two pieces of software need to agree on how you make requests and how results are returned.  There are several existing protocols, and the most used ones are well-known, but it can still leave you feeling ignorant of how your own machine works.  If you know the IP number and port of a service, but not the right protocol to use, you can do nothing with it - but, as I said, the IP number and port are in some sense arbitrary.  From a certain perspective, the internet is full of billions of holes, and it is your job to cup your hands and yell code phrases into exactly the right hole.  Yell the wrong code phrase or pick the wrong hole, and nothing (or nothing useful) happens.  Pick the right hole, and you'll hear a code phrased yelled back at you from the other end of a pipe.

The end result of all of this, functionally, is that if you have the right combination of IP number, port number, and protocol, you can call one function on a remote machine.  It is, in other words, the worst possible remote procedure call stack.

If that doesn't sound right to you, it's probably because many servers have come to expose a lot of functionality on a single port.  That's all down to the parameters you pass the service when you make a request; the exposed function that you're calling is itself a directory of other functions.  Perhaps, one might argue, that means that you are really exposing far more than a single function, but that argument is flawed.

In fact, generally speaking, it is the job of anything that wants to provide services to invent a new remote procedure call mechanism.  It is your task to take a data block as input and decide what function to call, with what arguments, and if data is returned, what to do with that return value.  It is one of many places in modern computing where the solution to a problem is, force programmers to find their own solutions to that problem.  One significant consequence: every programmer is forced to find their own ways to parse, validate, and verify incoming method parameters from that data block, which provides a massive and porous surface where programmer mistakes can become exploitable program vulnerabilities - and frequently, those vulnerabilities affect general purpose services that are capable of doing a lot of things.

If you want to build a system on top of remote procedure calls, the first thing you must do is not require everyone to reinvent remote procedure calls with every program they write.  It is in this context that I want to talk about how the MOS/ADA subsystems think about remote procedure calls.

RPCs Require Better Communication

The Agentic Distributed Applications model, and the Modular OS concept built on top of it, both talk about listing what remote procedure calls are available.  The list of procedure calls is used for three purposes: one, it tells remote callers what is available, two, it is used to verify that incoming requests are valid, and three, it is used to direct the final result to the function that will be handling the request.  It's worth noting that the IP suite in fact does not tell people what functions are available to be called on a server, and the IP suite itself doesn't verify or validate any incoming requests; if there is a service which can receive the request, that service handles it.

But the MOS/ADA RPC stack is expected to do more than that.  Ultimately, it is the function of the RPC mechanism and not of the individual service's back end, to convert packed data into arguments for the function to be called, and to convert the function's return value and/or error condition into a packed data type for the return trip.  This is known as not requiring programmers to reinvent remote procedure call mechanisms, or if you prefer shorthand, it is known as an RPC mechanism.

In order to do that, the system must have an awareness of data types, which is why data types are an important part of the system directory.  Not only must you be able to convert incoming arguments to the appropriate data type, but you must be able to verify that the incoming data type is an acceptable match for the type expected by the exposed function.  And by having those types be explicitly listed, the calling procedure can do certain checks before sending the request.

(I suppose you could expect every function to take an undefined, fully variable data type as parameter to every function, do no type checking ahead of time, and expect the end programmer to dedicate the first lines of every function to finding out whether or not they were passed the correct arguments, the way for example C programs do with their command line arguments… or you could compartmentalize those checks by having them done in an explicit precondition function, which may be an overridable hook, allowing exported functions to only contain that function's code.  One of those sounds like requiring programmers to reinvent remote procedure calls to me, while the other sounds like having an RPC mechanism.)

Already, we can see incredibly clear distinctions between what I am calling RPC mechanisms, and raw server sockets that get piped straight into application code.  There is a language to RPC calls, and if you want to understand that language, it can be explicitly queried, finding out exactly what the protocol expects and how to format your requests.  Rather than shouting memorized code phrases at one specifically chosen holes among billions in a wall, there is a piece of paper taped on the wall telling you what to say into which pipe to receive what reply.

Of course, someone developing their own RPC mechanism to respond to queries on a raw server socket can do all this… and they can do it separately for every single application they create, because it isn't a defined mechanism, it is a private standard that they have decided on.  It makes far more sense for this mechanism to be standardized and this directory to be machine generated, because most if not all of the information is already there when you compile your program (or run your script, if the language used doesn't get compiled).  As long as you agree on how to translate the information already required to create and present within your program, into this directory listing, it should be relatively simple to implement.

Relatively simple, of course, does not mean easy, or even simple.  There are a lot of questions about types that need definitive answers and canonical standards.  Today, before any of those questions are answered and those standards canonized, it would be relatively difficult and complex.  To put it another way, Project MAD has always, from the very beginning, been about doing the hard work ahead of time so that other people can simply walk in and do the fun parts.  This is known in some circles as creating an operating system, though perhaps not everyone would agree.

I would argue that once you have all of those standards, you can do everything that the IP-Port-Protocol stack can do, but with names instead of numbers so that you can read what you're doing, and with documentation that tells you what's available and how to use it.  A protocol, in other words, that is not only machine readable, but human readable.  One that not only allows communication at runtime, between processes that already speak the same language, but allows the programmer who created a program to communicate with users and other programmers in the future, making explicit their assumptions, needs, and intentions.

The cost in exchange, of course, is complexity and speed.  As with many aspects of Project MAD, that slowdown might be a real problem, but equally, the speed we have now comes with fragility and ignorance.  We have built a lot of very important systems on top of tools that only barely allow us to communicate; we've discovered that we don't need much structure in order to make something like the Internet work.  But as we have wanted to create more powerful and flexible tools, we've chafed at the restrictions.  Better communication is needed.

A Word or Two on Addressing

So I promised to come back to a bit about the Addressing side of the IP-Port-Protocol stack.  As I said at the beginning, this entire topic originated in a blog post about other things, and specifically, I was trying to list all of the problems that Project MAD tries to address with its solutions.  One of those is the confusing morass that is IP addressing within application containers and private networks.

For those not aware, application containers are used to explicitly contain a piece of software and its dependencies, so that if two applications each require different versions of the same library or tool in order to function, they will not accidentally select the other app's incompatible version in place of their own.  They likewise won't share some system configuration bits, which is useful if two apps each require the same embedded tool but configure them differently.  There is another conversation to be had, here, about configuration, but we'll get back to it in a bit.

Suppose for the moment you had a home server farm, with several hardware servers, each running hypervisors that split the system into multiple virtual servers, and each virtual server used IP-based application containers, with some of these containers incorporating their own private IP networks containing multiple internal containers.  It's fair to say that there would be a lot of IP addresses being used to direct traffic on this distributed system: each hardware server's network adapters have their own, each virtual server's network adapters have their own, each container and sub-container has its own.

Part of the very real problem with this setup is that all of these IP addresses are arbitrary, and due to the way their scopes work, it's entirely plausible that if you leave things to automated processes, random containers on different virtual servers may have the same internal IP address.  They shouldn't be visible to each other unless you've done something wrong, and perhaps no part of the distributed system as a whole may malfunction, but as an administrator, programmer, debugger, or power user of the application containers, it is frustrating that what is supposed to be a unique identifier is neither unique nor identifying.  An IP address alone may tell you neither what is at that address, nor can you be sure that it is used exactly once, even within your own, privately operated server room.  And it may not be quite as simple as an amateur would like to change those internal IPs so that everything is unique, especially if those unique IDs are only useful for reading logs and chasing down problems in a complex and distributed application.

Part of what I hoped to guarantee with the MOS System Directory is that across an entire, theoretically infinite distributed system, it is still plausible to uniquely identify every resource, by having explicit nested scopes.  Because systems list their internals, even privately, as long as you can distinguish peers from each other within their own context, you can create a correct, unique, descriptive identifier for every resource - meaning that at every level of context, you can uniquely identify all children of your peers (even ones you have no access to), and if you have permission to explore the peerages going up the stack and outwards as well as down, you can discover your system's parent's long-lost uncle's step-son's cousin's application container's database, and the fully qualified unique identifier for it and for you will tell you exactly what your relationship is and isn't.

At the same time, because the unique identifier is based on nested scopes, you don't need a fully qualified unique ID to describe anything, not unless you share no relationship except having the same system root.  In fact, if you know the scope you're targeting, you can convert a local directory reference into an absolute one, and vice versa.  It's still wise, of course, to use the fully qualified ID once you start going up in scope, in case there are subtleties you are missing, but it's not necessarily technically required.  Once any two resources share a scope, that scope is the highest you need to go in order for the two to identify each other and communicate across the network.  Even if there is a faster way to communicate, the two fully qualified IDs demonstrate the maximum number of scope changes required for one to reach the other.

It may be somewhat crude for me to say, that I believe an addressing protocol should allow you to give, get, and understand the address of that thing.  It may be a bit rude to suggest that the IP protocol's arbitrary identifiers make it difficult to feel like you are in charge of the layout of your system.  It is unquestionably arrogant of me to suggest that I could do better than the technology that united and changed the world.  But what I don't think is arrogant, rude, or crude, is pointing out the flaws in any technology, especially where those flaws have consequences.  Any system built on my ideals will have its own flaws, and I have to trust that some person smarter than me will someday figure out a way to address them... no pun intended.

And IP has its flaws.  All of this is without getting into the limitations of the still-ubiquitous IPv4, which hasn't had enough addresses in a very long time and has felt for a while like it should just get replaced by its successor, IPv6, to prevent some very real, and some other mostly theoretical problems.  Frankly, that's not my problem with it, not as a power user of home networks, nor as the architect of Project MAD.

My problem is that the IP stack as a whole was a very early (starting in the 1970s) way to do remote procedure calls, and all of computing has advanced and changed since then.  Even if you don't like my solutions, look at the general problems we are facing.  We want to combine systems together, tightly, in ways the designers of the internet protocol did not foresee at the time.  If you don't like my formulation of the general case problem that needs to be solved, find a better one.  But ever since I've had this general case problem that needed solving, my mind has been seeking out solutions that fit it.  You can build a distributed system on top of IP using yet another layer of abstraction, and maybe that'll be necessary.  But for addressing parts of a system, there are better ways than depending on arbitrary and not-guaranteed-unique numeric identifiers.

One Last Topic: Distributed Configuration

So I just barely touched on the topic of distributed configuration when talking about application containers, and it's an interesting side topic, one that deserves its own full blog post later, but I believe I've teased that before, and so it's better to have a brief discussion about it, in case it keeps slipping my mind in future posts.

Configuration in a distributed system is tricky, because at any given time, an application is under multiple, potentially conflicting policy and rule domains.  The user running the application may have rules and policies.  The hardware device that the application is running on may have certain specific configuration rules necessary to make it work.  The distributed system may have an administrator that is enforcing certain rules and policies.  The application itself may have multiple default policy sets, for example, one for power users and one for non-technical ones.  And an application may be embedded in another application, and the parent application may have specific rules that it needs the embedded application to follow.

But also, general rules and policies are not the only items of interest.  Anyone who has any claim over an application, such as the hardware, user, administrator, or parent application, may call out that specific application and make an explicit configuration change to it, in order to ensure proper functioning.  A user may wish to start an instance of the application with a special configuration, whether that's part of a script, or a deliberate choice made by an interactive user in a shell.

It is my intention that the Modular OS, and the Agentic Distributed Applications model specifically, has an explicit mechanism for gathering and resolving all configurations present on the system.  The resolution step may be accomplished by a callback hook, that is, an application can choose to apply configuration changes in a specific way or even may choose to misunderstand or ignore configuration changes, but there needs to be a mechanism that presents the application with a list of all configurations, and there ought to be a standard way to resolve any disputes.

Although I'm not quite happy with it yet, the working name for this mechanism is the Standard Environment Variable Evaluator, or STEVE.  STEVE is an important part of the ADA model, and arguably fundamentally necessary for ADA to work properly, just as STEVE makes little sense outside of a distributed context.

This topic, about how best to handle distributed configuration, is one that will be determined, in the end, by people who know the topic better than I.  No matter what I try to say about it now, it is a topic that will get revisited and revised once good intentions meet practical concerns, and as such, feel free to take much of what I say below with a very large grain of salt.  But naively, I believe that there is a hierarchy of default and explicit desired configuration states.  The least important configuration, for example, is the program default built into the code, which is used only if nothing else is configured.  On the other hand, an explicit decision made by the user when starting the program is the highest priority among its peers in the configuration hierarchy.

Beyond that, though, all defaults yield to all explicit configurations, and both defaults and explicit configurations are ordered by how specific the configuration is.  Configurations are less specific if, for example, they refer to an application category broadly, like Start all windows maximized.  More specific would be a configuration referencing a specific application in general, then a specific application when run by a particular user, and then a specific instance of an application when run by that user, such as when the application is passed a configuration parameter when it is launched.

But there is a major exception to this rule.  One of the things that came out of Project MAD is that, in a distributed system, every component has the ability and authority to refuse service--indeed, it's frankly the only control you have in a distributed system--and as such, everything that has dominion over an application, such as the hardware it's operating on, the user, or an administrator, can kill a misbehaving application if it does not follow policy.  Consequently, when we talk about configuration, there is a distinction between desires and rules.  A program caught not following the rules may be killed, but a program that merely disrespects the desires of others will not be.

In the hierarchy of configuration options, then, any applicable rule should take precedence over a stated desire.  If, say, the Administrator refuses to allow full-screen applications, and the user explicitly tries to start an application in full-screen mode, your options when parsing the configuration are clear: do you allow the user to do something that may attract the administrator's ire, possibly causing the application to be forcefully quit, or do you prevent the user from doing what they have explicitly said they want to do?

Of course, it's even more complicated than that, because we're talking about a distributed system, which may have multiple competing sources for various software and hardware services.  Suppose for example you have your private phone connected to a desktop machine, in order to use the desktop's monitor, CPU, GPU, and input devices to play games.  The system administrator for that desktop machine forbids full-screen applications, but your phone is not subject to that system administrator; it is completely under your control.  Your phone, however, may not be configured to not display desktop-style applications on its own screen, and for good reason - it may be too small, too low-resolution, and in the wrong orientation, and the processors may be lackluster and prone to overheating.  In that context, if you want to launch a full-screen application, do you try to launch it on the desktop screen where that isn't allowed, on the phone screen that it isn't suited to, or neither?

My thoughts on this matter are incomplete, but there is one thing that I am certain of: it should not be down to a programmer to find and read configuration options across a distributed system, nor to determine what the nominal hierarchy is.  An operating system for a distributed system should find and request all relevant configuration options and present them to the application for parsing.  This needs to be done before a distributed application selects what hardware it is going to use, because the rules and policies governing the hardware will be used to determine which hardware is selected.

A bonus that comes from the system handling these matters, is that several lists of configurations can be made explicit: the total configuration on a system with sources, the total configuration as applied to a specific application, and depending on the configuration mechanism, you may get a list of what configuration options the application itself requests, including requests from all internal libraries and embedded utilities, which tells you what configuration changes you can make to influence its behavior.  You can see a list of what configuration changes a user has made, and which of them have and have not been actually queried by applications in the last day, week, month, or year--indicating that they may be useless, malformed, or out of date.

But likewise, a system-wide configuration parser can validate a list of configuration options, providing a list of warnings and errors that indicate some options are malformed, out of date, or nonexistent.  It can simulate how configuration would be applied if you used it on a piece of hardware, on a system you're connected to.  It can tell you how Administrator-applied policies have affected you and your programs, and how your own configurations have affected you.  It can tell you when a configuration option made explicitly simply matches the default.  It can tell you when rules and configuration options have changed, in ways that affect you.

These kind of features are only possible in a system where handling configuration is a standard process.  As with the other mechanisms I've described in this blog post, the current solution is “Bring your own,” or in other words, there is no configuration mechanism.  Even centralized systems like the Windows Registry and Windows Active Directory don't give you all the information you could possibly want, and too many applications have completely internal and private configuration files that will never be explained to anyone, meaning the applications basically cannot be configured even though they were designed to be.

These are all systems that solved specific cases of the problem when they needed to, but nobody tried to solve the problem in the general case.  Perhaps if they had tried, it would have worn out its welcome wherever and whenever application developers wanted something different. I believe that it is laudable to at least examine the problem's general case and search for solutions - but more than that, in the case of a distributed system, I think it will be necessary.  To not have a standard solution to this problem will put undue burden on application developers.

Wrapping Up

I'll consider it fair if I get feedback on a lot of the points I've raised in this blog, not merely this post, as being incorrect on a technical level.  Others know more than me about a lot of the things I've discussed.  But the point of view of this blog, and the reason why it's worth writing, is that I see challenges ahead that we're not ready for.  Building a system on top of remote procedure calls requires a lot, and our current model of clients and servers is a poor excuse for that.  Arguably, the client-server model was never meant to stand in for general RPC mechanisms - but in the modern day, it's been a hammer tasked with far more than driving in and pulling out nails.

I feel like the various web services and web APIs prove this point as well as anything.  Many of these are utilizing the tools meant for web pages as a middleware to enable general remote procedure calls over the internet, and as such, they are awkward, insecure, and suffer from a lot of problems whose primary source is programmers needing to reinvent various wheels.  Tasks that should be mechanical underpinnings of a system are being hacked together.  That's before talking about using webpages as a display in non-web contexts because standards between various operating systems are massively inconsistent, or talking about webpages being the best answer we have for a remote server presenting a local interface to the user.

The tools are already stretched.  Utilizing webpages on nonstandard ports is inconvenient, even if it's the best way to get multiple separate server applications to cooperate, rather than integrating them into a single app.  Trying to understand the flow of data in a container stack is a headache, but it's still easier than trying to manage dependencies on a system not built to properly handle dependencies.  Trying to understand what is going wrong when a client and server communicate over a network can be maddening, but it's the best way we have to make use of another machine's computing resources in parallel with your own machine's.

If you want to merge machines and utilize one's capabilities from another, you need a better mechanism.  If you don't like mine, by all means, show me up.  Do better.  I'd love to hear about it.