Proplet
Propeller's Proplet is the Rust-based edge runtime. It receives Wasm tasks from the Manager via MQTT, executes them inside a sandboxed Wasmtime runtime, and publishes results.
The Proplet is Propeller's edge execution runtime. It receives task commands from the Manager over MQTT, fetches WebAssembly binaries, executes them using an embedded or external Wasmtime runtime, and publishes results back over MQTT.
Session behavior: The Proplet uses clean MQTT sessions (
clean_session = true). Subscriptions and missed messages are not restored on reconnect. The Proplet re-subscribes to its topics after each connection.
For how the Manager coordinates proplets and schedules tasks, see the Manager documentation. For DAG-based workflows and job execution, see Task Scheduling.
Why the Proplet
Edge-First Execution
WebAssembly workloads need to run where the data lives—on edge devices, gateways, and embedded systems. The Proplet provides:
- Lightweight runtime: A Rust-based binary that runs on resource-constrained devices
- Secure sandboxing: WebAssembly's memory-safe sandbox isolates workloads from the host system
- Protocol efficiency: MQTT communication minimizes bandwidth and handles intermittent connectivity
- Automatic discovery: Proplets announce themselves to the Manager on startup
Decoupled from Orchestration
The Proplet focuses solely on execution—it doesn't make scheduling decisions or manage workflows. This separation allows:
- Simple proplet logic: Receive task, execute, report results
- Centralized intelligence: The Manager handles scheduling, load balancing, and workflow coordination
- Resilient operation: Proplets continue executing tasks even during temporary Manager disconnects
The following diagram shows how Proplets communicate with the Manager and Proxy over MQTT, illustrating all message topics and their directions:
For the Manager's role in task orchestration, see Manager: Task Operations.
Running the Proplet
Using Docker Compose
The recommended way to run proplets is with Docker Compose:
cd propeller
docker compose -f docker/compose.propeller.yaml --env-file docker/.env up -dVerify proplets are running:
curl "http://localhost:7070/proplets?limit=10"Your output should look like this:
{
"offset": 0,
"limit": 10,
"total": 1,
"proplets": [
{
"id": "a95517f9-5655-4cf5-a7c8-aa00290b3895",
"name": "crimson-falcon",
"task_count": 0,
"alive": true,
"last_alive_at": "2026-03-01T12:00:10Z",
"metadata": {
"os": "linux",
"hostname": "edge-node-1",
"cpu_arch": "amd64",
"wasm_runtime": "wasmtime-internal"
}
}
]
}Running Standalone
Set the required environment variables and start the proplet binary:
export PROPLET_TENANT_ID="your_tenant_id"
export PROPLET_CHANNEL_ID="your_channel_id"
export PROPLET_ENTITY_ID="your_entity_id"
export PROPLET_API_KEY="your_api_key"
propeller-propletStartup logs confirm the runtime and subscriptions:
INFO Starting Proplet (Rust) - Instance ID: 67b2bcb2-9c56-4c57-b163-085d6ec2c313
INFO MQTT client created (TLS: false)
INFO Using Wasmtime runtime
INFO Starting PropletService
INFO Published discovery message
INFO Subscribed to topic: m/.../control/manager/start
INFO Subscribed to topic: m/.../control/manager/stop
INFO Subscribed to topic: m/.../registry/serverArchitecture
Runtime Selection
The proplet selects its WebAssembly runtime at startup based on configuration and hardware detection:
| Runtime | Trigger | Use Case |
|---|---|---|
| Embedded Wasmtime | Default (no external runtime configured) | Standard workloads, lowest latency |
| Host Runtime | PROPLET_EXTERNAL_WASM_RUNTIME set | Custom runtimes, debugging, alternative engines |
| TEE Runtime | TEE detected + PROPLET_KBS_URI set + encrypted: true task | Confidential computing with encrypted workloads |
Component Model Detection
When executing a WebAssembly binary, the Wasmtime runtime automatically detects the module type:
| Module Type | Detection | Embedded Wasmtime | Host Runtime |
|---|---|---|---|
| Core module | Magic bytes 0x00 0x61 0x73 0x6d + version != 0x0d | start_app_core — direct invocation | wasmtime run |
| Component | Magic bytes indicate component model | start_app_component — WASI Preview 2 | wasmtime run |
| HTTP Proxy | Component with wasi:http/incoming-handler export | start_app_proxy — built-in HTTP | wasmtime serve -Shttp — external process |
TEE Detection
At startup, the proplet probes for Trusted Execution Environment support in priority order—TDX first, then SEV/SNP, then SGX:
| TEE | Detection Method |
|---|---|
| Intel TDX | /dev/tdx_guest, /sys/firmware/tdx_guest, cpuinfo TDX flags |
| AMD SEV/SNP | /dev/sev, EFI variables (SevStatus), cpuinfo SEV flags |
| Intel SGX | /dev/sgx_enclave, /dev/sgx/enclave, /dev/isgx (legacy) |
If a TEE is detected and PROPLET_KBS_URI is not set, the proplet exits with an error—encrypted workload support requires the Key Broker Service.
For the complete TEE setup and encrypted workload flow, see the TEE guide.
Task Execution
Proplet's Role
When the Manager sends a task start command, the proplet handles the execution lifecycle:
1. Command validation — The proplet validates the StartRequest payload, ensuring required fields are present (id, name, and encrypted workload fields when applicable).
2. Targeting filter — Skipped entirely when broadcast is true. Otherwise, if proplet_id is set, the proplet checks whether it matches its own ID and silently drops the command if not. If neither broadcast nor proplet_id is set, all proplets execute the task.
3. Duplicate detection — If the task is already running, the proplet ignores the duplicate command.
4. Binary acquisition — The proplet obtains the WebAssembly binary via one of three paths:
- Inline base64: If
fileis set, decode directly from the payload - HTTP/HTTPS fetch: If
image_urlstarts withhttp://orhttps://, fetch the binary directly over HTTP (up to 100 MB; responseContent-Typemust beapplication/wasmorapplication/octet-stream) - Registry fetch: Otherwise, if
image_urlis set, request chunks from the Proxy via MQTT
5. Runtime execution — The appropriate runtime executes the module with the provided inputs and environment variables.
6. Result publishing — Upon completion (or failure), the proplet publishes results back to the Manager.
The following diagram illustrates this complete task execution flow, showing the decision points and paths through the system:
For how the Manager handles task scheduling and result processing, see Manager: Task Operations.
Binary Fetching
When a task specifies image_url instead of an inline file, the proplet requests the binary from the Proxy service:
- Proplet publishes a fetch request to
registry/propletwith the image URL - Proxy pulls the image from the container registry (GHCR, Docker Hub, etc.)
- Proxy streams binary chunks to
registry/server - Proplet assembles chunks into the complete binary
Chunk assembly includes a 5-minute TTL—incomplete assemblies are automatically expired by a background task.
The diagram below shows the complete binary fetching flow, from the Proplet's request through the Proxy to the container registry and back:
Task Stopping
When the Manager sends a stop command:
- The proplet looks up the task in
running_tasks - For proxy tasks, it signals cancellation via a watch channel
- For all tasks, it aborts the Tokio task handle
- The task is removed from the running tasks map
Configuration
The proplet is configured through environment variables. Values can also be loaded from a config.toml file generated by propeller-cli provision.
Core Settings
| Variable | Description | Default |
|---|---|---|
PROPLET_LOG_LEVEL | Log level (debug, info, warn, error) | info |
PROPLET_LIVELINESS_INTERVAL | Heartbeat publish interval in seconds | 10 |
PROPLET_METRICS_INTERVAL | Proplet-level metrics publish interval in seconds | 10 |
PROPLET_ENABLE_MONITORING | Enable per-task process monitoring | true |
MQTT Connection
| Variable | Description | Default |
|---|---|---|
PROPLET_MQTT_ADDRESS | MQTT broker URL | tcp://localhost:1883 |
PROPLET_MQTT_TIMEOUT | MQTT operation timeout in seconds | 30 |
PROPLET_MQTT_QOS | MQTT Quality of Service level | 2 |
PROPLET_MQTT_KEEP_ALIVE | MQTT keepalive interval in seconds | 30 |
PROPLET_MQTT_MAX_PACKET_SIZE | Maximum MQTT packet size in bytes | 10485760 |
PROPLET_MQTT_INFLIGHT | Maximum in-flight MQTT messages | 10 |
Atom Authentication
| Variable | Description | Default |
|---|---|---|
PROPLET_TENANT_ID | Atom tenant identifier | (required) |
PROPLET_CHANNEL_ID | Atom channel (resource) identifier | (required) |
PROPLET_ENTITY_ID | Atom entity ID for MQTT authentication | (required) |
PROPLET_API_KEY | Atom API key for MQTT authentication | (required) |
These values are generated by propeller-cli provision. For details on provisioning, see Getting Started.
Runtime Configuration
| Variable | Description | Default |
|---|---|---|
PROPLET_EXTERNAL_WASM_RUNTIME | Path to an external Wasm runtime executable. If empty, uses embedded Wasmtime. | "" |
PROPLET_DIRS | Colon-separated list of host directories to preopen for WASI access (e.g. /data:/tmp). | "" |
PROPLET_HTTP_ENABLED | Enable the built-in HTTP proxy server for WASI HTTP components | false |
PROPLET_HTTP_PROXY_PORT | Port for the built-in HTTP proxy server | 8222 |
PROPLET_HAL_ENABLED | Enable the Hardware Abstraction Layer | true |
HTTP TLS
When PROPLET_HTTP_ENABLED=true, WASM components can make outgoing HTTPS requests using wasi:http/outgoing-handler. These settings control TLS verification for those outbound requests:
| Variable | Description | Default |
|---|---|---|
PROPLET_HTTP_TLS_CA_CERT | Path to a CA certificate PEM file to trust for HTTPS | "" |
PROPLET_HTTP_TLS_INSECURE_SKIP_VERIFY | Skip TLS certificate verification (insecure, testing only) | false |
By default, the proplet trusts the system's root CA store (webpki-roots) for all outbound HTTPS requests. When PROPLET_HTTP_TLS_CA_CERT is set, the specified CA certificate is added to the trust store alongside the system roots, allowing HTTPS connections to internal servers with custom or self-signed certificates.
When PROPLET_HTTP_TLS_INSECURE_SKIP_VERIFY=true, all TLS certificate verification is disabled — use for testing only.
The same TLS configuration also applies when the proplet itself fetches WASM binaries over HTTPS (via image_url starting with https://).
Inbound HTTPS
WASM proxy components (those exporting wasi:http/incoming-handler) receive HTTP requests through the proplet's built-in reverse proxy. The proplet does not terminate TLS directly. To serve proxy components over HTTPS, deploy a standard reverse proxy in front of the proplet:
Configure the reverse proxy to accept HTTPS on the desired port and proxy requests as plain HTTP to the port where the WASM proxy component is listening. This is the same approach used throughout the Propeller ecosystem (MQTT over TLS, gRPC over TLS, etc.).
Docker Compose
In docker/compose.propeller.yaml, uncomment the environment variables and volume mount under the proplet service:
services:
proplet:
environment:
# Outgoing HTTP TLS — uncomment to trust custom/self-signed CA certificates
# for HTTPS requests made by WASM workloads and proplet itself.
# PROPLET_HTTP_TLS_CA_CERT: ${PROPLET_HTTP_TLS_CA_CERT}
# PROPLET_HTTP_TLS_INSECURE_SKIP_VERIFY: ${PROPLET_HTTP_TLS_INSECURE_SKIP_VERIFY}
volumes:
# HTTP TLS CA cert (uncomment to trust custom CAs for outgoing HTTPS):
# - type: bind
# source: ./ssl/certs/ca.crt
# target: /etc/propeller/tls/http-ca.crt
# read_only: trueIn docker/.env, uncomment the variables:
PROPLET_HTTP_TLS_CA_CERT=/etc/propeller/tls/http-ca.crt
# PROPLET_HTTP_TLS_INSECURE_SKIP_VERIFY=falseThe CA certificate is generated alongside the other TLS certificates by running make all (or make ca) in docker/ssl/. It lives at docker/ssl/certs/ca.crt. See the certificate generation section for details.
Running as a Binary
Set the environment variables before starting the proplet:
export PROPLET_HTTP_ENABLED=true
export PROPLET_HTTP_TLS_CA_CERT=/path/to/ca.crt
# export PROPLET_HTTP_TLS_INSECURE_SKIP_VERIFY=true # testing only
./propeller-propletThe PROPLET_HTTP_TLS_CA_CERT path must point to a PEM-encoded CA certificate file on the host filesystem.
TEE Configuration
Required only when running inside a Trusted Execution Environment with encrypted workloads:
| Variable | Description | Default |
|---|---|---|
PROPLET_KBS_URI | URI of the Key Broker Service | "" |
PROPLET_AA_CONFIG_PATH | Path to the Attestation Agent configuration file | "" |
If a TEE is detected and PROPLET_KBS_URI is not set, the proplet exits with an error.
Config File Fallback
| Variable | Description | Default |
|---|---|---|
PROPLET_CONFIG_FILE | Path to the TOML config file | config.toml |
PROPLET_CONFIG_SECTION | Section name within the TOML config file | proplet |
Heartbeats and Metrics
Liveliness
The proplet sends periodic heartbeats to indicate availability. The Manager uses these heartbeats to track proplet liveness. A proplet is considered alive if its last heartbeat arrived within 10 seconds. Dead proplets are excluded from task scheduling.
For how the Manager uses heartbeats for scheduling decisions, see Manager: Proplet Management.
Proplet Metrics
When monitoring is enabled, the proplet collects and publishes system-level metrics (CPU and memory) to control/proplet/metrics.
Per-Task Metrics
For running tasks, the proplet can publish process-level metrics including CPU usage, memory consumption, disk I/O, thread count, and file descriptors. These are published to control/proplet/task_metrics and include aggregated statistics.
Discovery
On startup, the proplet announces itself to the Manager by publishing a discovery message to control/proplet/create with its ID and namespace.
The Manager receives this message and creates a proplet record with a generated human-readable name (e.g., "crimson-falcon"). If the proplet disconnects unexpectedly, the MQTT broker delivers the Last Will message, notifying the Manager of the disconnect.
MQTT Topics
| Topic | Direction | Description |
|---|---|---|
m/{tenant_id}/c/{channel_id}/control/proplet/create | Proplet → Manager | Startup discovery announcement |
m/{tenant_id}/c/{channel_id}/control/proplet/alive | Proplet → Manager | Periodic heartbeat |
m/{tenant_id}/c/{channel_id}/control/proplet/metrics | Proplet → Manager | Proplet-level resource metrics |
m/{tenant_id}/c/{channel_id}/control/proplet/task_metrics | Proplet → Manager | Per-task process metrics |
m/{tenant_id}/c/{channel_id}/control/proplet/results | Proplet → Manager | Task execution results |
m/{tenant_id}/c/{channel_id}/control/manager/start | Manager → Proplet | Task start command |
m/{tenant_id}/c/{channel_id}/control/manager/stop | Manager → Proplet | Task stop command |
m/{tenant_id}/c/{channel_id}/registry/proplet | Proplet → Proxy | Request Wasm binary chunks |
m/{tenant_id}/c/{channel_id}/registry/server | Proxy → Proplet | Wasm binary chunk delivery |
Using the Host Runtime
The Proplet supports running Wasm modules using an external runtime binary instead of the embedded Wasmtime. When PROPLET_EXTERNAL_WASM_RUNTIME is set, the Proplet uses the which crate to locate the binary in $PATH. If the binary is not found at the specified path or in $PATH, the Proplet logs a warning and falls back to the embedded Wasmtime runtime.
# Use a specific runtime binary
export PROPLET_EXTERNAL_WASM_RUNTIME="/usr/bin/wasmtime"
propeller-proplet
# Or place the binary in $PATH and let the Proplet discover it
export PROPLET_EXTERNAL_WASM_RUNTIME="wasmtime"
propeller-propletThe proplet detects the module type from the binary and invokes the appropriate subcommand:
| Module Type | Subcommand | Example |
|---|---|---|
| Core module | wasmtime run <file> [cli_args…] | Standard execution with task inputs |
| Component | wasmtime run <file> [cli_args…] | WASI Preview 2 component execution |
| HTTP Proxy | wasmtime serve -Shttp [cli_args…] <file> | HTTP server for WASI incoming-handler |
Proxy Components
When the Wasm binary exports wasi:http/incoming-handler, the host runtime spawns wasmtime serve -Shttp <file> as an external HTTP server process. This enables the external runtime to handle the same proxy component workloads that the embedded runtime supports.
The task result contains the actual port the server is listening on:
{
"task_id": "…",
"status": "completed",
"result": "started at port 8222"
}Port Allocation
When PROPLET_HTTP_PROXY_PORT is set (default 8222), the host runtime probes that port first. If the port is busy, it tries the next port (8223, 8224, etc.) up to 100 attempts. If cli_args includes --addr (e.g. --addr 0.0.0.0:9093), the specified port is used directly without fallback.
CLI Arguments
Proxy components accept cli_args for wasmtime serve flags:
{
"name": "http-guest",
"image_url": "ghcr.io/myorg/http-proxy:v1",
"cli_args": ["--addr", "0.0.0.0:9093"],
"daemon": true
}The task must set "daemon": true since wasmtime serve runs indefinitely. When daemon is true, the proplet tracks the subprocess and reports the assigned port in the task result rather than waiting for it to exit.
Core & Component Tasks
For non-proxy modules, the proplet invokes wasmtime run <file> with any cli_args from the task:
{
"name": "add",
"image_url": "ghcr.io/myorg/addition:v1",
"cli_args": ["--invoke", "add"],
"inputs": [10, 20]
}Federated Learning
The proplet participates in federated learning rounds by executing WASM training modules and reporting model updates. The proplet automatically fetches model weights from the Model Registry and datasets from the Local Data Store based on environment variables set by the Coordinator.
For the complete FL architecture and training lifecycle, see Federated Learning. For a hands-on example, see Federated Learning Example.
Manager
Propeller's Manager is the Go-based orchestration engine that schedules Wasm tasks, coordinates distributed proplets, and manages workload lifecycle across Cloud-Edge nodes.
Embedded Proplet
Run WebAssembly on microcontrollers with Propeller's Embedded Proplet. Executes Wasm workloads on ESP32-S3 and Zephyr RTOS devices using WAMR — with as little as 128 KB RAM.