HORUS/blog

Sep 5, 2026 · robotics-middleware · scaling-up · team-workflow · ros2

What Happens When You Outgrow Your Robotics Middleware?

Outgrowing a middleware is gradual, not sudden. Most teams need a restructure rather than a migration, and this post shows how to tell which one you need.

Outgrowing a robotics middleware is gradual rather than sudden, and the answer is one of three: restructure your processes, tune ROS 2, or change transport. Most teams who feel the ceiling have a scheduling problem wearing a transport costume, and restructuring fixes that for far less than a migration to HORUS or anything else costs. The verdict flips when the pain sits on one machine where several languages pass large data to each other. The rest of this post is for a team whose robot works but has started stuttering, arguing about start order, and losing an afternoon a week to plumbing.

It started as one of those weeks. The robot ran the same demo it has run for months, except this time the arm hesitated on the way down and everyone in the room saw it. Nobody had touched the arm code. Somebody had added a logger.

Then the list grew. Processes have to be started in a particular order now, there is a document that says so, and the document is already out of date. A message arrives with a timestamp older than the one before it and the filter reports a position the robot has never occupied. Someone adds a queue setting they found on a forum and the stutter moves somewhere else rather than going away. Two engineers disagree about whether the camera process is late or the network is late, and neither can prove it, because the tools you have show what the robot published rather than when your code actually got the data.

The uncomfortable part is not any single bug. It is that your bugs stopped being about the robot and started being about the plumbing, and nobody on the team can say whether that means you chose wrong at the start or simply grew.

What happens when you outgrow your robotics middleware?

Nothing breaks on a particular Tuesday; the robot keeps working while the cost of every change quietly climbs. Outgrowing shows up as a shift in where your time goes. A year ago the hard problems were about the world: the gripper slips on shiny objects, the wheels drift on carpet, the map wanders down a corridor with no features in it. Now the hard problems are about your own system: which process was late, why start-up order matters, what a subscriber should do with data that describes where the robot used to be. Teams read that shift as a failure of judgement, and it usually is not. Almost every robot stands on a foundation chosen when the robot was smaller than it is now, because nobody can choose well for a system they have not built yet. The useful move is to name the shift out loud, because a team that has not named it keeps paying for it in sleeps, retries and a start-up document nobody maintains. Naming it converts a vague sense that things got worse into a decision with options and a price.

What does robotics middleware actually do for a team?

Middleware carries messages between the separate programs that make up a robot and decides what happens when one of those programs is late. The second half is the part teams forget when they choose one. Any library can move bytes from a publisher to a subscriber on a good day. What you are really adopting is a set of default answers to unpleasant questions. Does a slow consumer hold up a fast producer, or does the producer discard what the consumer could not keep up with? Does a subscriber that joins late receive the most recent message or wait for the next one? When a publisher dies halfway through writing, what does the reader see? Which of two topics arrives first when both are ready? On a small robot those defaults never surface, because nothing is ever late. On a demanding robot they surface constantly, and a team with no shared vocabulary for them argues in circles, each engineer describing a different symptom of one unstated policy. Middleware is a pile of decisions with a library attached, and outgrowing one usually means outgrowing its decisions rather than its speed.

What are the actual options once the current setup hurts?

There are six honest moves and only one of them is a migration. You can stay on ROS 2 and change how it is configured, which is where every team should start, because it is the cheapest thing that could possibly work. You can leave the transport alone and restructure the processes instead, merging the ones that must share a rhythm and separating the one that blocks everything else. You can go hybrid, keeping ROS 2 at the edges for drivers and tooling while a tighter path carries the data your control loop depends on. You can replace the transport on that path with something built for it, such as HORUS, an open-source real-time robotics middleware for Rust, Python and C++ where all three languages share the same shared-memory ring buffers, so messages between processes on one machine are not serialised. You can write your own, which some good teams do and most later regret. Or you can accept the current behaviour and spend the effort elsewhere, which is a legitimate choice rather than a cowardly one.

Which option fits which kind of team?

The option that fits is the one that matches your situation, not the one that sounds most serious. Read the table as a description of teams rather than a ranking of software: the same robot can justify two different rows depending on who is available, what the next quarter demands, and whether anyone still remembers why the third process exists. Most teams sit on the first two rows for longer than they expect, and the teams who skip straight to the fourth row usually do so because a language boundary, not a message rate, is what hurts.

