softmata/horus

Fastest Robotics Runtime System. If phones have Android, robots deserve HORUS.

435

stars

390

commits

Rust

primary language

Sep 7, 2026

updated

docs.horusrobotics.dev/
artificial-intelligence
automation
autonomous-vehicles
distributed-systems
drivers
drones
framework
humanoid
humanoid-robot
middleware
python
real-time
robotframework
robotics
robotics-programming
robots
ros
ros2
rust
Browse cluster: Rust for Robotics and ROS2

README

HORUS

English · 简体中文 · Português (Brasil) · 日本語 · Español · Deutsch

Real-time distributed middleware for Rust, Python, and C++. Sub-200ns IPC.

CI Version Rust Python C++ License Discord

Docs · Quick Start · Benchmarks · Coming from ROS2? · Discord


Get Started

curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | bash
horus new my_robot && cd my_robot && horus run

Or install manually:

git clone https://github.com/softmata/horus.git && cd horus && ./install.sh

Requires Rust 1.90 or newer (rustup update stable). Python 3.9+ for the Python API; CMake 3.16+ and a C++17 compiler for the C++ API.

Python: pip install horus-robotics · C++: link against libhorus_cpp and #include <horus/horus.hpp>


Why HORUS?

HORUS is a real-time distributed middleware that replaces DDS with shared-memory ring buffers and lock-free synchronization. Built for any system where latency, determinism, and safety matter — robotics, industrial automation, autonomous vehicles, trading systems, game engines, and more.

HORUSROS2
IPC latency3–304 ns median (topology-dependent)50–500 µs (DDS)
SchedulingDeterministic, 5 execution classesBest-effort callbacks
RT supportBuilt-in (budget, deadline, watchdog)Manual DDS QoS
SafetyGraduated watchdog, safe-state hook, BlackBoxApplication-level
AI + RTSame process — AsyncIo for GPU, RT for motorsSeparate processes
GPU tensorsDLPack zero-copy (PyTorch/JAX native)Serialize → deserialize
LanguagesRust + Python + C++ (same shared memory)C++ + Python (DDS serialization)
ConfigSingle horus.tomlpackage.xml + CMakeLists.txt + launch files
Setuphorus new && horus runcolcon build + source install + launch

Quick Start

Rust — 1kHz motor controller in one file:

use horus::prelude::*;

message! {
    SensorReading { position: f64, velocity: f64 }
    MotorCommand  { voltage: f64 }
}

struct Sensor {
    reading: Topic<SensorReading>,
    pos: f64,
}

impl Sensor {
    fn new() -> Result<Self> {
        Ok(Self { reading: Topic::new("sensor.data")?, pos: 0.0 })
    }
}

impl Node for Sensor {
    fn name(&self) -> &str { "sensor" }
    fn tick(&mut self) {
        self.pos += 0.01;
        self.reading.send(SensorReading { position: self.pos, velocity: 0.5 });
    }
}

struct Controller {
    sensor: Topic<SensorReading>,
    cmd: Topic<MotorCommand>,
    target: f64,
}

impl Controller {
    fn new() -> Result<Self> {
        Ok(Self {
            sensor: Topic::new("sensor.data")?,
            cmd: Topic::new("motor.cmd")?,
            target: 1.0,
        })
    }
}

impl Node for Controller {
    fn name(&self) -> &str { "controller" }
    fn tick(&mut self) {
        if let Some(s) = self.sensor.recv() {
            self.cmd.send(MotorCommand { voltage: (self.target - s.position) * 0.5 });
        }
    }
}

fn main() -> Result<()> {
    let mut sched = Scheduler::new().tick_rate(1000_u64.hz());
    sched.add(Sensor::new()?).order(0).build()?;
    sched.add(Controller::new()?).order(1).rate(1000_u64.hz()).on_miss(Miss::SafeMode).build()?;
    sched.run()
}

You need three concepts to read that: a node (a struct with a tick()), a topic (Topic::new("sensor.data")), and the scheduler that runs them. Everything else in main() is timing policy and can wait: tick_rate() sets the scheduler's clock (default 100 Hz), .order() sequences nodes within a tick (default 0), .rate() moves a node onto its own real-time thread, and .on_miss() says what to do when a node overruns its deadline. Delete all four and the program still runs — every node ticks best-effort at 100 Hz. Add them back when timing matters. Execution classes →

