Kalos Architecture

Kalos is a C++20 microkernel application server. The kernel — kalosd — orchestrates plugins and a message bus and does nothing else. It has no HTTP, no SQL, no authentication, no file handling of its own. Every capability the runtime has arrives as a plugin loaded behind a versioned binary boundary. This page describes that architecture as it is built.

The kernel provides seven services and nothing else

The kernel implements exactly seven services: configuration, plugin loading, the message bus, the plugin lifecycle, logging, the plugin registry, and a local management socket. It knows nothing about the protocols and subsystems that make up a running application. HTTP, TLS, and CORS are not in the kernel. SQLite and PostgreSQL are not in the kernel. Authentication, the ADL engine itself, the Lua scripting layer: none of them are in the kernel. They are plugins.

This is the literal meaning of “nothing in core.” The kernel is an orchestrator: it reads configuration, loads the plugins that configuration names, runs them through a defined lifecycle, and carries messages between them. The application is assembled from plugins at startup.

That design serves two ends at once. It is a governance property: because no capability lives in a privileged core, there is no privileged core to bypass; every request travels the same plugin pipeline, and the rules the ADL engine enforces have no side door around them. And it is a portability property: a kernel with no operating-system entanglements in it is a kernel with little to port. What varies between a Linux server, a macOS workstation, a Windows host, and an ARM single-board computer is largely which plugins load, not what the kernel is.

Plugins do all the work

At startup the kernel loads each configured plugin with dlopen (or LoadLibrary on Windows), in the order the configuration lists them. A representative configuration:

{ "core": { "plugins": [
  { "name": "transport.rest", "path": "kalos_rest.so",   "enabled": true },
  { "name": "store.sqlite",   "path": "kalos_sqlite.so", "enabled": true },
  { "name": "handler.authd",  "path": "kalos_authd.so",  "enabled": true },
  { "name": "adl",            "path": "kalos_adl.so",    "enabled": true },
  { "name": "seeder",         "path": "kalos_seeder.so", "enabled": true },
  { "name": "scripting.lua",  "path": "kalos_lua.so",    "enabled": true }
] } }

Read that list as a fact about the architecture: the REST transport, the SQLite store, the authentication handler, the ADL engine, the seeder, and the Lua scripting layer are all peers, all plugins, all replaceable by editing configuration. The kernel does not privilege any of them. Load order is significant (plugins initialize in the order given, and a plugin may call actions registered by earlier plugins but not later ones), so the canonical order is transport → store → auth → ADL → seeder → scripting.

Every plugin receives an ICore* when it loads and uses it to participate: register_action(name, handler) to expose an operation on the bus, register_route(method, path, action) to map a transport route to an action, handle_request(request) to dispatch, get_store / get_plugin to reach peers, and emit / subscribe for events.

The plugin archetypes

Plugins fall into archetypes by the role they play. The kernel treats archetype as introspection metadata (it does not enforce it), but the categories describe how the system is organized:

  • Transport: how requests arrive and responses leave. REST (with TLS and CORS) is the transport plugin shipping today.
  • Store: where data lives. SQLite and PostgreSQL are implemented as store plugins behind a common interface.
  • Handler: request-handling subsystems. The ADL engine itself is a handler; so are authd (authentication and identity) and the seeder.
  • Scripting: the in-request extension layer. Lua, embedded via sol2, is the scripting plugin today.
  • Service and Middleware: supporting archetypes for background services and for logic that sits in the request path.

The load-bearing observation is that the engine itself, the ADL handler, is a plugin among plugins, not a privileged core. Kalos is not “a server with a plugin system bolted on.” It is a kernel that is only a plugin system, with the application server assembled entirely out of plugins.

The plugin boundary: C at the door, disciplined C++ inside

Plugins are separately built shared libraries, so the boundary between the kernel and a plugin is a binary contract. Kalos structures it deliberately:

At the door, the contract is C. Each plugin exposes an extern "C" entry point returning a plain-old-data information struct that carries, among other things, an api_version. The kernel checks that version before any C++ object is constructed or any C++ method is called across the boundary. A version mismatch is refused at load time with a clean error, not discovered later as a crash. This is the enforced load-time handshake: the compatibility check happens first, in the one language whose ABI is stable across compilers.

Inside, past the handshake, the contract is disciplined C++. The kernel and plugins communicate through pure-virtual interfaces (ICore, IPlugin), and STL types and JSON objects cross the boundary — which means a plugin must be built against a compatible C++ standard library and build configuration, a constraint the handshake exists to police. Errors are returned as values (a Result<T> discipline) rather than thrown; exceptions do not cross the boundary. The rationale is written into the type definitions: a boundary that must stay binary-stable cannot rely on exception propagation between separately compiled objects.

The short form, which is accurate: a versioned plugin ABI with an enforced load-time handshake — C at the door, disciplined C++ inside.

The message bus and the lifecycle

The kernel carries messages between plugins on a synchronous bus. A Request carries its action, params, body, headers, identity, and originating transport; a Response carries a code, data, and any error_message and headers. Dispatch is synchronous. An unknown action returns 404. Alongside dispatch, plugins emit and subscribe to events, which are fire-and-forget with undefined ordering; events drive history, notifications, workflow advancement, Lua hooks, and integrations.

Each plugin moves through a five-phase lifecycle in config order: on_register (receive and store the ICore pointer, nothing more) → on_init (open connections; the ADL handler restores domains and verifies schema here) → on_start (register actions and routes, begin listening). Shutdown runs on_stop then on_destroy in reverse order.

Two current constraints are worth stating plainly rather than discovering later. Action registration is append-only (names cannot be replaced or unregistered), which means domains cannot be unloaded without a restart, and a Lua hot-reload can leave a stale handler in place. And the management socket that exposes status, plugins, actions, and shutdown is local only, line-delimited JSON, never network-exposed. These are properties of the runtime as built, not aspirations.

Why this shape

A microkernel with a versioned plugin boundary is more work to build than a monolith. Kalos is built this way on purpose, and the payoffs are concrete: the engine that enforces your application’s rules is isolated behind the same boundary as everything else, so there is no privileged code path around it; the store, the transport, and the auth layer are swappable without touching the kernel or your ADL; and a kernel with nothing platform-specific in it travels wherever the plugin boundary can be hosted. The essay C at the Door: The Plugin Boundary That Lets One Runtime Reach From the Server to the Edge develops what that portability makes possible.