Where Payment Routing Ends and Orchestration Begins
Building fluxrig has meant spending a lot of time on a narrow question: what happens between the moment a byte arrives on a socket and the moment something durable decides what it means.
Those are two different jobs, and they want opposite things from the software doing them. The first has to answer now and is free to forget. The second has to remember everything and can take its time. Most of the trouble in this domain comes from buying one engine and asking it to do both.
Two layers, and the third one behind them
fluxrig calls itself an orchestration engine, and its architecture documentation has split that word into a hot path and a cold path for a while now, calling the arrangement two-speed. I am going to call them micro and macro orchestration instead, for a reason that matters later: speed is the closest axis and the first thing you notice, but it is not what actually separates them.
A transaction turns out to cross three tiers, not two, each on a clock orders of magnitude from the others.
| Tier | Clock | Owns |
|---|---|---|
| Edge (micro) | Per packet | The reply |
| Core (macro) | Seconds to months | The workflow |
| Data plane | Minutes to hours | The record |
Micro orchestration: the edge
Which gear a message passes through, which destination it leaves by, what happens when a link goes down, all of it inside a single request and the reply it is still owed. This is where fluxrig lives: Go, NATS, and WebAssembly gears on wazero, with the hot path in memory by design, and Conductor doing the switching.
Calling that routing is only half right. A network router decides on the header, never looks inside, and hands the payload on exactly as it arrived. This layer does the opposite on both counts: it decodes the payload, decides on what it finds there, and often rewrites it on the way through. A BIN range picks the destination, a field is masked before it leaves the building, a response code is translated because acquirer and issuer do not mean the same thing by a decline. Content-aware switching rather than forwarding, which is why the vocabulary here is gears and wires instead of routes and hops.
Stateless here means free of business state, not free of state: the edge holds open connections, keep-alives, sequence numbers and the correlation between a request and the reply still owed to it, and getting that last one wrong is how two terminals end up cross-wired.
In memory does not mean no disk either. A switch writes a log of every message in and out, because once the reply is gone that log is the only place an answer to “what happened to this transaction” still exists. What matters is the ordering: the write goes after the reply is on its way, never in front of it.
Macro orchestration: the core
What makes this tier macro is not how long it takes. It is that the work has several steps, that each can fail on its own, and that failing halfway has to be compensated rather than blindly retried. A settlement runs overnight, a dispute for months, and a smart reversal that waits a few seconds for a void before reversing is over almost at once: all three are macro, because the process has to remember where it was if it dies mid-flight, and nothing holds a socket open waiting for it.
Its canonical shape in payments is the compensating transaction: money left one account, the second leg failed, and something has to put it back and stay correct while doing so. That needs durable execution, a specific thing rather than a slogan. Temporal describes it as a complete log of a workflow’s life, replayed after a crash so the work carries on as though nothing failed; Zeebe, Camunda 8’s workflow engine, gets there by persisting event streams instead. An interest to declare: fluxrig’s roadmap names Temporal for its own cold path, so I am not neutral here.
The price of admission is stated most plainly by the people who like these tools. A practitioner’s guide to Temporal published in February puts the overhead at 50 to 200 milliseconds per activity, and says that if you need answers faster than that, Temporal is the wrong tool. It also lists what running one costs: a cluster, a database, an Elasticsearch cluster, and someone who knows distributed systems well enough to keep it alive.
That is the right trade for work that has to resume where it left off, and the wrong one on the authorization path, though not because a couple of hundred milliseconds would blow the window. The cost is per step and per transaction, so a switch doing thousands a second pays it thousands of times a second, on a disk. Writing down every step is what buys the guarantee, and it is what you cannot afford per packet: the property that makes this tier trustworthy is exactly what makes it unusable at the edge.
The data plane
The records the edge left behind, carried out for reporting and analytics, where completeness matters more than speed and nothing waits on the answer. Apache NiFi, Flink or JetStream consumers all fill the slot. For fluxrig it is a later stage, since there is nothing worth reporting on until the edge is carrying real traffic.
What happens between them
Asking one engine to hold all three clocks fails in both directions: a stateless router handed a workflow that has to outlive the process will lose it just as surely as a durable engine will miss a deadline.
The handoff is where the real design work sits, and it is more specific than a tier diagram makes it look. The edge has to answer the terminal inside its budget whether or not the core has finished thinking. So the questions worth arguing about are which decisions the edge takes alone, what it may promise on the core’s behalf, and how the two reconcile when the answer they settle on is not the one the terminal already got. Payments has a name for the extreme case of all three at once: stand-in processing, where the issuer is unreachable and the edge approves or declines on its behalf against whatever limits it was handed, leaving the two sides to reconcile afterwards. Anyone who has run one has an opinion about where those limits belong. Picking good tools for the three tiers settles none of that. It only gives the decision somewhere to live.
One correction, my own drafts included, because two numbers get confused constantly. The terminal’s patience runs to tens of seconds, somewhere around twenty before it gives up. That budget is not yours: it is spent across the acquirer, the switch, the scheme and the issuer, and one hop gets a slice. What you actually control is the overhead your own router adds, and that one should be sub-millisecond, which is the figure fluxrig’s own documentation states for the hot path. Saying the edge “needs sub-millisecond latency” collapses a twenty-second window and a fraction of a millisecond into one idea, and turns the tier into a benchmark contest when what matters is the tail and how predictable it is.
Reading Mifos again, twelve years on
That is the theory. What sent me to test it against somebody else’s was a project I have written before about: in 2014 Mifos replaced a from-scratch core banking build at $ERO, and it was the platform behind our Central Bank licence.
So it is embarrassing that I only found the Mifos Payment Hub EE this week. What I knew of Mifos and Fineract was accounts and core banking, with no native ISO 8583, so I did not expect it near this problem.
It connects a core ledger to payment rails through connectors, with Zeebe orchestrating and a Kafka exporter landing events well clear of the path a transaction waits on: a macro-orchestration layer with a proper data plane behind it. It never claimed to sit on a raw socket parsing bitmaps, and an instant payment API does not hand you one.
The community is looking after it, with Summer of Code work on it this August. Giulio Rinalduzzi’s migration report is worth reading whatever you build, for one line: “a migration that compiles proves almost nothing, so for every component where I could get a runtime I ran it and wrote down what happened.” It also carries the licence question in a build file, with the Zeebe client libraries pinned at 8.4.17, below the 8.6 relicensing.
Point it at card traffic and something still has to terminate the socket, decode the bitmap and answer in time. Not a hole in what they built: a second layer nobody has put next to the first. Which is the layer fluxrig covers.
Where an agent belongs, and where it does not
A fourth thing is arriving, and the question is which tier it belongs to. The orchestration world has answered soundly for its own: Temporal positions durable execution as the substrate for agents.
My answer for the edge is narrower, and not new. fluxrig’s documented pattern is the side-chain, borrowed from the mixing desk: the deterministic signal stays on the main path while a copy goes out on an aux send, and the inference returns as metadata on a message that has already moved on.
An agent has no business on the authorization path, and the reason is arithmetic rather than distrust. Its cost is unbounded by construction, since it decides how many tool calls to make while making them. You cannot promise a bounded response time while letting something choose its own round trips inside it, or explain a decline to a regulator by saying the agent felt it was right.
None of which argues against AI in payments. A fraud model with a fixed feature vector and bounded inference is a function, and fine inline; an agent is a loop with discretion over its own runtime. Give it the reconciliations, the disputes and last night’s declines.
The envelope: CBOR, not Protobuf, and not CMF either
fluxrig wraps every message in an envelope called FluxMsg, encoded in deterministic CBOR: an id, a correlation id, a trace id, metadata, a decoded data map and the original wire bytes. Same nested, self-describing structure as JSON, without the string parsing, which matters when the parse happens on every transaction.
Why not Protocol Buffers? Not because it cannot do dynamic messages, because it can, but because of who owns both ends of the boundary. fluxrig loads WebAssembly gears at runtime, and a gear need not be compiled with the host. A self-describing envelope lets a gear read a field it was never compiled against; a compile-time schema turns that boundary into a coordinated release between parties who may never speak. It also lets a routing flag be attached in flight without recompiling the gears that will carry it.
The format outlasts the engine, which is the part that only becomes obvious years later. An engine is something you replace. A format is something you have to take with you, and when only one engine understands it, the description of your own system stays behind with the vendor. When Camunda 7 reached the end of its life, five banks forked it into Fluxnova under FINOS and their models kept running, because BPMN could walk out on its own.
The same reasoning explains why fluxrig does not adopt the jPOS Common Message Format, and I want to be careful here, because jPOS is where I learned most of what I know about this protocol. CMF is good work and still moving: the v3 dataset model adds composite fields carrying TLV and bitmap-addressed datasets. But its frame of reference is ISO 8583, a scope rather than a flaw, and a different job from an envelope that privileges no message family over another.
That last part is a design goal, not a shipped capability: fluxrig has one protocol codec today, and whether the envelope is really neutral gets settled by the second family through it. That test has started. A tutorial walks through the roaming case: an authorization arrives, a GSMA Open Gateway network API is asked whether the cardholder’s handset is in the merchant’s country, and the answer rides back in a private ISO 8583 field. A card transacting in Montevideo while its handset is in Madrid is a fact the message does not contain. It obeys everything above, too: the call carries a hard time budget, “no answer inside it” is a response code rather than an error, and the authorization completes either way.
Where this leaves fluxrig
In one tier of the three, on purpose. The cold path is marked roadmap in the documentation, its durable stores along with it, and the data plane is further out still. The documentation is also specific about the consequence rather than vague: at-rest encryption is not implemented yet, so cardholder data has to stay in memory until it ships, and it says so instead of leaving anyone to find out. If your problem is a multi-step process that must survive a crash, fluxrig is not what you want.
What reading the Payment Hub changed is not the design, which was written down first. It is that I had been explaining fluxrig by saying what it is not, which leaves the listener correcting a misunderstanding I planted. Saying it does micro orchestration asks nobody to hold a negative.
If you are working on that handoff, or you have a link that needs to be somewhere it currently cannot go, I would be glad to hear about it: get in touch through JAAB.
