Skip to content

Node communication

A node by itself isn’t very useful without the ability to communicate with other nodes in the node stack. Each node is only aware of the interfaces it exposes and the ones it subscribes to, as defined in its peppy.json5 configuration. For example:

{
schema_version: 1,
manifest: {
name: "controller",
tag: "0.1.0",
language: "python",
},
process: {
add_cmd: ["uv", "sync"],
start_cmd: ["uv", "run", "controller"]
},
interfaces: {
exposes: {
topics: [],
services: [],
actions: [],
},
subscribes_to: {
topics: [],
services: [],
actions: [],
},
}
}

Here we have interfaces with exposes and subscribes_to containing topics, services, and actions. These interfaces define the dependencies between nodes.

PeppyOS provides three primary communication patterns for nodes to exchange data:

  • Topics are used for continuous, unidirectional data streams. A node publishes messages to a topic, and any number of nodes can subscribe to receive them. This is ideal for sensor data, state updates, or any information that flows continuously (e.g., camera images, odometry).

  • Services implement a request-response pattern. A node connects to another node and waits for a response. Use services for quick operations that need a result, like querying a node’s state or triggering a one-time computation.

  • Actions are for long-running tasks that need feedback and cancellation support. A client sends a goal to an action node, which provides periodic feedback during execution and a final result upon completion. Actions are built on top of topics and services internally. Use them for tasks like navigation or arm movement. Actions are processed serially by a node: two requests to the same action cannot run in parallel. For example, a move_arm action that moves a robot’s arm can only perform one movement at a time.

In our hello_world_param node, we’ve already exposed a topic. Let’s try to make this topic communicate with another node.

We first need to initialize the hello_receiver node in a new folder:

  1. Initialize the node:

    Terminal window
    peppy node init --toolchain uv hello_receiver
  2. Navigate into the directory:

    Terminal window
    cd hello_receiver

with the following peppy.json5:

peppy.json5
{
schema_version: 1,
manifest: {
name: "hello_receiver",
tag: "0.1.0",
language: "python",
},
process: {
add_cmd: [
"uv",
"sync"
],
start_cmd: [
"uv",
"run",
"hello_receiver"
]
},
interfaces: {
subscribes_to: {
topics: [
{
id: "hello_world_param_subscriber", // subscriber_id, up to you to decide
node: "hello_world_param", // Will look for that node name
tag: "0.1.0", // On that particular tag/version
name: "message_stream", // With this topic name
}
],
}
}
}

and the following source file:

src/hello_receiver/__main__.py
import asyncio
from peppygen import NodeBuilder, NodeRunner
from peppygen.parameters import Parameters
from peppygen.subscribed_topics import hello_world_param_message_stream
async def setup(_params: Parameters, node_runner: NodeRunner) -> list[asyncio.Task]:
return [asyncio.create_task(receive_messages(node_runner))]
async def receive_messages(node_runner: NodeRunner):
while True:
(
instance_id,
message,
) = await hello_world_param_message_stream.on_next_message_received(node_runner)
print(f"Received from {instance_id}: {message.message}")
def main():
NodeBuilder().run(setup)
if __name__ == "__main__":
main()

Finally, add this new node to the stack:

  1. Sync the node interfaces:

    Terminal window
    peppy node sync
  2. Add the node to the stack:

    Terminal window
    peppy node add .

Now we need to make sure our nodes are started. If we take a look at our node stack:

Terminal window
peppy stack list
Listing nodes...
Requesting node stack graph from core 'sweet-germain-4388'...
Node stack:
- sweet-germain-4388:core-node (/Users/tuatini/workspace/peppy) (1 instance: ["brave-zhukovsky-6918"])
- hello_receiver:0.1.0 (/Users/tuatini/.peppy/nodes/hello_receiver_0.1.0_d41475) (0 instances: [])
- hello_world_param:0.1.0 (/Users/tuatini/.peppy/nodes/hello_world_param_0.1.0_9d5fa0) (0 instances: [])
Dependencies:
- hello_receiver:0.1.0 -> hello_world_param:0.1.0