Prefer less boilerplate? The node! macro writes the struct and impl Node for you. horus new --macro scaffolds a starter in that style — a single Controller publishing Twist on motors.cmd_vel, not the two-node example above.

Python — same robot, 8 lines:

import horus

def sensor_tick(node):
    node.send("sensor.data", {"position": sensor_tick.pos, "velocity": 0.5})
    sensor_tick.pos += 0.01
sensor_tick.pos = 0.0

def controller_tick(node):
    s = node.recv("sensor.data")
    if s is not None:
        node.send("motor.cmd", {"voltage": (1.0 - s["position"]) * 0.5})

horus.run(
    horus.Node(name="sensor", pubs=["sensor.data"], tick=sensor_tick, rate=1000),
    horus.Node(name="ctrl", subs=["sensor.data"], pubs=["motor.cmd"], tick=controller_tick, rate=1000),
)

node.recv(topic) returns None when nothing is waiting. node.has_msg(topic) asks the same question without consuming the message — the reading is held and handed to the next recv(). horus new --python scaffolds a one-node starter that uses both.

pubs= and subs= accept either a list of topics, as above, or a single bare topic string: pubs="motor.cmd" and pubs=["motor.cmd"] build the same node. Both spellings are in circulation across the docs, so a bare string in an example is not a typo.

C++ — same robot, idiomatic API:

#include <horus/horus.hpp>
using namespace horus::literals;

// Struct-based node with built-in pub/sub (like Rust's impl Node)
class Controller : public horus::Node {
public:
    Controller() : Node("controller") {
        sensor_ = subscribe<horus::msg::CmdVel>("sensor.data");
        motor_  = advertise<horus::msg::CmdVel>("motor.cmd");
    }

    void tick() override {
        auto s = sensor_->recv();
        if (!s) return;
        horus::msg::CmdVel cmd{};
        cmd.linear = (1.0f - s->get()->linear) * 0.5f;
        motor_->send(cmd);
    }

    void enter_safe_state() override { /* stop motors */ }

private:
    horus::Subscriber<horus::msg::CmdVel>* sensor_;
    horus::Publisher<horus::msg::CmdVel>*  motor_;
};

int main() {
    horus::Scheduler sched;
    sched.tick_rate(1000_hz);

    horus::Publisher<horus::msg::CmdVel> sensor_pub("sensor.data");

    sched.add("sensor").order(0)
        .tick([&] {
            auto out = sensor_pub.loan();
            out->linear = 0.5f;
            sensor_pub.publish(std::move(out));
        }).build();

    Controller ctrl;
    sched.add(ctrl).order(1).on_miss(horus::Miss::SafeMode).build();

    sched.spin();
}

horus::log::info(node_name, message) writes to the HORUS log stream rather than stdout, so horus log sees it. horus new --cpp scaffolds a one-node starter that uses it.

All three languages share the same topics over shared memory — zero overhead between Rust, Python, and C++.


Features

Deterministic Scheduling

Five execution classes — the scheduler auto-selects based on your configuration:

sched.add(motor).order(0).rate(1000.hz()).on_miss(Miss::SafeMode).build()?;  // RT
sched.add(planner).compute().build()?;                                        // Thread pool
sched.add(estop).on("emergency.stop").build()?;                               // Event-driven
sched.add(detector).async_io().build()?;                                      // GPU / network I/O
sched.add(logger).build()?;                                                   // Best-effort

Set .rate(), .budget(), or .deadline() and RT is automatic — no manual thread management. Learn more →

Safety

The scheduler monitors every node at runtime:

  • Graduated watchdog — warn → halve the rate → isolate → kill, at 3, 5, 10 and 20 consecutive misses by default. Recovery walks back down: 100 clean ticks de-isolate, 100 more restore the original rate. A killed node stays stopped.
  • Deadline enforcement.budget() and .deadline() with miss policies (Warn, Skip, SafeMode, Stop)
  • enter_safe_state() — you define what "safe" means per node (stop motors, close valves)
  • BlackBox flight recorder — ring-buffer event log for post-mortem crash analysis
  • Fault tolerance — per-node failure policies (restart with backoff, skip, fatal)

Miss::SafeMode is a hook, not a state machine. The scheduler calls enter_safe_state() once, on the transition into safe mode, and the node keeps ticking afterwards — it is not isolated, and is_safe_state() is never polled. So tick() has to go on publishing the safe outputs itself; a zeroed velocity command still has to be sent every cycle. The latch clears the first time the node meets its deadline again, which is what lets a later degradation be caught, so a flapping node is safed once per episode rather than once per miss. Isolating or stopping a node is the graduated watchdog's job, above.