OptionWho it is forWhat it assumes you knowWhen to pick itWhen not to
Tune your existing ROS 2 setupTeams whose robot mostly worksYour current settings and which process is lateThe stutter is occasional and traceableYou have already tuned twice and it only moved
Restructure the processes you haveTeams whose diagram grew by accidentHow your loops are scheduled and who waits on whomSeveral processes exist for historical reasonsThe boundaries are already in the right places
Hybrid: ROS 2 outside, a tighter path insideTeams needing borrowed drivers and steady controlBoth stacks, and where your real boundary sitsThe ecosystem matters but one path is hurtingNobody has time to own two systems
HORUSMixed-language teams on a single machineYour message shapes and how loops are scheduledPython, C++ and Rust parts share data on one boxBorrowed packages are most of the robot
Write your own transportTeams with an unusual constraint and people spareConcurrency, memory ownership, failure modesNothing existing meets a hard requirementYou are doing it because it sounds enjoyable
A general message brokerTeams whose robot is mostly telemetryBrokers, topics, keeping a service aliveRemote commands and dashboards dominateA control loop depends on message ordering
Leave it aloneTeams shipping something that worksWhat your customers actually complain aboutThe pain is annoying rather than blockingSafety behaviour depends on timing you cannot explain

What should you do if you are a small product team with a shipping date?

Remove the symptom your customer can see and defer everything else until after the ship date. A small team with a date has one currency, which is attention, and a migration spends all of it on work that produces no new capability. Find the single worst behaviour — the hesitation in the arm, the moment the base drifts before it corrects — and trace which process was late when it happened. Nine times out of ten the fix is local: one process doing disk work inside a loop that must keep time, one subscriber holding a lock while it decodes an image, one setting inherited from a tutorial. Fix that, ship, and write down what you found. The reason to write it down is that the second and third occurrence tell you whether you have a collection of accidents or one structural problem, and you cannot tell from the first. Teams that migrate mid-quarter almost always discover afterwards that the original symptom survived the move, because the symptom belonged to their own scheduling rather than to the transport underneath.

What changes if the robot runs on one small computer instead of several?

Everything gets simpler, and the strongest argument for changing transport gets stronger. A robot spread across a network has to worry about discovery, dropped packets, clock differences between machines and what happens when the link degrades, and a mature networked stack earns its complexity there. A robot that is one board has none of those problems and pays for them anyway, because a network-shaped transport still copies and encodes data on its way between two processes that share memory a few centimetres apart. On a small computer that cost shows up in two places you can feel: heat, and the number of processes you can afford before start-up becomes slow enough that people test less often. Testing less often is how projects quietly die. So on a single board, the two moves that pay are reducing the number of processes to the number you actually need, and shortening the path between the two processes that exchange the most data. Both are cheaper than a migration and both are reversible, which is what you want while you are still learning where the real cost sits.

What if you have three months before the next demo?

Three months is enough to change one thing properly and not enough to change two. Pick by asking what the demo fails on if nothing changes. If the demo fails because the robot hesitates under load, spend the time on scheduling and process structure, because those produce visible improvement early and can be stopped halfway without leaving the system broken. If the demo fails because you cannot get a new sensor into the system at all, spend the time on the integration and leave the transport alone. If the demo fails because two languages cannot exchange data without a copy that ruins your timing, that is the one case where three months is enough to move a single path onto a different transport and leave the rest untouched. Whatever you choose, insist on a state where the robot works at the end of every week. Migrations that require a long broken stretch have a way of colliding with the exact week a customer visits, and a half-finished transport change is harder to explain than the stutter you started with.

What if nobody on the team has replaced a transport before?

Start by making the problem visible, because a team that has never done this will otherwise argue from intuition. The first piece of work is not code; it is instrumentation that shows when each stage of your pipeline received data and when it finished. Almost every team that does this discovers something surprising, and roughly half discover their transport is innocent. If the transport is guilty, the second piece of work is a small experiment on one path rather than a plan for the whole robot: one publisher, one subscriber, the real message sizes, running beside the existing system rather than instead of it. That experiment teaches you the things documentation cannot — how the build fits your existing project, what breaks when a process dies, whether your Python side can keep up. Only then is a plan worth writing. The failure mode for inexperienced teams is not choosing the wrong option; it is committing to a whole-system change before anyone has felt what the new option is like on a Wednesday afternoon when something goes wrong.

What do teams try first, and why does it stop working?

Most teams first reach for settings, and settings genuinely help, right up to the point where they stop. Queue depths get increased so nothing is dropped, which converts a dropped-message problem into a stale-message problem and the robot starts acting on information about the past. Threads get added around the slow node, which helps until two threads want the same lock during the one moment that matters. A sleep appears somewhere to let another process get ready, and it works, and nobody removes it, and eighteen months later that sleep is load-bearing. Each of these is a local fix for a global property, and the global property is that your system has no agreed policy for what happens when a producer outruns a consumer. Once four or five local fixes coexist, they interact, and you get the behaviour teams describe as flaky: a fault that appears when the fan spins up, or on the fourth run, or only with the logger enabled. That is the point at which people start asking whether they have outgrown their first framework, and it is a fair question to ask.

