Pollen
Amalgame's peer-to-peer data bus + declarative workflow orchestrator. No central broker, no ZooKeeper cluster, no SPOF — every node talks directly to the others over UDP, and they all read the same workflow.json on a shared network mount to know what to do at each step. Sister project to Mosaic: where Mosaic handles your inbound HTTP traffic, Pollen distributes data between your services and coordinates their processing.
Why "Pollen"
Pollen travels without a central coordinator: carried from flower to flower by autonomous carriers, it transports the most valuable thing to propagate — genetic information. Each carrier knows where to go, the pollen knows what to become. That is exactly the execution model we're building: autonomous nodes that emit and receive directly, on a network where coordination lives in the shared data (the workflow.json), not in a conductor.
Two layers: a peer-to-peer bus and a workflow orchestrator
Pollen is unlike RabbitMQ, Kafka or NATS — all central brokers. Pollen is closer to a mix of ZeroMQ (for transport) and Argo Workflows (for orchestration), fully decentralized:
- Transport — direct UDP server to server, no TCP handshake, no intermediary. The shortest path between two processes on the same LAN.
- Coordination — a
workflow.jsonfile on a shared network mount. It describes the nodes (who does what) and their topology (who follows whom). Each server reads this file on startup, learns which events are meant for it and where to send the result.
The workflow.json is the orchestrator. There is no "orchestrator" process to run anywhere. The file is declarative, the nodes are autonomous — each one knows what to do at every step.
When to use Pollen
- LAN / local network — IoT, SCADA, industrial sensors, factory supervision
- Distributed processing pipelines — transformation chain (acquire → filter → aggregate → archive) across several machines
- Symmetric topologies — no client/server, every node emits and receives
- Zero infra to provision — no dedicated VM, no Docker compose for a broker, no workflow server
- Low latency — UDP, no handshake, no intermediate queue
- Resilient to network breaks — a node that comes back re-reads
workflow.jsonand resumes its role
workflow.json — the declarative orchestrator
The core idea: no process decides who does what. The decision lives in a JSON file dropped on a network mount visible to every node (NFS, SMB, cluster share). Each server reads it and applies its part.
// workflow.json — example: temperature processing chain { "name": "telemetry-pipeline", "version": 3, "nodes": { "acquisition-1": { "host": "sensor-gw-01.lan", "port": 5000, "emits": ["temperature.raw"], "next": ["filter-1"] }, "filter-1": { "host": "proc-01.lan", "port": 5000, "consumes": ["temperature.raw"], "emits": ["temperature.filtered"], "next": ["aggregator-1", "archive-1"] }, "aggregator-1": { "host": "proc-02.lan", "port": 5000, "consumes": ["temperature.filtered"], "emits": ["temperature.minute-avg"], "next": ["dashboard-1"] }, "archive-1": { "host": "storage-01.lan", "port": 5000, "consumes": ["temperature.filtered"] }, "dashboard-1": { "host": "web-01.lan", "port": 5000, "consumes": ["temperature.minute-avg"] } } }
Every Pollen server, on startup:
- ◐Reads
workflow.jsonfrom the shared network mount - ◐Identifies which node(s) it owns (matching on
host+port) - ◐Automatically subscribes to
consumestopics and configuresnextdestinations for its emissions - ◐Listens on UDP for messages on its topics, applies its processing, re-emits to the next nodes
- ◐Watches
workflow.jsonvia amalgame-io-filewatcher — any change reloads the topology hot, without restarting services
Changing the topology = editing a JSON file. Adding a node = adding an entry. Re-routing a flow = changing next. No redeploy, no management API, no central console.
Architecture (taken from the TARMeule prototype)
The Node.js implementation (github.com/BastienMOUGET/TARMeule) is the functional reference for the Amalgame port. Concepts:
Versioned topics
Every topic is identified by a UUID + a schema ({ value: "number", unit: "string" }) + a version. Producers send referencing a version. Schemas evolve without breaking old consumers.
Explicit subscriptions
Each node declares its subscriptions: { ip, port, topics: [...] }. Producers read this list to know whom to send to. No multicast discovery — JSON file directory shared between nodes.
UDP transport + custom ACK
Direct UDP node → node, with an application-layer ACK mechanism (configurable timeout + retry, ackTimeout × maxRetries). Message UUIDs are remembered for dedup.
Optional AES encryption
If encryptionKey is set, every packet is AES-256-CBC encrypted before send. Otherwise plain JSON payload (trusted network).
Notification-based sync
When a node creates/updates a topic or a subscription, it broadcasts a SYNCHRONIZATION message to other nodes, which reload the matching file. JSON files are the persisted source of truth.
RAM cache + file persistence
All topics and subscriptions live in RAM for performance. JSON files in sharedDir/ let a node restart without losing state, and let the cluster reform after an outage.
Target Amalgame API — a node following the workflow
App-side code is minimal: declare your name in the workflow, register a function per consumed topic, and Pollen does the rest (reading the shared JSON, subscriptions, UDP routing to the next nodes).
namespace App import Amalgame.Pollen public class Program { public static void Main(string[] args) { // Initialize the node (reads config.json + shared workflow.json) let node = Pollen.Node("filter-1", configDir: "./config") // Register a handler for the consumed topic. // Forwarding to "next" nodes is done automatically by Pollen // based on the handler's return value. node.On("temperature.raw", fn(msg) { let v = msg.Get("value") as float if (v < -50.0 || v > 150.0) { return Pollen.Drop() // out of range → ignored } return Pollen.Emit("temperature.filtered", { value: v, unit: msg.GetString("unit"), source: msg.Source }) }) node.OnTimeout(fn(msgId) { log.Warn("ACK missed for {msgId}") }) node.Run() // UDP loop + filewatch on workflow.json } }
The lower-level layer (inherited from TARMeule) stays available if you need free pub/sub without a workflow:
// "Raw" mode — direct pub/sub, no workflow.json let bus = Pollen.Raw("./config") bus.UpsertTopic(null, "temperature", 1, { value: "number" }) bus.UpsertSubscription({ ip: "192.168.1.143", port: 5000, topics: ["temperature"] }) bus.SendMessage({ uuid: topicId, version: 1 }, { value: 25 })
Why port it to Amalgame
The Node.js prototype works, but it's heavy to deploy on the target machines (sensors, industrial gateways, PLCs): you need Node.js and its 50 MB runtime, you have to manage process keep-alive, run npm install, etc.
The Amalgame port will deliver:
- ◐A single native binary —
./pollen-node, ~3 MB, starts in milliseconds - ◐No runtime to install — deployable on Raspberry Pi, Linux PLCs, Windows embedded
- ◐Minimal UDP latency — no Node.js GC pauses, bounded RAM
- ◐
amalgame-serviceintegration — native systemd daemon or Windows service - ◐Encryption via
amalgame-crypto— shares the same AES code as the rest of the ecosystem (once Argon2id/AES land) - ◐Typed Amalgame API — topic schemas validated at build time, no more runtime structure bugs
Planned roadmap
Three phases, preconditioned on amalgame-crypto shipping AES (scheduled in Mosaic phase 2.1).
Transport layer port
AM port of TARMeule: UDPManager, ACK + retry, RAM cache, JSON files per node, sync notifications. Wire-compatible with a Node.js TARMeule node.
Workflow layer
Reading workflow.json from the network share, host:port → node matching, automatic subscriptions and next routing. Filewatch + hot reload via amalgame-io-filewatcher.
Schema validation
Message validation against the topic's structure on emit AND on receive. Explicit error if a producer sends an incompatible payload.
Configurable QoS
Three modes: fire-and-forget (no ACK), ack-required (current mode), at-least-once (retry until success). Per-topic or per workflow edge.
Multicast discovery (optional)
Optional LAN multicast announce (239.x.x.x:port) for nodes not listed in workflow.json — useful for dynamic sensors that join and leave.
Visual workflow editor
Small web tool (served by Mosaic) to edit workflow.json graphically — drag & drop nodes, draw edges. Optional: you can absolutely edit the JSON by hand.
Bridge to Mosaic
Optional AM bridge to expose an Pollen topic as a Mosaic WebSocket, and vice versa. Wire a web dashboard to sensors without a third-party broker.
Key-based auth
Today: one shared symmetric AES key. Evolution: public keys (Ed25519 once amalgame-crypto has them) to sign messages and authorize publishers by identity.
What Pollen is not
- Not a durable broker — no persistent queue, no Kafka-style replay. A missed message is lost (except via retries within the ACK window).
- Not a full Temporal / Argo Workflows — no execution history, no stateful retry-with-backoff, no transactional saga. The workflow is topological (who talks to whom), not sequential-stateful (step A then B then C with rollback).
- Not multi-datacenter — UDP rarely traverses an Internet firewall. Pollen targets LAN or VPN-LAN.
- Not a Mosaic replacement — to expose a web API to off-LAN clients, you want Mosaic.
- Not a transactional MOM — no atomic cross-topic transactions, no dead-letter queue, no global ordering guarantees.
For those cases, the ecosystem provides the right third-party tools: Kafka for durable replay, RabbitMQ for transactional workflows, MQTT for centralized-broker IoT. Pollen plays in the "real-time LAN pipeline, no broker, no orchestration server" slot.
Not yet implemented
Pollen is currently a working Node.js prototype (TARMeule) and a port intent. The current priority remains Mosaic; Pollen starts once amalgame-crypto ships AES.