Safety Monitor → · BlackBox → · Fault Tolerance →

Zero-Copy AI Pipeline

Run camera → YOLO → tracking → motor control in one process. 4K frames stay in shared memory; DLPack hands GPU tensors directly to PyTorch.

def detector_tick(node):
    frame = node.recv("camera")
    if frame is not None:
        tensor = torch.from_dlpack(frame)        # zero-copy GPU transfer
        for det in model(tensor):
            node.send("detections", horus.Detection(
                x=det.x, y=det.y, width=det.w, height=det.h,
                confidence=det.conf, class_name=det.label
            ))

8 built-in perception types: Detection, Detection3D, TrackedObject, SegmentationMask, Landmark, Image, PointCloud, CameraInfo. Learn more →

40+ Message Types · Services · Actions · Transforms

Everything you need for robotics, built-in:

// 40+ message types — all zero-copy Pod structs
let imu: Topic<Imu> = Topic::new("imu")?;
let cmd: Topic<CmdVel> = Topic::new("cmd_vel")?;

// Lock-free coordinate transforms (10-33x faster than ROS TF2)
let tf = TransformFrame::new();
tf.add_frame("laser").parent("base_link")
    .static_transform(&Transform::from_translation([0.2, 0.0, 0.1]))
    .build()?;

// Services (request/response) and Actions (long-running with feedback)
service! { AddTwoInts { request { a: i64, b: i64 } response { sum: i64 } } }
action!  { Navigate { goal { x: f64, y: f64 } feedback { dist: f64 } result { ok: bool } } }

Hardware Drivers

Declare hardware in horus.toml, access typed handles in code. 30+ Terra HAL drivers — Dynamixel, RPLiDAR, RealSense, CAN, EtherCAT, and more.

[drivers.arm]
terra = "dynamixel"
port = "/dev/ttyUSB0"
baudrate = 1000000

CLI

horus new my_robot              # scaffold project (Rust, Python, or C++)
horus new my_bot --cpp          # scaffold C++ project
horus run                       # build and run
horus topic list                # inspect live topics
horus topic echo camera.rgb     # watch messages (works across all languages)
horus monitor                   # TUI system dashboard
horus deploy pi@192.168.1.50    # deploy to robot
horus doctor                    # ecosystem health check

40+ commands. Full CLI reference →


Performance

Measured with RDTSC cycle counting, Tukey IQR outlier filtering, bootstrap 95% CIs on Intel i9-14900K. Full methodology →

TopologyHORUSMeasurement
Same-process pub/sub91 nsproducer-side send()
Cross-process171 nsend-to-end, one-way
1 pub → 3 subs80 nsproducer-side send()

Reproduce with cargo run --release --bin all_paths_latency, which prints the full percentile distribution, the backend selected for each topology, and the measured hardware floor it subtracts.

Against ROS 2. The nearest published figure is ROS 2's REP 2014 reference for default DDS, ~5 µs median for a 64-byte same-process message. Compared to HORUS's end-to-end cross-process 171 ns — the harder case for HORUS, and therefore the conservative comparison — that is roughly 30x. HORUS does not measure ROS 2 itself: dds_comparison_benchmark quotes published values unless built with -F dds and a DDS implementation installed, and results carry a provenance field marking them literature rather than measured so the two are never confused. Any number here that matters to your decision is worth measuring on your own hardware and message sizes.

vs iceoryx2HORUSiceoryx2Speedup
Same-thread11 ns69 ns6.3x
Cross-process170 ns361 ns2.1x
Throughput95 M msg/s22 M msg/s4.3x

Unlike the ROS 2 row above, this one is measured on both sides: reproduce with cargo run --release --bin iceoryx2_comparison --features iceoryx2, which links iceoryx2 and times it in the same harness.

Scales near-linearly to 100 nodes (14% degradation) and O(1) to 1,000 topics.

cargo run --release -p horus_benchmarks --bin all_paths_latency    # run it yourself

Examples

10 working projects in examples/ — from differential drive to quadruped gait generation:

