Daemon configuration
The peppy daemon reads one global configuration file, ~/.peppy/conf/peppy_config.json5. It sets the daemon’s core-node name, controls the messaging topology of the whole stack, the subscriber channel buffer sizes, the grace periods that govern node lifecycle, and the timeout for federating to your organization’s cloud router. The daemon applies it to its own core-node session and to every node it spawns. The same file also records the backend resource-server URL the peppy auth login / whoami / logout commands talk to; that block is read by the CLI, not the daemon.
How the file is managed
Section titled “How the file is managed”You never need to create or migrate this file by hand:
- First start. If the file does not exist, the daemon creates it with every setting at its default value, annotated with explanatory comments.
- Missing settings. If the file exists but omits settings (typically a file written by an older peppy, before a newer knob existed), the daemon appends each missing setting with its default value and comments. Your own values, comments, formatting, and any unrecognized keys are preserved exactly as you wrote them.
- Malformed file. If the file cannot be parsed, or a value is out of range, the daemon refuses to start and reports the error instead of silently falling back to defaults. A file that fails to load is never modified.
A setting you delete from the file therefore comes back with its default on the next daemon start. To change a setting, edit its value instead of removing it.
The default file
Section titled “The default file”// Read once when the peppy daemon starts, so any edit below (mode or buffer// sizes) takes effect only after you restart the daemon.{ // Fixed name for this daemon's core node, or null to derive a // machine-specific default (core-node-...). Names must be unique across all // daemons reachable over the same router/federation: a daemon whose name is // already in use refuses to boot. At most 63 characters from the node-name // character set (start with a letter; letters, digits, `_`, `-`). // `peppy service serve --core-node-name` overrides this for one run. core_node_name: null,
// "peer" - Zenoh peer sessions with gossip: nodes form direct // peer-to-peer links and data stops relaying through the router. // "router" - gossip off: all traffic relays through the central zenohd // router. // Container nodes in a separate network namespace (Lima on macOS) always use // the router path regardless of this setting. mode: "peer",
// Subscriber channel buffer sizes (number of in-flight messages) per QoS // tier, used in peer mode where there is no router relay to buffer between a // publisher and a subscriber. Defaults match peppy's built-in behavior; only // edit to tune backpressure. peer: { standard_buffer_size: 128, high_throughput_buffer_size: 1024, },
lifecycle: { // Node lifecycle knobs. `daemon_grace_secs` is the grace period a spawned node // waits, after the daemon's heartbeat goes silent, before shutting itself down // to avoid orphaning. daemon_grace_secs: 180,
// How long a clean shutdown (ctrl+C / `systemctl stop`) and `peppy node // stop` wait for a node to exit cooperatively before force-killing its // process group. Seconds; minimum 1. A robot node uses this window to park // actuators and release hardware before it is killed. shutdown_grace_secs: 5, },
// Backend resource-server URL the `peppy auth login` / `whoami` / `logout` // commands talk to. Baked in at compile time (the dev backend in debug // builds, prod in release); --api-url / PEPPY_API_URL override it at runtime. resource_servers: { api: "https://api.peppy.bot", },
// Per-user zenoh-router federation: how the daemon links its local router to // your private cloud router. Only tuned to bound a slow/unreachable backend // during the federation step. federation: { // Seconds the daemon spends resolving your per-user cloud router before // giving up for this attempt (it retries in the background). Bounds the // federation done at startup and on each `peppy auth login`/`logout`; // minimum 1. If the backend is unreachable within this window the daemon // stays standalone rather than blocking. connect_timeout_secs: 30, },}core_node_name: the daemon’s core-node name
Section titled “core_node_name: the daemon’s core-node name”core_node_name fixes the name of this daemon’s core node. Leave it null (the default) and the daemon derives a stable, machine-specific name of the form core-node-...; set a string to pin an explicit one.
Core-node names must be unique across every daemon reachable over the same router or federation. On boot the daemon probes its own name and, if another daemon already answers under it, refuses to start rather than break the name-based routing that every core-node call relies on. If two daemons end up sharing a name, give one of them a unique core_node_name (or pass peppy service serve --core-node-name <name> for a single run) and restart it. This most commonly matters when logging in federates several machines together, or when cloning a disk image reuses another machine’s derived name.
The value must be non-empty, at most 63 characters, and use only the node-name character set (start with a letter; letters, digits, _, -). An invalid value stops the daemon at startup with an error instead of failing later when the core node boots. peppy service serve --core-node-name <name> overrides the config for one run (the flag wins over the file); both absent falls back to the derived default.
mode: messaging topology
Section titled “mode: messaging topology”mode selects how nodes exchange data:
"peer"(default): nodes run Zenoh peer sessions with gossip discovery enabled. After discovery, nodes form direct peer-to-peer links and data stops relaying through the central router, which removes a network hop from every message."router": gossip is off and all traffic relays through the centralzenohdrouter. Use this when direct node-to-node connectivity is unreliable or undesirable, or to simplify debugging by funneling all traffic through one process.
Container nodes that live in a separate network namespace (such as the Lima VM peppy uses on macOS) always take the router path, regardless of this setting: gossip cannot establish direct links across the namespace boundary.
peer: subscriber buffer sizes
Section titled “peer: subscriber buffer sizes”Each subscriber buffers in-flight messages in a bounded local channel. The peer block sets that channel’s capacity (number of messages, not bytes) per QoS tier:
standard_buffer_size(default128): the buffer for topics on the standard QoS tier, which most topics use. The same capacity also sizes the channels behind service requests and replies.high_throughput_buffer_size(default1024): the buffer for topics on the high-throughput QoS tier, such as sensor-data streams, where short bursts well above the average rate are normal.
The capacities apply in every mode, but they matter most in peer mode, which is why they live under the peer block: with nodes peering directly there is no router relay between a publisher and a subscriber, so this buffer is all that absorbs a burst.
When a subscriber falls behind and its buffer fills up, what happens next depends on the topic’s QoS: topics published with a reliable profile block delivery so backpressure propagates to the publisher, while best-effort topics (including sensor-data streams on the high-throughput tier) drop messages instead. Raise a buffer size to absorb longer bursts at the cost of memory and worst-case latency; lower it to surface backpressure, or message loss, sooner. The defaults match peppy’s built-in behavior, so you only need to touch this block to tune backpressure.
Both values must be greater than 0; the daemon rejects a zero buffer size at startup.
lifecycle: grace periods
Section titled “lifecycle: grace periods”The lifecycle block tunes the two windows peppy uses to guarantee that no node outlives the daemon. The full mechanics are described in Daemon shutdown and orphan prevention.
daemon_grace_secs(default180, minimum30): every spawned node runs a watchdog that listens for the daemon’s periodic heartbeat (published every 5 seconds). If the heartbeat goes silent for this many seconds, the node shuts itself down rather than lingering as an orphan. This only governs unclean daemon death (crash, OOM,SIGKILL); a clean shutdown does not wait for it. The minimum exists so a brief daemon blip or a quick restart never trips every node’s watchdog.shutdown_grace_secs(default5, minimum1): the node’s cooperative-cleanup budget. A clean daemon shutdown (Ctrl+C,systemctl stop) andpeppy node stopwait this window, plus a fixed allowance for the node’s runtime to finish tearing down (in Rust, the async runtime dropping; in Python, the event-loop join and interpreter finalize), before force-killing its process group. Raise it for nodes that need longer to park actuators or release hardware before dying; the force-kill deadline rises with it.
resource_servers: backend URL
Section titled “resource_servers: backend URL”This block holds the platform-backend base URL the CLI auth commands talk to. The daemon ignores it; the peppy auth login / whoami / logout commands read it. See Authentication for the full login flow.
api(defaulthttps://api.peppy.botin release builds,http://127.0.0.1:3000in debug): the backend the auth commands talk to. There is no dev/prod selection at runtime; the file stores exactly the build’s backend.
The URL is resolved in precedence order: --api-url, then PEPPY_API_URL, then api here. An empty block falls back to the build’s default backend. Plain http is accepted only for local backends (loopback / *.localhost); any other host must be https, validated when the command runs.
federation: cloud-router timeout
Section titled “federation: cloud-router timeout”When you are logged in, the daemon links (“federates”) its local messaging router to your organization’s private cloud router so that robots signed in to the same organization interoperate across the federation (see Authentication). Resolving that cloud router involves a backend round-trip, and this block bounds it.
connect_timeout_secs(default30, minimum1): how long the daemon spends resolving the cloud router (once at startup, and again each timepeppy auth login/logoutpokes the daemon) before giving up for that attempt. If the backend is unreachable within the window, the daemon leaves its router standalone and retries federation in the background rather than blocking startup. A logged-out daemon never federates, so this timeout does not apply to it.
Reference
Section titled “Reference”| Setting | Default | Constraint | Effect |
|---|---|---|---|
core_node_name | null (derived core-node-...) | Non-empty, ≤ 63 chars, node-name charset | Fixed name for this daemon’s core node; must be unique across all daemons on the same router/federation or the daemon refuses to boot. Overridable per run with --core-node-name. |
mode | "peer" | "peer" or "router" | Messaging topology: direct peer links with gossip, or relay everything through the central router. |
peer.standard_buffer_size | 128 | > 0 | Subscriber channel capacity (messages) for standard-QoS topics; also sizes service request/reply channels. |
peer.high_throughput_buffer_size | 1024 | > 0 | Subscriber channel capacity (messages) for high-throughput-QoS topics such as sensor-data streams. |
lifecycle.daemon_grace_secs | 180 | >= 30 | Seconds without a daemon heartbeat before a spawned node self-terminates (unclean daemon death only). |
lifecycle.shutdown_grace_secs | 5 | >= 1 | Seconds a clean shutdown and peppy node stop wait for cooperative exit (plus a fixed runtime-teardown allowance) before force-killing. |
resource_servers.api | https://api.peppy.bot (release), http://127.0.0.1:3000 (debug) | https, or http for local hosts | Backend the CLI auth commands talk to. Read by the CLI, not the daemon. |
federation.connect_timeout_secs | 30 | >= 1 | Seconds the daemon spends resolving your organization’s cloud router (at startup and on each auth login/logout) before falling back to a standalone router. |
A value outside its constraint, an unknown mode, or a syntax error all stop the daemon at startup with an error pointing at the problem (an out-of-range value names the offending field; a parse error reports the bad value or its position), so a typo can never silently revert your stack to defaults.