No description
  • Haskell 91.1%
  • Rust 8%
  • Nix 0.9%
Find a file
Alexandr_Croitor 2933d30dfd
All checks were successful
CI / linux (push) Successful in 2m6s
fix: create users for declared modules
2026-06-29 08:23:08 +03:00
.forgejo/workflows ci: add Forgejo CI workflow 2026-06-15 14:36:41 +03:00
planner-actor fix: dont commit before execution 2026-06-29 07:57:15 +03:00
proto feat: add virtualization 2026-06-28 19:08:52 +03:00
src fix: create users for declared modules 2026-06-29 08:23:08 +03:00
test fix: create users for declared modules 2026-06-29 08:23:08 +03:00
.gitignore choire: add target to ignore 2026-06-24 15:46:30 +03:00
.stan.toml choire: add .stan.toml 2026-06-23 13:26:22 +03:00
AGENTS.md refactor: migrate from service to module domain 2026-06-21 14:45:41 +03:00
cabal.project refactor(planner): adapt to IR v9 Action split and ModulesConfig 2026-06-26 02:11:37 +03:00
Cargo.lock choire: update deps 2026-06-25 15:31:49 +03:00
Cargo.toml build: add root Cargo workspace for git dep support 2026-06-23 19:12:42 +03:00
CHANGELOG.md feat: add virtualization 2026-06-28 19:08:52 +03:00
CLAUDE.md choire: add Claude.md 2026-06-15 14:50:24 +03:00
CODESTYLE.md docs: document planner architecture and conventions 2026-06-15 14:40:43 +03:00
flake.lock choire: update deps 2026-06-28 19:58:42 +03:00
flake.nix choire: better flake 2026-06-27 18:30:46 +03:00
LICENSE chore: initial project scaffold 2026-06-15 14:26:32 +03:00
planner-core.cabal feat: register Planner.Observation module and ObservationSpec 2026-06-26 13:07:58 +03:00
README.md choire: 9 -> x 2026-06-23 16:24:19 +03:00

FrogOS Planner

Planner is the stateless Haskell planning core for FrogOS. It turns verified FrogOS intent plus an observed system snapshot into a deterministic transition plan.

IRDocument + SystemState -> Either PlanError Plan

The Rust runtime owns observation, actors, execution, retries, generation storage, and rollback orchestration. Planner only computes what should happen next.

Current Status

This repository contains a working planning scaffold:

  • pure Haskell plan entry point;
  • resource graph and delta pipeline for services, packages, and users;
  • power profile planning;
  • Protobuf FFI boundary;
  • Rust planner-actor wrapper and smoke tests.

The C ABI exists today as hs_planner_init, hs_plan, and hs_plan_free. IRDocument still enters the boundary as IR JSON from frogos-ir. SystemState and PlanResult cross the FFI boundary as Protobuf bytes. Individual DeltaAction values inside PlanStep are JSON-encoded byte fields inside the Protobuf message.

Architecture

configuration.frog
        |
        v
DSL / validation
        |
        v
frogos-ir IRDocument JSON
        |
        v
Rust runtime
  - observes system
  - owns actor lifecycle
  - stores generations
        |
        v
planner-actor
        |
        v
C ABI: hs_plan
        |
        v
Haskell Planner core
  IRDocument + SystemState
        |
        v
PlanResult
        |
        v
Rust Executor
  - executes steps
  - rolls back completed steps on failure

Planner does not read /proc, call dinit, run Nix, persist generations, or execute commands. Those are runtime responsibilities.

Planning Pipeline

One plan call performs a full deterministic recalculation:

IRDocument
  |
  | 1. reject unsupported IR version
  v
active profiles
  |
  | 2. evaluate conditions from SystemState
  v
desired resource graph
  |
  | 3. resolve desired service/package/user states and priorities
  v
current resource graph
  |
  | 4. derive from observed SystemState
  v
delta
  |
  | 5. compare desired vs current
  v
ordered plan steps
  |
  | 6. users -> packages -> services, plus power steps
  v
Plan

Important invariants:

  • equal inputs must produce equal plans;
  • already reconciled resources produce no step;
  • each step carries undo metadata;
  • equal-priority conflicting desired states return ConflictingActions;
  • runtime execution errors are not PlanError.

Input And Output Example