cargo run --example 01_hello_node     # your first node
cargo run --example 02_pub_sub        # topics and messages
cargo run --example 03_multi_rate     # multi-rate scheduling
cargo run --example 04_services       # request/response
cargo run --example 05_realtime       # RT with deadline enforcement

Full examples → · Tutorials → · ROS 2 bridge recipe →


Coming Soon

  • Embedded HORUSno_std runtime for STM32, ESP32, and other microcontrollers
  • HORUS–Zenoh Bridge — Distributed multi-machine deployments over Zenoh for seamless cloud-edge-robot communication
  • ROS2 Bridge — Bidirectional topic bridging between HORUS and ROS2

Architecture

horus/          Umbrella crate — prelude, universal types
horus_core/     Runtime — scheduler, nodes, topics, services, actions, safety monitor
horus_types/    Universal IPC types — math, diagnostics, time, generic
horus_cpp/      C++ bindings — extern "C" FFI, idiomatic C++17 headers (pool, params, TF, services, actions)
horus_py/       Python bindings (PyO3)
horus_manager/  CLI — build, run, test, deploy, monitor (40+ commands)
horus_sys/      Platform HAL — Linux, macOS
horus_net/      LAN replication — transparent cross-machine topics

# Separate packages (install via `horus install`):
# horus-tf         Coordinate frame transforms (lock-free, 10-33x faster than ROS2 TF2)
# horus-robotics   Standard robotics message types (CmdVel, Imu, LaserScan, 45+ types)
benchmarks/     Performance suite — latency, throughput, jitter, comparisons

Running HORUS on Real Hardware?

We'd love to hear from you. HORUS is validated in simulation — if you're running it on a real robot, your experience helps us improve.

Tell us (via GitHub Issues, Discord, or email):

  • What robot — platform, actuators, sensors
  • What control rate you're achieving on real hardware
  • What worked out of the box
  • What needed tuning — PID gains, sensor dropout thresholds, timing budgets
  • What broke — anything that works in sim but fails on hardware

We'll add validated hardware to the docs and credit contributors.


Report a Bug · Contributing · Discord · Apache-2.0

Contributors

neos-builder

341 commits

claude

33 commits

dependabot[bot]

14 commits

gokugohango

2 commits

softmata/horus

Fastest Robotics Runtime System. If phones have Android, robots deserve HORUS.

435

stars

390

commits

Rust

primary language

Sep 7, 2026

updated

docs.horusrobotics.dev/
artificial-intelligence
automation
autonomous-vehicles
distributed-systems
drivers
drones
framework
humanoid
humanoid-robot
middleware
python
real-time
robotframework
robotics
robotics-programming
robots
ros
ros2
rust
Browse cluster: Rust for Robotics and ROS2

README

HORUS

English · 简体中文 · Português (Brasil) · 日本語 · Español · Deutsch

Real-time distributed middleware for Rust, Python, and C++. Sub-200ns IPC.

CI Version Rust Python C++ License Discord

Docs · Quick Start · Benchmarks · Coming from ROS2? · Discord


Get Started

curl -fsSL https://github.com/softmata/horus/raw/main/install.sh | bash
horus new my_robot && cd my_robot && horus run

Or install manually:

git clone https://github.com/softmata/horus.git && cd horus && ./install.sh

Requires Rust 1.90 or newer (rustup update stable). Python 3.9+ for the Python API; CMake 3.16+ and a C++17 compiler for the C++ API.

Python: pip install horus-robotics · C++: link against libhorus_cpp and #include <horus/horus.hpp>


Why HORUS?

HORUS is a real-time distributed middleware that replaces DDS with shared-memory ring buffers and lock-free synchronization. Built for any system where latency, determinism, and safety matter — robotics, industrial automation, autonomous vehicles, trading systems, game engines, and more.

HORUSROS2
IPC latency3–304 ns median (topology-dependent)50–500 µs (DDS)
SchedulingDeterministic, 5 execution classesBest-effort callbacks
RT supportBuilt-in (budget, deadline, watchdog)Manual DDS QoS
SafetyGraduated watchdog, safe-state hook, BlackBoxApplication-level
AI + RTSame process — AsyncIo for GPU, RT for motorsSeparate processes
GPU tensorsDLPack zero-copy (PyTorch/JAX native)Serialize → deserialize
LanguagesRust + Python + C++ (same shared memory)C++ + Python (DDS serialization)
ConfigSingle horus.tomlpackage.xml + CMakeLists.txt + launch files
Setuphorus new && horus runcolcon build + source install + launch