What do you give up by moving off your current middleware?

You give up other people's work, and that is the largest asset in robotics. Depending on where you move, that can mean mapping and navigation you did not write, drivers for sensors you have not bought yet, a visualiser that shows what the robot believes about the world, recording and replay so you can debug last Tuesday at your desk, and a decade of forum answers from people who hit your problem first. You also give up onboarding. A new engineer who knows the common stack can read a robot built on it during their first week and cannot read your private architecture at all, and that cost lands every time you hire. Finally, you give up a certain kind of argument-ending authority: when something behaves oddly, the ecosystem answer is a search away, and the bespoke answer is a person on your team who may be on holiday. What you get back is a smaller system to hold in your head, a start-up sequence you can explain in one sentence, and a timing story you can actually reason about.

When is ROS 2 the better choice?

ROS 2 is the better choice whenever the value of your robot lives in code your team did not write. If mapping, navigation or motion planning is the reason your robot is worth anything, the packages are the product and the transport underneath is an implementation detail you should stop thinking about. If your system spans several machines — a robot and a workstation, or a fleet — ROS 2 was designed for that world and is well travelled there. If your team hires from a pool where everyone already knows the tooling, shared vocabulary beats a better transport on almost any week you can name. If a sensor you depend on has one usable driver and that driver is a package, the decision is made for you. HORUS is not the answer for those projects, and choosing it there trades a working ecosystem for plumbing you would then have to rebuild and maintain yourself. Teams that make this mistake usually do so after one bad month, and the honest question to ask during that month is whether you dislike your middleware or your own architecture.

Is the middleware the reason your robot stutters?

No, and here is why: the stutter almost always originates in scheduling, and the transport is simply where the delay becomes visible. A control loop that must keep a steady rhythm shares a machine with a vision process that allocates memory in bursts, a logger that writes to disk, and an operating system that was never told which of those matters most. When any of them takes the processor at the wrong moment, the control loop misses its slot, and the evidence surfaces as a message that arrived after it was useful — which looks exactly like a transport fault from where you are standing. The distinction changes the fix entirely. If the cause is scheduling, replacing the transport buys you a fresh set of unknowns and the same stutter. If the cause really is the path between processes, you will see the delay grow between one process finishing and the next one starting, with both processes running promptly. Instrument before you decide, because these two diagnoses look identical from a log and need opposite responses.

Will changing the transport mean rewriting the robot?

Partly, but not the way you think. The part you rewrite is the plumbing: how processes are launched, how topics are addressed, how a subscriber decides that data has become too old to act on, and the tooling that reads your recordings. That is genuine work, typically a few weeks for a small robot, and it is tedious rather than dangerous. What you do not rewrite is the part that took months to earn: the control logic, the calibration, the state machine that knows the gripper actually has the object, the constants somebody found by breaking things on a Friday. That knowledge lives in your functions, not your transport, unless you let the transport into your functions. Which points at the discipline that makes any future move cheap. Keep control logic in plain functions that take data and return commands rather than functions that also publish. Define message shapes in one place. Keep hardware-touching code apart from deciding code. Teams that did those three things treat a transport change as mechanical; teams that did not find that rewriting becomes the only option regardless of which foundation they leave.

How do you decide what to do next?

Decide by naming the single behaviour that would make the robot acceptable, then ask which option removes that behaviour soonest. If the answer is a setting, you did not outgrow anything and you have a good week ahead. If the answer requires moving two processes together or splitting one apart, you have an architecture problem and no transport will fix it for you. If the answer is that two languages cannot hand each other data without a copy that ruins your timing, you have the one problem a different transport genuinely solves, and it is worth the disruption. Ask a second question to break ties: how many machines will this robot be in a year, and how many languages will it speak? One machine and several languages is where a shared-memory transport stops being a nicety. Several machines and one language is where the mature ecosystem earns its complexity. Teams that work through this in the open tend to reach a decision faster than teams that argue, which is why how other teams decided to switch is worth reading before you commit.

Decide by situation rather than preference:

When the choice is close, weigh it on the five axes of the HORUS Fit Framework — ecosystem size, setup effort, team size fit, deployment target, and licence — and take the option that loses on the fewest. No score and no numbers: five honest questions about your team rather than about the software. If your answers keep landing on one machine, more than one language, and behaviour only your team understands, star HORUS on GitHub so it is in your list when you start building the next version.

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