This example asks Planner to create user alice, install htop, enable an Nginx module named mysite, and switch to the performance power profile.

Input: IRDocument

IRDocument is supplied by frogos-ir as JSON. This is a simplified example of the shape relevant to Planner:

{
  "version": X,
  "packages": ["htop"],
  "users": [
    {
      "name": "alice",
      "normal_user": true
    }
  ],
  "profiles": [
    {
      "name": "desktop",
      "policies": [],
      "actions": [
        {
          "type": "module_enable",
          "module": {
            "domain": "nginx",
            "name": "mysite"
          },
          "priority": 100
        },
        {
          "type": "power_profile",
          "value": "performance",
          "priority": 100
        }
      ]
    }
  ]
}

Input: SystemState

SystemState crosses FFI as Protobuf bytes. Human-readable decoded view:

SystemState {
  running_processes: ["waybar", "foot"]
  active_services: []
  power_profile: "balanced"
  cpu_load: 0.0              # 0.0 means not observed
  battery_percent: -1        # -1 means not observed
  running_apps: []
  installed_packages: []
  existing_users: []
}

Output: PlanResult

PlanResult also crosses FFI as Protobuf bytes. For readability, the action_json and undo_json byte fields are shown decoded:

PlanResult {
  plan: Plan {
    steps: [
      PlanStep {
        action_json: {"type":"user_add","config":{"name":"alice","normal_user":true}}
        undo_json:   {"type":"user_remove","name":"alice"}
      },
      PlanStep {
        action_json: {"type":"package_install","name":"htop"}
        undo_json:   {"type":"package_remove","name":"htop"}
      },
      PlanStep {
        action_json: {"type":"module_enable","module":{"domain":"nginx","name":"mysite"},"priority":100}
        undo_json:   {"type":"module_disable","module":{"domain":"nginx","name":"mysite"},"priority":100}
      },
      PlanStep {
        action_json: {"type":"power_profile","value":"performance","priority":100}
        undo_json:   {"type":"power_profile","value":"balanced","priority":100}
      }
    ]
  }
}

If the current state already contains user alice, package htop, active module mysite, and power profile performance, the returned plan is:

PlanResult {
  plan: Plan {
    steps: []
  }
}

Errors

Planner returns deterministic planning failures as PlanError:

PlanError {
  error_type: "version_mismatch"
  message: "expected 9, got 1"
}
PlanError {
  error_type: "conflicting_actions"
  message: "conflicting desired states at equal priority"
}

Malformed input, invalid Protobuf, invalid IR JSON, allocation failures, and unexpected Haskell exceptions are FFI boundary failures. Execution failures belong to Rust Executor, not Planner.

FFI Boundary

Rust-owned IR JSON bytes
Rust-owned SystemState protobuf bytes
        |
        v
hs_plan(ir_ptr, ir_len, state_ptr, state_len, out_ptr, out_len)
        |
        v
decode IR JSON
decode SystemState protobuf
plan ir state
encode PlanResult protobuf
        |
        v
Haskell-allocated output buffer
        |
        v
Rust copies result
        |
        v
hs_plan_free(output_ptr)

Rules:

  • all byte buffers are passed as pointer plus explicit length;
  • Rust never receives pointers to Haskell data structures;
  • hs_plan_free releases buffers allocated by hs_plan;
  • Haskell catches synchronous exceptions before returning through C;
  • hs_planner_init is the runtime lifecycle hook exported for callers.

Modules

src/Planner/
  Core.hs          -- pure orchestration: IRDocument + SystemState -> Either PlanError Plan
  Graph.hs         -- current and desired resource graphs
  Delta.hs         -- graph diff and step ordering
  Types.hs         -- planning domain types
  Proto.hs         -- Protobuf encode/decode helpers
  FFI.hs           -- C ABI boundary

planner-actor/
  src/ffi.rs       -- Rust FFI wrapper
  src/actor.rs     -- Tokio actor around Planner
  tests/           -- Rust smoke tests

Development

nix develop
cabal test
nix fmt
stan --no-default
nix flake check --keep-going --print-build-logs

Behavior changes should be test-driven. The current test suite includes example-based planning tests, table-driven state transition tests, property tests, shrink checks, Protobuf round-trip tests, and Rust FFI/actor smoke tests.

Project rules live in AGENTS.md and CODESTYLE.md.