Quick Start

Rust — 1kHz motor controller in one file:

use horus::prelude::*;

message! {
    SensorReading { position: f64, velocity: f64 }
    MotorCommand  { voltage: f64 }
}

struct Sensor {
    reading: Topic<SensorReading>,
    pos: f64,
}

impl Sensor {
    fn new() -> Result<Self> {
        Ok(Self { reading: Topic::new("sensor.data")?, pos: 0.0 })
    }
}

impl Node for Sensor {
    fn name(&self) -> &str { "sensor" }
    fn tick(&mut self) {
        self.pos += 0.01;
        self.reading.send(SensorReading { position: self.pos, velocity: 0.5 });
    }
}

struct Controller {
    sensor: Topic<SensorReading>,
    cmd: Topic<MotorCommand>,
    target: f64,
}

impl Controller {
    fn new() -> Result<Self> {
        Ok(Self {
            sensor: Topic::new("sensor.data")?,
            cmd: Topic::new("motor.cmd")?,
            target: 1.0,
        })
    }
}

impl Node for Controller {
    fn name(&self) -> &str { "controller" }
    fn tick(&mut self) {
        if let Some(s) = self.sensor.recv() {
            self.cmd.send(MotorCommand { voltage: (self.target - s.position) * 0.5 });
        }
    }
}

fn main() -> Result<()> {
    let mut sched = Scheduler::new().tick_rate(1000_u64.hz());
    sched.add(Sensor::new()?).order(0).build()?;
    sched.add(Controller::new()?).order(1).rate(1000_u64.hz()).on_miss(Miss::SafeMode).build()?;
    sched.run()
}

You need three concepts to read that: a node (a struct with a tick()), a topic (Topic::new("sensor.data")), and the scheduler that runs them. Everything else in main() is timing policy and can wait: tick_rate() sets the scheduler's clock (default 100 Hz), .order() sequences nodes within a tick (default 0), .rate() moves a node onto its own real-time thread, and .on_miss() says what to do when a node overruns its deadline. Delete all four and the program still runs — every node ticks best-effort at 100 Hz. Add them back when timing matters. Execution classes →

Prefer less boilerplate? The node! macro writes the struct and impl Node for you. horus new --macro scaffolds a starter in that style — a single Controller publishing Twist on motors.cmd_vel, not the two-node example above.

Python — same robot, 8 lines:

import horus

def sensor_tick(node):
    node.send("sensor.data", {"position": sensor_tick.pos, "velocity": 0.5})
    sensor_tick.pos += 0.01
sensor_tick.pos = 0.0

def controller_tick(node):
    s = node.recv("sensor.data")
    if s is not None:
        node.send("motor.cmd", {"voltage": (1.0 - s["position"]) * 0.5})

horus.run(
    horus.Node(name="sensor", pubs=["sensor.data"], tick=sensor_tick, rate=1000),
    horus.Node(name="ctrl", subs=["sensor.data"], pubs=["motor.cmd"], tick=controller_tick, rate=1000),
)

node.recv(topic) returns None when nothing is waiting. node.has_msg(topic) asks the same question without consuming the message — the reading is held and handed to the next recv(). horus new --python scaffolds a one-node starter that uses both.

pubs= and subs= accept either a list of topics, as above, or a single bare topic string: pubs="motor.cmd" and pubs=["motor.cmd"] build the same node. Both spellings are in circulation across the docs, so a bare string in an example is not a typo.

C++ — same robot, idiomatic API:

#include <horus/horus.hpp>
using namespace horus::literals;

// Struct-based node with built-in pub/sub (like Rust's impl Node)
class Controller : public horus::Node {
public:
    Controller() : Node("controller") {
        sensor_ = subscribe<horus::msg::CmdVel>("sensor.data");
        motor_  = advertise<horus::msg::CmdVel>("motor.cmd");
    }

    void tick() override {
        auto s = sensor_->recv();
        if (!s) return;
        horus::msg::CmdVel cmd{};
        cmd.linear = (1.0f - s->get()->linear) * 0.5f;
        motor_->send(cmd);
    }

    void enter_safe_state() override { /* stop motors */ }

private:
    horus::Subscriber<horus::msg::CmdVel>* sensor_;
    horus::Publisher<horus::msg::CmdVel>*  motor_;
};