We need to make sure that at least one instance is started for hello_receiver:0.1.0 and another one for hello_world_param:0.1.0. The order in which the nodes are started does not affect the node communication. Let’s start a hello_world_param:0.1.0 instance:

Terminal window
peppy node start hello_world_param:0.1.0 name=planet

Since our node requires a name, we provide it with that argument on startup. Now let’s start the hello_receiver node:

Terminal window
peppy node start hello_receiver:0.1.0
Running node hello_receiver:0.1.0...
Starting node hello_receiver:0.1.0 with instance_id 'vigorous-buck-8117' and 0 argument(s)...
Calling node_start for hello_receiver:0.1.0 (instance_id=vigorous-buck-8117)...
Log file: /Users/tuatini/.peppy/logs/start/vigorous-buck-8117.log
Started node instance 'vigorous-buck-8117' (pid: 77190)

When you start the node, you can see a path to the logs. In my case: /Users/tuatini/.peppy/logs/start/vigorous-buck-8117.log. If we open it up:

[2026-01-24T10:02:47.630] [stderr] Finished `release` profile [optimized] target(s) in 0.21s
[2026-01-24T10:02:47.635] [stderr] Running `target/release/hello_receiver`
[2026-01-24T10:02:51.243] [stdout] Received from gifted-moser-9365: hello planet count 7
[2026-01-24T10:02:54.243] [stdout] Received from gifted-moser-9365: hello planet count 8
[2026-01-24T10:02:57.244] [stdout] Received from gifted-moser-9365: hello planet count 9

We can see the messages received from the hello_world_param:0.1.0 instance!

Starting a second instance with different parameters

Section titled “Starting a second instance with different parameters”

Now let’s push things a little further. Imagine we need a second instance with different parameters—we can start one like this:

Terminal window
peppy node start hello_world_param:0.1.0 name=you
Running node hello_world_param:0.1.0...
Starting node hello_world_param:0.1.0 with instance_id 'admiring-black-0614' and 1 argument(s)...
Calling node_start for hello_world_param:0.1.0 (instance_id=admiring-black-0614)...
Log file: /Users/tuatini/.peppy/logs/start/admiring-black-0614.log
Started node instance 'admiring-black-0614' (pid: 78063)

And we check the logs again:

Terminal window
tail /Users/tuatini/.peppy/logs/start/vigorous-buck-8117.log
[2026-01-24T10:04:36.242] [stdout] Received from gifted-moser-9365: hello planet count 42
[2026-01-24T10:04:39.244] [stdout] Received from gifted-moser-9365: hello planet count 43
[2026-01-24T10:04:42.244] [stdout] Received from gifted-moser-9365: hello planet count 44
[2026-01-24T10:04:45.244] [stdout] Received from gifted-moser-9365: hello planet count 45
[2026-01-24T10:04:47.085] [stdout] Received from admiring-black-0614: hello you count 1
[2026-01-24T10:04:48.243] [stdout] Received from gifted-moser-9365: hello planet count 46
[2026-01-24T10:04:50.085] [stdout] Received from admiring-black-0614: hello you count 2
[2026-01-24T10:04:51.244] [stdout] Received from gifted-moser-9365: hello planet count 47

We can see the messages from both instances!

Terminal window
peppy stack list
Listing nodes...
Requesting node stack graph from core 'sweet-germain-4388'...
Node stack:
- sweet-germain-4388:core-node (/Users/tuatini/workspace/peppy) (1 instance: ["brave-zhukovsky-6918"])
- hello_receiver:0.1.0 (/Users/tuatini/.peppy/nodes/hello_receiver_0.1.0_d41475) (1 instance: ["vigorous-buck-8117"])
- hello_world_param:0.1.0 (/Users/tuatini/.peppy/nodes/hello_world_param_0.1.0_9d5fa0) (2 instances: ["gifted-moser-9365", "admiring-black-0614"])
Dependencies:
- hello_receiver:0.1.0 -> hello_world_param:0.1.0

We’ll explore services and actions in the advanced guides, although they fundamentally work the same way. Refer to nodes in this repository for more examples of topics/service/action usage.