embassy-supervisor 0.1.1

A generic, HAL-agnostic task-lifecycle supervisor for the embassy async embedded framework: dependency-ordered bring-up/teardown, lifecycle modes, elastic task pools, and runtime start/stop/pause/resume control.
Documentation

embassy-supervisor

crates.io docs.rs

A generic, HAL-agnostic task-lifecycle supervisor for the embassy async embedded framework. no_std, no allocator, no board crates — it compiles for any embassy target. The only third-party deps are pure-embassy crates (embassy-executor/-sync/-time/ -futures), heapless, and portable-atomic.

What it does

  • Dependency-ordered lifecycle — declare each task as a TaskNode with its deps; the supervisor topologically sorts the graph, brings tasks up in dependency order, and tears dependents down before the things they depend on.
  • Lifecycle modesTerminate (started at boot, restartable), Pause (park/resume while keeping a held resource), OnDemand (started on demand to scale a pool).
  • Elastic pools (feature pool)ElasticPool scales a set of single-instance worker nodes with load via a swappable ScalingPolicy (e.g. DeferredShrink), within a fixed budget.
  • Runtime control (feature control) — drive start/stop/pause/resume from anywhere (an HTTP endpoint, a button, …) through a decoupled mailbox (request_control / apply_control) that honors dependencies and pool membership.

The supervisor deliberately does not allocate, own a HAL, manage power states, or know what your tasks do — it orchestrates their lifecycle and leaves the rest to you.

Quickstart

use embassy_executor::Spawner;
use embassy_supervisor::{task_graph, Mode, Supervisor, TaskNode, wait_control};

// Each subsystem is a `TaskNode` wrapping a spawn fn; `app` depends on `net`.
static NET: TaskNode = TaskNode::new("net", Mode::Terminate, &[], |s| {
    s.spawn(net_task())?;
    Ok(())
});
static APP: TaskNode = TaskNode::new("app", Mode::Terminate, &[&NET], |s| {
    s.spawn(app_task())?;
    Ok(())
});

task_graph! { &NET, &APP }   // emits `ALL_NODES` + `NODE_COUNT`

#[embassy_executor::task]
async fn supervisor_task(spawner: Spawner) {
    let sup = Supervisor::new(&ALL_NODES).expect("no dependency cycle");
    sup.start(spawner).expect("initial spawn");   // brings up `net`, then `app`
    loop {
        let cmd = wait_control().await;           // runtime control requests
        sup.apply_control(cmd, spawner).await;    // applied in dependency order
    }
}

Cargo features

feature default what it adds
control runtime control plane (ControlOp, request_control, apply_control)
pool elastic worker pools (ElasticPool, with_pools, run_pools)
defmt route the supervisor's logs through defmt (otherwise the log macros are no-ops)

default-features = false gives a minimal core that only does dependency-ordered bring-up/teardown — dropping the control plane and pools trims flash and a couple of statics.

no_std / MSRV

#![no_std] and #![forbid(unsafe_code)]. Requires Rust 1.85+ (edition 2024). The embassy dependencies are pre-1.0 (embassy-executor 0.10, embassy-sync 0.8, embassy-time 0.5), so a consuming application must use compatible embassy minor versions.

Full example

The firmware crate in the repository is a complete working application on an RP2350 — USB-CDC-NCM networking, an HTTP control plane, an elastic worker pool, and OTA firmware update — all driven by this supervisor.

License

Dual-licensed under either MIT or Apache-2.0, at your option.