int main() {
    horus::Scheduler sched;
    sched.tick_rate(1000_hz);

    horus::Publisher<horus::msg::CmdVel> sensor_pub("sensor.data");

    sched.add("sensor").order(0)
        .tick([&] {
            auto out = sensor_pub.loan();
            out->linear = 0.5f;
            sensor_pub.publish(std::move(out));
        }).build();

    Controller ctrl;
    sched.add(ctrl).order(1).on_miss(horus::Miss::SafeMode).build();

    sched.spin();
}

horus::log::info(node_name, message) writes to the HORUS log stream rather than stdout, so horus log sees it. horus new --cpp scaffolds a one-node starter that uses it.

All three languages share the same topics over shared memory — zero overhead between Rust, Python, and C++.


Features

Deterministic Scheduling

Five execution classes — the scheduler auto-selects based on your configuration:

sched.add(motor).order(0).rate(1000.hz()).on_miss(Miss::SafeMode).build()?;  // RT
sched.add(planner).compute().build()?;                                        // Thread pool
sched.add(estop).on("emergency.stop").build()?;                               // Event-driven
sched.add(detector).async_io().build()?;                                      // GPU / network I/O
sched.add(logger).build()?;                                                   // Best-effort

Set .rate(), .budget(), or .deadline() and RT is automatic — no manual thread management. Learn more →

Safety

The scheduler monitors every node at runtime:

  • Graduated watchdog — warn → halve the rate → isolate → kill, at 3, 5, 10 and 20 consecutive misses by default. Recovery walks back down: 100 clean ticks de-isolate, 100 more restore the original rate. A killed node stays stopped.
  • Deadline enforcement.budget() and .deadline() with miss policies (Warn, Skip, SafeMode, Stop)
  • enter_safe_state() — you define what "safe" means per node (stop motors, close valves)
  • BlackBox flight recorder — ring-buffer event log for post-mortem crash analysis
  • Fault tolerance — per-node failure policies (restart with backoff, skip, fatal)

Miss::SafeMode is a hook, not a state machine. The scheduler calls enter_safe_state() once, on the transition into safe mode, and the node keeps ticking afterwards — it is not isolated, and is_safe_state() is never polled. So tick() has to go on publishing the safe outputs itself; a zeroed velocity command still has to be sent every cycle. The latch clears the first time the node meets its deadline again, which is what lets a later degradation be caught, so a flapping node is safed once per episode rather than once per miss. Isolating or stopping a node is the graduated watchdog's job, above.

Safety Monitor → · BlackBox → · Fault Tolerance →

Zero-Copy AI Pipeline

Run camera → YOLO → tracking → motor control in one process. 4K frames stay in shared memory; DLPack hands GPU tensors directly to PyTorch.

def detector_tick(node):
    frame = node.recv("camera")
    if frame is not None:
        tensor = torch.from_dlpack(frame)        # zero-copy GPU transfer
        for det in model(tensor):
            node.send("detections", horus.Detection(
                x=det.x, y=det.y, width=det.w, height=det.h,
                confidence=det.conf, class_name=det.label
            ))

8 built-in perception types: Detection, Detection3D, TrackedObject, SegmentationMask, Landmark, Image, PointCloud, CameraInfo. Learn more →

40+ Message Types · Services · Actions · Transforms

Everything you need for robotics, built-in:

// 40+ message types — all zero-copy Pod structs
let imu: Topic<Imu> = Topic::new("imu")?;
let cmd: Topic<CmdVel> = Topic::new("cmd_vel")?;

// Lock-free coordinate transforms (10-33x faster than ROS TF2)
let tf = TransformFrame::new();
tf.add_frame("laser").parent("base_link")
    .static_transform(&Transform::from_translation([0.2, 0.0, 0.1]))
    .build()?;

// Services (request/response) and Actions (long-running with feedback)
service! { AddTwoInts { request { a: i64, b: i64 } response { sum: i64 } } }
action!  { Navigate { goal { x: f64, y: f64 } feedback { dist: f64 } result { ok: bool } } }

Hardware Drivers

Declare hardware in horus.toml, access typed handles in code. 30+ Terra HAL drivers — Dynamixel, RPLiDAR, RealSense, CAN, EtherCAT, and more.

[drivers.arm]
terra = "dynamixel"
port = "/dev/ttyUSB0"
baudrate = 1000000

CLI

