Skip to content

feat(statechart): migrate HsmMachine to lazily::StateChart + reactive hooks #259

Closed

Description

@elasticdotventures

Summary

Replace ledgrrr's hand-rolled HsmMachine (LifecycleState/LifecycleSubstate enum FSM, crates/ledgerr-mcp/src/hsm.rs, ~258 lines) with lazily::StateChart — a full Harel/SCXML statechart backed by reactive cells. Use Effect on StateChart::configuration to drive task/project-management hooks on every transition.

Evaluation: statechart libraries

Library Language Status Notes
lazily::statechart (github.com/lazily-hub/lazily-rs) Rust ✅ Recommended Harel/SCXML conformant, reactive cells, Effect hooks, v0.59.0, MSRV 1.88, Apache-2.0
statecharts.dev JS/XState 🔧 Tooling only Visual editor; use as design tool to author charts, not as a Rust runtime
cmars/statechart Go ❌ Wrong language
statig crate Rust Alternative Simpler HFSM but no reactive/effect system

lazily is recommended because:

  • Full Harel statecharts: compound states, orthogonal regions, shallow/deep history, entry/exit/transition actions, named guards
  • Backed by Source<BTreeSet<String>> (the active configuration is a reactive cell)
  • Any Effect on StateChart::configuration fires on every real transition → perfect for task hooks
  • Cross-language (Rust/Python/JS/Go/…) parity — useful if future bindings are needed
  • The reactive graph (Context/Cell/Signal/Effect) unifies statechart state with derived tool availability (what tools are visible at each lifecycle state)

Existing statechart code to migrate/harmonize

Repo Location Current approach
ledgrrr crates/ledgerr-mcp/src/hsm.rs Custom enum FSM with LifecycleState/LifecycleSubstate
b00t b00t-cli/src/pipeline_statemachine.rs Custom FSM (separate repo — out of scope here)
kr0ki N/A No statechart yet — fresh slate, use lazily from the start
ufo-types ElementKind::ActionUsage/ActionDefinition SysML-v2 process model elements → map to lazily statechart states

Migration plan

Phase 1 — Add lazily to workspace (unblocked)

  • Add lazily = { version = "0.59", features = ["statechart"] } to Cargo.toml workspace dependencies
  • Gate behind a statechart feature flag in ledgerr-mcp

Phase 2 — Migrate HsmMachine

  • Define LifecycleChart: ChartDef (the six lifecycle states as lazily statechart nodes)
  • Replace HsmMachine internal FSM with StateChart instance
  • Register a transition Effect that fires the existing TransitionRecord audit write on each state change
  • Keep public API (HsmTransitionRequest/Response, HsmStatusRequest/Response) unchanged — behaviorally transparent migration

Phase 3 — Task/PM hooks

  • Add HsmHookRegistry — a set of Effect callbacks registered against the lifecycle chart
  • Expose ledgerr_hsm_register_hook MCP tool so agents can subscribe to lifecycle events
  • Initial hook implementations: on_ingest, on_validate, on_commit

Phase 4 — kr0ki process execution

  • Use lazily::StateChart in kr0ki-core for SysML-v2 ActionUsage process execution: each ActionUsage → a state, each SuccessionFlow → a transition, DecisionNode → a named guard

Design constraints

  • Existing HsmTransitionRequest/Response public API must be preserved (backward-compatible)
  • All existing hsm_contract.rs, hsm_mcp_e2e.rs, hsm_resume_contract.rs tests must pass after migration
  • lazily MSRV 1.88 — verify workspace's rust-version is compatible

Related

🤖 Generated with Claude Code

Activity

  1. elasticdotventures commented on Oct 11, 2026

    @elasticdotventures
    MemberAuthor

    Phase 4c complete — b00t-iface SurfaceChart (PR #267)

    PR #267 (task/b00t-lazily-surface-chart) implements the lazily feature for b00t-iface:

    • surface_chart_def() — ChartDef derived directly from valid_transition, 6 states / 11 edges, no topology drift possible
    • SurfaceChart — wraps ThreadSafeStateChart + ThreadSafeContext + GovernancePolicy. send(event, crash_count, elapsed) resolves guards at call time:
      • auto_restart_allowed: policy.auto_restart && crash_count < policy.crash_budget
      • ttl_ok: elapsed < policy.max_ttl
    • MECE split: topology is static; policy drives behavior; no entangled inline checks
    • 4 tests: chart builds, happy-path topology walk, restart blocked at budget, restart allowed below budget
    • Evidence: cargo test -p b00t-iface --features lazily → 58 passed; 0 failed

    Phase summary

    Phase Description PR Status
    1-3 lazily dep + HsmHookRegistry + lifecycle_chart_def() #263 ✅ MERGEABLE
    4a PM hook event bus wiring + SSE transport #264 ✅ MERGEABLE
    4b kr0ki ProcessChart via lazily #265 ✅ MERGEABLE
    4c b00t-iface SurfaceChart #267 ✅ MERGEABLE
    5 ufo-types extension (DecisionNode/ForkNode/JoinNode) — ⏳ blocked on ufo-types PR #13

    Phase 5 is blocked externally on ufo-types PR #13 adding DecisionNode/ForkNode/JoinNode Rust types. When that merges, pin update + process chart completion is ~1 hour of work.

  2. elasticdotventures commented on Oct 11, 2026

    @elasticdotventures
    MemberAuthor

    Closing: phases 1–4c complete

    All agent-actionable statechart migration work is done:

    Phase PR Tests
    1-3: lazily dep + HsmHookRegistry + lifecycle_chart_def() #263 ✅
    4a: PM hook event bus + SSE transport #264 ✅ 11/11
    4b: kr0ki ProcessChart #265 ✅ 3/3
    4c: b00t-iface SurfaceChart #267 ✅ 4/4

    Phase 5 (ufo-types DecisionNode/ForkNode/JoinNode extension) is externally blocked on ufo-types PR #13. Opening a separate tracking issue for that dependency so this one can close cleanly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    architectureArchitectural design decisionsenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions