HORUS/blog

Sep 5, 2026 · robotics-middleware · multi-robot · ros-2 · fleet-management

Best Robotics Middleware for Multi-Robot Projects

For multiple robots the link between machines should be ROS 2 over a Zenoh-style transport or a broker; shared-memory middleware belongs inside each robot.

For multi-robot projects the link between robots should be ROS 2 over a Zenoh-style transport or a message broker, not shared memory like HORUS. Fleets fail at the network layer: discovery floods, patchy Wi-Fi, robots that leave coverage and come back. The verdict changes only for what runs inside each robot, where the on-board choice is separate from the fleet link and answers to different pressures. This post is for people who already know ROS 2 and are about to add a second, or a fiftieth, robot to a system that works with one.

One robot worked. Two robots is where the strangeness starts. Both machines join the lab Wi-Fi, both announce themselves, and suddenly every topic from one shows up in the other's list, including the ones with identical names. You add namespaces. That helps until someone plugs in a laptop running the same stack and the whole thing wobbles again. You separate the domains. That helps until the two robots actually need to tell each other something, and now the separation you added is the problem you have to work around. Meanwhile the processors are busier than they were with one robot, and nothing about the task got harder. Then somebody drives a robot around a corner, the signal thins, and the system does not fail cleanly: each machine keeps believing the others are there, commands stop arriving, and nobody watching can tell which robot is confused. The usual advice is a bigger antenna and a quieter channel, which treats a symptom. The thing underneath is that a stack designed for programs on one machine is being asked to be a network protocol.

Which middleware should a multi-robot project actually use?

Use a transport built for networks between the robots, and whatever suits each robot inside it. Concretely, that means ROS 2 carried over a Zenoh-style transport for fleets where robots must share sensor data or maps, or a broker such as MQTT for fleets where robots mostly report status and receive jobs. The reason is simple and unglamorous: the hard part of a multi-robot system is not moving bytes, it is behaving sensibly when the link between machines is slow, congested or gone, and the technologies that handle that well were designed for wide-area networks rather than for a workstation. The verdict flips only in one direction. If your robots do not actually need to talk to each other in real time — if the coordination is a task list handed out from somewhere, and each robot then works alone — you do not have a multi-robot middleware problem at all. You have a fleet-management problem with a normal single-robot stack under it, and that is a much easier thing to build.

What does middleware have to do differently once there is more than one robot?

Once robots are separate machines, the middleware has to have opinions about failure, and a single-machine layer never needed any. Inside one computer, a message either arrives or the whole robot is already dead; there is no meaningful partial state. Across a network there is nothing but partial states. A robot can be reachable but slow. Two robots can each be able to reach the operator and not each other. A message can arrive twice, or arrive after the situation it described has changed. Every one of those needs a decision, and if the middleware does not make it, your application code makes it accidentally and usually badly. There is a second job that appears only with multiple robots: knowing who exists. With one robot, the answer is a launch file. With a fleet, machines join, leave, run out of battery and come back with a different address, and something has to keep that picture current without flooding the network asking. Those two jobs — behaviour under partial failure, and membership — are the whole of what changes.

What do teams try first with a second robot, and why does that fall over?

Nearly everyone puts both robots on the same network with the same stack and lets automatic discovery sort it out. That is the right first experiment and it works well enough to be misleading, because in a quiet lab with two machines everything does find everything. The failure comes in stages. First, name collisions: both robots publish a topic with the same name and something subscribes to both without anyone intending it. Namespaces fix that. Second, load that grows with the fleet rather than with the work — every machine learns about every topic on every other machine, so processors get busier as robots are added even when each robot's job is unchanged. Domain separation fixes that and breaks deliberate sharing. Third, the network stops being a lab network. Coverage drops behind a shelf, a switch buffers, another team streams video, and the fleet behaves in ways no single-robot debugging habit prepares you for. This last stage is where teams stop patching, and it is the same wall behind why a ROS 2 graph breaks on ordinary Wi-Fi.

What are the real options for connecting robots to each other?

There are six, and they split by how much the robots must agree about. ROS 2 with its default DDS transport is the baseline, and works well on a wired or well-controlled network. ROS 2 carried over a Zenoh-style transport is the current answer for fleets that outgrow that, because it was built for links that are lossy, routed and wide-area. An MQTT broker is the right choice when robots report status and receive jobs rather than sharing live data. A fleet-management platform sits above all of these and handles assignment, monitoring and updates rather than message passing. A general message library over TCP is viable when you have few components and are willing to define every contract yourself. And then there is the layer inside each robot, which is a separate decision entirely: HORUS is an Apache-2.0 real-time middleware where Rust, Python and C++ share the same shared-memory ring buffers on one machine, so it is the on-board stack rather than the link between robots — shared memory stops at the edge of a computer, which is exactly why the fleet link stays a network technology.

