Peppy is a modern robotics middleware framework designed for robots. Similar to ROS 2, it provides a distributed communication layer for robotic systems with a big focus on ease of use and explicit configuration.
Full documentation lives at docs.peppy.bot.
🛠️ Installation
curl -fsSL https://peppy.bot/install.sh | shThe installer puts the peppy CLI on your PATH, registers the background service that builds and supervises nodes (systemd on Linux, launchd on macOS), and configures the Apptainer container runtime used by container nodes. Check that both the CLI and the service are up with:
Peppy runs on Ubuntu 24.04 or later and the Linux distributions built on it, on Debian 13 or later (x86_64/aarch64, tested on Ubuntu 24.04), on Raspberry Pi OS (64-bit, based on Debian 13 or later), and on macOS (Apple Silicon). Of the Raspberry Pi boards, it runs on the Raspberry Pi 5, the Pi 500 and the Compute Module 5. Other Linux distributions, Fedora and Arch Linux among them, are not supported, and neither are Ubuntu 22.04, Debian 12, 32-bit systems and the older Raspberry Pi boards: the installer stops on them before it changes anything.
The installation guide covers version pinning (PEPPY_VERSION), skipping the service install (PEPPY_NO_SERVICE_INSTALL), and managing the service afterwards.
🤖 Try it
The quickstart takes you from nothing installed to a simulated bimanual OpenArm you can drive from your browser, without cloning a repository or writing any code:
peppy stack launch openarm_simulation --with mujoco
That single command adds, builds, and starts every node in the selection, in dependency order, under the background service.
🚀 Key Features
- Real-time Communication: Low-latency message passing between nodes thanks to Zenoh
- Three interface kinds: topics for pub/sub streams, services for request/response, and actions for long-running goals with feedback and cancellation
- Quality of Service: Configurable reliability and performance settings
- Language Agnostic: Support for Python and Rust (more languages will be supported in the future with
Cbeing the next on the list) - Cross-platform: Linux and macOS support
- Supervised node stacks: A background daemon builds, starts, health-checks, and restarts the nodes of a stack; launch files declare a whole robot in one document
- Container nodes: Nodes can ship as containers built and run on Apptainer, with no change to how they are wired
- Node repositories: Resolve nodes and launchers by name from repositories you or others publish
- Explicit over implicit: Every node communication or feature of the framework is controlled through explicit configuration. This allows things like output messages of one node to break the code of other nodes depending on it to avoid implicit crashes at runtime.
- Not opinionated on build tools: Peppy doesn't force any tool upon its developers. The
peppyliblibrary however, is available only in a few supported languages.
Non-goals:
- We do not aim for API stability between releases until 1.0, preferring to iterate quickly and refine the API as much as possible. But we do follow SemVer.
🧰 The CLI
| Command group | What it does |
|---|---|
peppy node |
Scaffold, add, build, run, inspect, and stop individual nodes |
peppy stack |
Launch or build a stack from a launcher, list what is running, benchmark interface latency |
peppy repo |
Manage the repositories nodes and launchers are resolved from |
peppy container |
Check and repair the Apptainer container prerequisites |
peppy platform |
Sign in to the platform, enroll this machine in a project's cloud router, and show its status |
peppy service |
Install, serve, stop, uninstall, and reset the background service |
peppy info |
Print the CLI version, container setup, and daemon info |
peppy --version |
Print the CLI version alone, without contacting the daemon |
📚 Documentation
- Quickstart: drive a simulated bimanual robot in three steps
- Creating your first node: write and run a node from scratch, in Rust or Python
- Communication and choosing a pattern: wiring nodes together
- Concepts: nodes, instances, contracts, and the stack, defined
- Daemon configuration: the
peppy_config.json5reference, including running your own Zenoh router instead of the one Peppy manages - Changelog: release notes
LLM-friendly versions of the documentation are available at /llms.txt and /llms-full.txt.
🔨 Building from source
You only need this to work on Peppy itself. To use Peppy, install it with the command above.
New machine setup
Peppy builds apptainer and its bundled squashfuse from source, so the host needs their build dependencies. On Ubuntu:
sudo apt-get install -y make gcc g++ pkg-config squashfs-tools cryptsetup curl ca-certificates \
libseccomp-dev libfuse3-dev zlib1g-dev liblzo2-dev liblz4-dev liblzma-dev libzstd-dev fuse2fs uidmap
That list mirrors APPTAINER_BUILD_DEPS in crates/containers-internal/build.rs, which the build script asserts before it starts, plus three the constant does not carry: fuse2fs, which apptainer needs at run time to mount EXT3 images; uidmap, which provides the newuidmap that fakeroot needs; and g++, which apptainer's mconfig probes for and which Ubuntu's gcc package does not pull in. macOS builds apptainer inside Lima instead, so the host needs none of them.
Then the toolchains, each from its own project's recommended installer:
- Rust, with clippy (rustup). Track current stable: the dependency graph moves with it. clippy is a test dependency rather than a lint here, because the generator suites run
cargo clippy --all-targets -- -D warningsover the crates they generate. - Go (go.dev).
containers-internalcompiles apptainer with thegoit finds on PATH, and apptainer'smconfigenforces a minimum of its own that Ubuntu'sgolang-godoes not meet. - pixi (pixi.sh), at or above the
requires-pixiinscripts/pixi.toml.generator-internal's build script builds the embedded peppylib.sothrough a barepixi run. - uv (astral.sh/uv). The Python generator tests and the release scripts exec a bare
uv. - qemu (
apt, or Homebrew with Lima on macOS). The release-script tests boot Lima VMs. - Docker, with buildx (
apton Ubuntu; on macOS choose your own runtime). The multi-daemon suite builds its daemon image withdocker buildx build. - Node.js, only for the docs site in
docs/; itspackage.jsonnames the major it wants.
When Go is installed from the tarball, add /usr/local/go/bin to your ~/.profile. Docker group membership only applies to new logins, so log out and back in before running the multi-daemon suite.
CI runners
CI runs on RunsOn runners: each job gets an EC2 instance of its own, sized by the job's runs-on: label, and the instance is discarded when the job ends. The CI jobs shape the org runner with labels in their workflow; .github/runs-on.yml defines only the arm64 runner, which the aarch64 release stages and the aarch64 install archive of a pull request build on, since RunsOn reads that file from dev alone. The exceptions are the install.sh tests, which run on GitHub-hosted runners because they must start from the stock image a user's machine starts from, and the macOS release stages, which run on Blacksmith because RunsOn has no macOS. Every RunsOn job boots peppy-ubuntu24, or on the aarch64 jobs its arm64 build peppy-ubuntu24-aarch64. Both are built weekly with setup_machine.sh --ci-runner, the script that provisions a development machine, so they carry everything listed above, plus two things beyond it:
- passwordless sudo for the runner's user. Each container suite installs a peppy release under the job's own directory and runs
peppy container setup, which writes an AppArmor profile keyed to that install's path, and a job has no terminal forsudoto prompt at. - the aarch64 cross toolchain (
rustup target add aarch64-unknown-linux-gnuandgcc-aarch64-linux-gnu) that the cross-check job builds with.
The workflows install nothing on those images: the first step of every cargo job stops the run naming any tool that is missing. A cargo suite keeps its build cache (its target directory, the cargo registry and the build scripts' tool cache in ~/.peppy/tmp) on a sticky disk of its own, which RunsOn restores from the newest snapshot of the same branch, or of dev for a branch's first run, and snapshots again after the job. A job that finds its disk more than half full empties the caches first and builds cold, so a build always has room.
Build
Install cargo & Rust if you have not already, then keep the toolchain current with:
To build Peppy, just type the following:
cargo build --release --all-targets
The public-facing crates (config, peppylib, pmi, and friends) live in peppy-shared/, a sealed tree this workspace depends on by path. The tree is a workspace of its own, excluded from this one, and the dependency runs one way only: nothing in it may depend on crates/, which is what lets platform-backend consume it on its own. peppy-shared/build-helpers/tests/sealed_tree.rs holds every manifest and symlink in the tree to that rule, and CI runs the tree's suites from a checkout holding nothing but the tree, so a reach a manifest cannot show has nothing to resolve to.
Test
The workspace's default-members covers crates/* only, so the slow documentation integration tests are excluded from that run. Run them explicitly with:
cargo test -p docs-integration-tests
The peppy-shared tree holds a workspace of its own, so its suites run from there:
cd peppy-shared
cargo test --workspace --exclude peppylib-py --locked
CI runs all of these, plus the feature-gated container and multi-daemon end-to-end suites and the release-scripts tests. See .github/workflows/tests.yml for the exact commands.
📄 License
Peppy is licensed under the Business Source License 1.1. You may make production use of it, provided that use does not include offering a product or service to third parties whose value derives primarily from Peppy. On 2031-01-01 the license converts to Apache License, Version 2.0.
The peppy-shared/ directory is licensed separately, under the Apache License, Version 2.0.