horus new my_robot              # scaffold project (Rust, Python, or C++)
horus new my_bot --cpp          # scaffold C++ project
horus run                       # build and run
horus topic list                # inspect live topics
horus topic echo camera.rgb     # watch messages (works across all languages)
horus monitor                   # TUI system dashboard
horus deploy pi@192.168.1.50    # deploy to robot
horus doctor                    # ecosystem health check

40+ commands. Full CLI reference →


Performance

Measured with RDTSC cycle counting, Tukey IQR outlier filtering, bootstrap 95% CIs on Intel i9-14900K. Full methodology →

TopologyHORUSMeasurement
Same-process pub/sub91 nsproducer-side send()
Cross-process171 nsend-to-end, one-way
1 pub → 3 subs80 nsproducer-side send()

Reproduce with cargo run --release --bin all_paths_latency, which prints the full percentile distribution, the backend selected for each topology, and the measured hardware floor it subtracts.

Against ROS 2. The nearest published figure is ROS 2's REP 2014 reference for default DDS, ~5 µs median for a 64-byte same-process message. Compared to HORUS's end-to-end cross-process 171 ns — the harder case for HORUS, and therefore the conservative comparison — that is roughly 30x. HORUS does not measure ROS 2 itself: dds_comparison_benchmark quotes published values unless built with -F dds and a DDS implementation installed, and results carry a provenance field marking them literature rather than measured so the two are never confused. Any number here that matters to your decision is worth measuring on your own hardware and message sizes.

vs iceoryx2HORUSiceoryx2Speedup
Same-thread11 ns69 ns6.3x
Cross-process170 ns361 ns2.1x
Throughput95 M msg/s22 M msg/s4.3x

Unlike the ROS 2 row above, this one is measured on both sides: reproduce with cargo run --release --bin iceoryx2_comparison --features iceoryx2, which links iceoryx2 and times it in the same harness.

Scales near-linearly to 100 nodes (14% degradation) and O(1) to 1,000 topics.

cargo run --release -p horus_benchmarks --bin all_paths_latency    # run it yourself

Examples

10 working projects in examples/ — from differential drive to quadruped gait generation:

cargo run --example 01_hello_node     # your first node
cargo run --example 02_pub_sub        # topics and messages
cargo run --example 03_multi_rate     # multi-rate scheduling
cargo run --example 04_services       # request/response
cargo run --example 05_realtime       # RT with deadline enforcement

Full examples → · Tutorials → · ROS 2 bridge recipe →


Coming Soon

  • Embedded HORUSno_std runtime for STM32, ESP32, and other microcontrollers
  • HORUS–Zenoh Bridge — Distributed multi-machine deployments over Zenoh for seamless cloud-edge-robot communication
  • ROS2 Bridge — Bidirectional topic bridging between HORUS and ROS2

Architecture

horus/          Umbrella crate — prelude, universal types
horus_core/     Runtime — scheduler, nodes, topics, services, actions, safety monitor
horus_types/    Universal IPC types — math, diagnostics, time, generic
horus_cpp/      C++ bindings — extern "C" FFI, idiomatic C++17 headers (pool, params, TF, services, actions)
horus_py/       Python bindings (PyO3)
horus_manager/  CLI — build, run, test, deploy, monitor (40+ commands)
horus_sys/      Platform HAL — Linux, macOS
horus_net/      LAN replication — transparent cross-machine topics

# Separate packages (install via `horus install`):
# horus-tf         Coordinate frame transforms (lock-free, 10-33x faster than ROS2 TF2)
# horus-robotics   Standard robotics message types (CmdVel, Imu, LaserScan, 45+ types)
benchmarks/     Performance suite — latency, throughput, jitter, comparisons

Running HORUS on Real Hardware?

We'd love to hear from you. HORUS is validated in simulation — if you're running it on a real robot, your experience helps us improve.

Tell us (via GitHub Issues, Discord, or email):

  • What robot — platform, actuators, sensors
  • What control rate you're achieving on real hardware
  • What worked out of the box
  • What needed tuning — PID gains, sensor dropout thresholds, timing budgets
  • What broke — anything that works in sim but fails on hardware

We'll add validated hardware to the docs and credit contributors.


Report a Bug · Contributing · Discord · Apache-2.0

Contributors

neos-builder

341 commits

claude

33 commits

dependabot[bot]

14 commits

gokugohango

2 commits

Languages

Rust

93.2%

Python

2.5%

C++

2.2%

Shell

1.3%