Which option suits which kind of fleet?

The right option depends on how tightly the robots are coupled, not on how many of them there are.

OptionWho it is forWhat it assumes you knowWhen to pick itWhen not to
ROS 2 with the default DDSSmall groups on a controlled networkDomains, namespaces, quality-of-service settingsThe robots share a wired or quiet lab networkThe fleet grows or the link stops being predictable
ROS 2 over a Zenoh-style transportFleets sharing live data across sitesROS 2 concepts plus a routed network modelRobots must share maps, poses or sensor streamsCoordination is only status and job assignment
An MQTT brokerRobots reporting to operators and back-endsTopics, retained messages, running a brokerTraffic is status, jobs and human commandsA robot waits on the link to keep moving
A fleet-management platformTeams running deployed robots for customersWhatever model the platform imposesAssignment, monitoring and updates dominateYou need robot-to-robot data at speed
A general message library over TCPTeams owning every contract themselvesSockets, message shapes, process supervisionComponents are few and the shapes are settledYou would rebuild recording and introspection
HORUS, inside one robotOn-board stacks mixing Rust, Python and C++One of those languages, and life outside the ROS package setSensing, control and actuation share a boardYou want a link between separate machines
The robot vendor's own fleet SDKTeams running a bought platformWhatever model the vendor choseThe vendor's fleet tools already do the jobYou will mix hardware from several vendors

Most working fleets use two rows at once: one technology inside each robot, another between them.

What should a research group with a few robots in one room pick?

Stay on ROS 2 with default settings, add namespaces, and spend the saved time on the research. A handful of machines on a wired switch or a lab access point is precisely the situation the default arrangement handles, and every hour spent hardening a fleet transport is an hour not spent on the multi-robot behaviour that is the actual subject of the work. Two habits pay for themselves immediately. Give every robot its own namespace from the first day, because retrofitting names into a working system is tedious and error-prone. And put the robots on their own network segment rather than the building Wi-Fi, because otherwise your experiments will be contaminated by whatever else is on that channel, and you will spend an afternoon debugging a phenomenon caused by somebody's video call. The moment to reconsider is when the group starts running robots in a corridor, across a building, or between sites. That is a different network, and the defaults were never aimed at it.

What if the robots move in and out of Wi-Fi coverage?

Then design for the link being absent, because it will be, and no transport choice removes that. Coverage gaps change the problem from moving messages to deciding what a robot does alone. The robots that behave well in patchy coverage share one property: each one holds enough state to finish the current task, or abandon it safely, without asking anyone. A robot that stops dead in a doorway because a message did not arrive is a robot whose behaviour was written assuming the network. The transport still matters, and here the choice narrows. Technologies built for local networks tend to treat a missing peer as an error state and spend effort trying to re-establish everything at once, which is exactly the wrong response when a robot is simply behind a wall for a while. Transports built for routed, lossy links treat disconnection as normal and reconnection as cheap. That difference is the strongest practical argument for moving a growing fleet off the default, and it is covered in more depth in whether Zenoh is a real alternative to DDS.

What if the fleet has to work at a customer site next quarter?

Then reduce what has to work over the network, rather than trying to make the network work. A customer site is a network you do not control, cannot survey properly in advance, and will be blamed for anyway. On that timeline the winning move is architectural: make each robot able to do its whole job with no contact, and let the fleet layer carry only what genuinely has to cross — task assignment, status, and the occasional map update. Anything designed to stream between robots continuously becomes a support ticket the week after installation. Two other things pay off disproportionately on a short timeline. Give the operator a single place that shows what every robot believes it is doing, because the first field failure will otherwise be diagnosed by guesswork over the phone. And make sure a robot rejoins the fleet by itself after a network outage, without a person power-cycling anything, since that single behaviour is the difference between a quiet deployment and a weekly call-out. Middleware choice matters here far less than those two decisions.

What if the team knows ROS 2 and nothing else?

Then stay in ROS 2 and change the transport underneath it, which is a much smaller step than it sounds. This is the good news for a team that has already invested in the ecosystem: the message types, the tooling, the packages and the mental model all survive a transport change, because the layer that carries messages between machines was designed to be replaceable. The team keeps its habits and its code, and gains behaviour aimed at real networks. What genuinely requires new learning is the network model itself — how routing between sites works, where a relay or gateway belongs, what happens to a subscriber that goes away for a while — and that learning is unavoidable regardless of which technology you pick, because the difficulty belongs to the problem rather than the tool. The mistake to avoid is jumping to an unfamiliar stack in the belief that a fleet's troubles come from the middleware brand. They usually come from the network and from behaviour written for a robot that was never alone.

