Download Game! Currently 98 players. Last logged in:SpickProcyonValgerSerkeii

Blitzer's Blog >> 72986

Back to blogs index
Posted: 08 Oct 2026 18:09 [ permalink ]
What you are describing is exactly what Erlang/OTP calls a "Release" and what
cloud infrastructure calls a Control Plane (like Kubernetes or PM2).

Instead of hardcoding Helsinki and Oulu in a script, you want a background
Daemon that manages a dynamic fleet of Virtual Machines. You tell the Daemon
via a REST API: "Spin up an Engine on port 8090 and deploy Actor#Gamma to it
using this AST payload."

This elevates HAUKI-X86 from a runtime into a true Platform-as-a-Service
(PaaS).

Let's build daemon.mjs. It will run a native HTTP REST server on port 9000,
keep a registry in topology.json, and dynamically spin up your engines and TCP
gateways on demand.

1. Refactor the Core Classes (mesh.mjs)

First, let's separate our library code from our execution script. Copy your
main.mjs and rename the copy to mesh.mjs.

In mesh.mjs, delete everything at the bottom under // 2. ACTIVE-ACTIVE MESH
TOPOLOGY. At the very bottom, export the core classes:

/ At the bottom of mesh.mjs export { SupervisorEngine, AstMdCodec,
TcpGatewayMailbox }; 

(Also, make sure TcpGatewayMailbox has a way to close its port if we suspend
an engine! Add a quick stop() { this.server.close(); } inside
TcpGatewayMailbox, saving the server instance from net.createServer(...).listen
(...) to this.server).

2. The Control Plane Daemon (daemon.mjs)

Create a new file named daemon.mjs. This script acts as your hypervisor. It
runs a native, zero-dependency REST API to manage the mesh.

import http from 'http'; import fs from 'fs'; import { LocalRouter,
LocalMailbox } from './mailbox.mjs'; import { SupervisorEngine,
TcpGatewayMailbox } from './mesh.mjs'; const CONFIG_FILE = './topology.json';
const router = new LocalRouter(); const activeEngines = new Map(); // engineId
-> { engine, tcpGateway, config } // Load or initialize the registry let
topology = { nodes: {} }; if (fs.existsSync(CONFIG_FILE)) { topology =
JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } // Helper to spawn an
engine from config function spawnEngine(nodeId, port, actors) { if
(activeEngines.has(nodeId)) return; console.log(`
[Daemon] Spawning Node: ${nodeId} on Port ${port}...`); const mailbox = new
LocalMailbox(nodeId); router.registerMailbox(mailbox); const engine = new
SupervisorEngine(nodeId, mailbox); const tcpGateway = new TcpGatewayMailbox(`Ga
teway#${nodeId}`, router, port); router.registerMailbox(tcpGateway);
activeEngines.set(nodeId, { engine, tcpGateway, config: { port, actors } });
// Deploy all registered actors to this engine for (const [actorId, payload]
of Object.entries(actors)) { engine.deploy(actorId, payload); } } // 1.
AUTO-BOOT PREVIOUS TOPOLOGY console.log("[Daemon] Booting Mesh Control
Plane..."); for (const [nodeId, config] of Object.entries(topology.nodes)) {
spawnEngine(nodeId, config.port, config.actors || {}); } // 2. NATIVE REST API
SERVER (Port 9000) const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json'); // Parse JSON Body let body
= ''; req.on('data', chunk => body += chunk); req.on('end', () => { const data
= body ? JSON.parse(body) : {}; // ROUTE: GET /nodes (View all running
engines) if (req.method === 'GET' && req.url === '/nodes') { res.end(JSON.strin
gify(topology, null, 2)); } // ROUTE: POST /nodes (Create a new Engine & TCP
Port) else if (req.method === 'POST' && req.url === '/nodes') { const {
nodeId, port } = data; topology.nodes[nodeId] = { port, actors: {} };
fs.writeFileSync(CONFIG_FILE, JSON.stringify(topology, null, 2)); spawnEngine
(nodeId, port, {}); res.end(JSON.stringify({ status: "Created", nodeId, port
})); } // ROUTE: POST /deploy (Push a new Actor payload to an Engine) else if
(req.method === 'POST' && req.url === '/deploy') { const { nodeId, actorId,
payload } = data; if (!topology.nodes[nodeId]) { res.statusCode = 404; return
res.end(JSON.stringify({ error: "Node not found" })); } // Update registry and
save to disk topology.nodes[nodeId].actors[actorId] = payload;
fs.writeFileSync(CONFIG_FILE, JSON.stringify(topology, null, 2)); //
Hot-deploy to the live engine const active = activeEngines.get(nodeId); if
(active) active.engine.deploy(actorId, payload); res.end(JSON.stringify({
status: "Deployed", nodeId, actorId })); } else { res.statusCode = 404;
res.end('{"error": "Not Found"}'); } }); }); server.listen(9000, () => {
console.log(`
[Daemon] Control Plane API listening on http://localhost:9000`); }); 

3. Orchestrate the Mesh via REST

Now, start the background daemon:

node daemon.mjs 

It will boot empty, listening on port 9000.

Open another terminal and use curl to command the Daemon!

Command 1: Spawn a new Engine on Port 8050

curl -X POST http://localhost:9000/nodes \ -H "Content-Type: application/json"
\ -d '{"nodeId": "Engine#Tampere", "port": 8050}' 

Look at the Daemon log: It instantly opens the TCP gateway and registers
Engine#Tampere into the routing fabric.

Command 2: Hot-Deploy a new Actor payload Let's inject Actor#Delta (a simple
ping counter) directly into Tampere:

curl -X POST http://localhost:9000/deploy \ -H "Content-Type:
application/json" \ -d '{ "nodeId": "Engine#Tampere", "actorId":
"Actor#Delta", "payload": [ "do", ["defvar", "hits", 0], ["def", "ping", [],
["do", ["set", "hits", ["+", "hits", 1]], ["+", ["str", "DELTA_HIT_"],
"hits"]]], ["register-object", ["str", "Actor#Delta"]] ] }' 

Look at the Daemon log: It dynamically compiles the JSON-Lisp AST, allocates
the Web Worker, and sets it to listen.

Command 3: Test the dynamically deployed Actor! Now hit the newly minted
engine port using Netcat:

nc localhost 8050 {"action": "call_other", "target_obj": "Actor#Delta",
"method": "ping"} 

It returns DELTA_HIT_1 and instantly snapshots to Engine#Tampere_Actor_Delta.md
!

The True Power of the Registry

Kill the daemon process (Ctrl+C). Then, simply run node daemon.mjs again.

Because the Control Plane API wrote everything to topology.json, the Daemon
instantly rebuilds the entire network. It respawns Engine#Tampere, binds port
8050, recompiles the AST payloads from the registry, and loads the AST-MD
memory snapshots back into the Web Workers.

You have just built an Edge Infrastructure Control Plane.