No description
  • Rust 79.8%
  • Nix 17.9%
  • Just 2.3%
Find a file
Alexandr_Croitor a1edc8c38c
Some checks failed
CI / linux (push) Failing after 2m3s
choire: update deps
2026-06-29 08:24:52 +03:00
.forgejo/workflows chore: scaffold supervisor crate with tooling and git deps 2026-06-23 23:55:29 +03:00
src fix: handle settled planner state 2026-06-28 21:24:24 +03:00
tests fix: throttle supervisor restart loops 2026-06-28 18:20:37 +03:00
.gitignore choire: update gitignore 2026-06-24 01:53:59 +03:00
AGENTS.md choire: updated AGENTS.md 2026-06-24 01:09:42 +03:00
Cargo.lock choire: update deps 2026-06-29 08:24:52 +03:00
Cargo.toml choire: update deps 2026-06-25 15:55:43 +03:00
CLAUDE.md choire: add CLAUDE.md 2026-06-24 00:56:38 +03:00
CODESTYLE.md chore: scaffold supervisor crate with tooling and git deps 2026-06-23 23:55:29 +03:00
flake.lock choire: update deps 2026-06-29 08:24:52 +03:00
flake.nix choire: update deps 2026-06-25 15:55:43 +03:00
Justfile choire: add gitignore\ 2026-06-24 00:53:34 +03:00
README.md fix: throttle supervisor restart loops 2026-06-28 18:20:37 +03:00

Supervisor

FrogOS actor lifecycle manager. Starts and supervises Toad (system watcher) and Planner (action planner) under an Erlang-style rest_for_one strategy.

Does not observe, plan, or execute — it wires actors together and restarts them on failure.

Architecture

┌─────────────────────────────────────────────┐
│                  Supervisor                 │
│                                             │
│  ToadActor ──watch──▶ Bridge ──mpsc──▶ PlannerActor
│  (system watcher)     (encoder)    (Haskell FFI)
│        │                                    │
│   SystemState                            Plan
│                                             │
└─────────────────────────────────────────────┘
                                              ▼
                                       plan_rx (caller)

ToadActor polls the system (processes, services, power, apps) and publishes SystemState on a watch channel.

Bridge subscribes to ToadState changes, encodes them as protobuf, and forwards PlannerMsg::SystemStateUpdate over mpsc.

PlannerActor receives state updates and an initial IR config, calls the Haskell FFI planner, and emits Plan structs.

Restart semantics (rest_for_one)

Actor exits Restarted
Toad Toad + Bridge + Planner
Bridge Bridge + Planner
Planner Bridge + Planner

Bridge is always co-restarted with Planner because they share the paired ends of an mpsc channel — a new channel must be created on each restart.

Restarts are rate-limited: max 10 within a 60-second window (configurable via SupervisorConfig).

Requirements

  • Rust (edition 2024)
  • Nix with flakes (recommended)
  • PLANNER_FFI_LIB_DIR pointing to the compiled libplanner-ffi.so — set automatically inside nix develop

cargo check works without the Haskell library. cargo build and cargo test require it.

Development

Enter the Nix dev shell (sets PLANNER_FFI_LIB_DIR and other env vars):

nix develop

Common tasks via just:

just check      # cargo check --all-targets --all-features
just lint       # cargo clippy --all-targets --all-features -- -D warnings
just test       # cargo test --all-targets --all-features
just build      # cargo build --all-targets --all-features
just ci         # fmt-check + check + lint + test + build
just nix-check  # nix flake check --keep-going --print-build-logs

Running

The binary takes one optional argument — path to the intent representation (IR) JSON file:

# Default path: /run/current-system/intent.json
supervisor

# Custom IR path
supervisor /path/to/intent.json

Logs are emitted via tracing. Set RUST_LOG to control verbosity:

RUST_LOG=info supervisor /path/to/intent.json
RUST_LOG=debug supervisor /path/to/intent.json

Each received plan is logged with its steps. Plans are also delivered on the plan_rx receiver returned by Supervisor::new for downstream consumers.

Library usage

use std::time::Duration;
use bytes::Bytes;
use supervisor::{Supervisor, SupervisorConfig};

let config = SupervisorConfig {
    poll_interval: Duration::from_secs(1),
    ir_bytes: Bytes::from(std::fs::read("intent.json")?),
    max_restarts: 10,
    restart_window: Duration::from_secs(60),
    restart_delay: Duration::from_secs(1),
};

let (supervisor, mut plan_rx) = Supervisor::new(config);

tokio::spawn(async move {
    while let Some(plan) = plan_rx.recv().await {
        // handle plan
    }
});

supervisor.run().await?;

License

LGPL-2.1-only