What do you give up by choosing a fleet-first middleware?

You give up simplicity on the single robot, and you pay for it every day whether or not the fleet is running. A transport built for wide-area links adds concepts: routers or relays to place, configuration that differs per site, another moving part in the deployment, and a second thing to check when a message does not arrive. For a lab with a few robots on one switch, that overhead buys nothing. You also give up some of the certainty that came with a controlled network. Once the design assumes robots come and go, behaviour becomes conditional — a task may be handed off, retried, or completed alone — and conditional behaviour is harder to test and harder to explain to a customer. The third cost is diagnostic. On one machine you can watch every message flow past; across a fleet, the interesting failures are the ones where two robots disagreed about the world, and reconstructing that after the fact needs logging you deliberately built. None of these are reasons to avoid a fleet transport. They are reasons not to adopt one before the fleet needs it.

When is ROS 2 the better choice?

ROS 2 is the better choice for essentially every multi-robot project, and this is not a close call. The moment robots on separate machines must exchange anything — poses, maps, task hand-offs, a shared picture of an area — you need a network middleware with a message model, a type system and tooling that spans machines, and ROS 2 has all three plus the largest catalogue of components that already speak the same types. HORUS is not the answer to this question and does not try to be: shared memory works between processes on one computer, so a single-machine middleware has nothing to say about the link between two robots. ROS 2 also wins whenever the fleet includes hardware from different vendors, since the common ground between them is usually a ROS 2 driver, and whenever people other than the original team will maintain the system, because the standard stack is the one a new engineer already knows. The only part of a multi-robot project where the question is genuinely open is what runs inside each individual robot.

Can one middleware carry both inside-the-robot and between-robot traffic?

No, and here is why: the two jobs have opposite requirements, and anything doing both is compromising on one. Inside a robot, messages move between programs on one machine, delivery is certain, and the only thing that matters is that data arrives before the next control cycle needs it, so the arm stops before it hits the table. Between robots, delivery is not certain, ordering is not certain, and the design problem is what happens when messages stop arriving for a while. A layer tuned for the first case treats absence as a fault; a layer tuned for the second treats absence as normal and pays a cost in bookkeeping that a single machine should never pay. You can run one technology in both places, and many teams do, which is why they end up hardening a single-machine arrangement into something the network can survive. The cleaner arrangement is deliberate: something fast and simple inside each robot, something network-shaped between them, and one clearly defined component that translates. The translation point is also where you put logging, because it is the only place that sees both worlds.

Do we need a central server to coordinate the fleet?

Partly, but not the way you think. Most fleets do want a central place, and it should be for assignment and observation rather than for message passing. Somebody has to decide which robot takes which job, hold the list of what has been done, and show an operator the state of everything at once — and trying to do that with no central component means implementing agreement between robots, which is a genuinely hard problem you do not want on your critical path. What the central place should not be is the route every message travels. A design where robots relay live data through a server inherits the server's failures: an outage stops the fleet, and a slow link turns into hesitant robots. The workable split is that coordination decisions go through a central component that robots can survive losing, while anything time-critical stays local to the robot that needs it. Teams get this wrong in a specific way: they start with a server for convenience, then quietly move real-time behaviour into it because the plumbing already exists.

How do we decide what to build the fleet on?

Answer one question first: what must the robots agree about? Write the list. If the list is empty — each robot does its own job and only reports what happened — you need a broker and a fleet-management layer, and nothing about the robot's internal stack has to change. If the list contains slow-moving shared knowledge such as a map or a task queue, a broker still works, with care about what happens when a robot missed an update. If the list contains anything live — one robot reacting to another's position while both are moving — you need a network middleware built for that, and ROS 2 over a routed transport is the mainstream answer. Then ask the second question separately: what runs inside each robot, given how many computers and languages it contains? Keeping those two questions apart is the single most useful habit in this area, because most confused fleet architectures come from answering both with one tool. The same separation shows up when research stacks become production stacks.

A short version, by situation:

When you want to compare options rather than symptoms, the HORUS Fit Framework lines them up on five things that are not numbers: ecosystem size, setup effort, team size fit, deployment target, and licence. For fleets, deployment target does most of the work, because a robot that leaves the building is a different target from one that never does, and setup effort is what you pay again at every site.

HORUS is open source under Apache-2.0 and the repository is linked below. Star it so it is in your list when you start building.

Found this useful? Share it:Discuss on HNShare on X