Repository navigation
feat(statechart): migrate HsmMachine to lazily::StateChart + reactive hooks #259
Copy link
Copy link
Labels
architectureArchitectural design decisionsArchitectural design decisionsenhancementNew feature or requestNew feature or request
Description
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
EffectonStateChart::configurationfires 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"] }toCargo.tomlworkspace dependencies - Gate behind a
statechartfeature flag inledgerr-mcp
Phase 2 — Migrate HsmMachine
- Define
LifecycleChart: ChartDef(the six lifecycle states as lazily statechart nodes) - Replace
HsmMachineinternal FSM withStateChartinstance - Register a transition
Effectthat fires the existingTransitionRecordaudit 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 ofEffectcallbacks registered against the lifecycle chart - Expose
ledgerr_hsm_register_hookMCP tool so agents can subscribe to lifecycle events - Initial hook implementations:
on_ingest,on_validate,on_commit
Phase 4 — kr0ki process execution
- Use
lazily::StateChartinkr0ki-corefor SysML-v2ActionUsageprocess execution: eachActionUsage→ a state, eachSuccessionFlow→ a transition,DecisionNode→ a named guard
Design constraints
- Existing
HsmTransitionRequest/Responsepublic API must be preserved (backward-compatible) - All existing
hsm_contract.rs,hsm_mcp_e2e.rs,hsm_resume_contract.rstests must pass after migration lazilyMSRV 1.88 — verify workspace's rust-version is compatible
Related
- Closes the
HsmMachinehand-rolled FSM tech debt - Enables arch: CLIF (ISO/IEC 24707) AST and interpreter for ledgrrr's process/diagram engine #114 (CLIF/process engine) via kr0ki integration (Phase 4)
- statecharts.dev visual editor can be used to design/document the lifecycle chart before committing to code
🤖 Generated with Claude Code
Metadata
Metadata
Assignees
Labels
architectureArchitectural design decisionsArchitectural design decisionsenhancementNew feature or requestNew feature or request
Activity
feat(hsm): Phase 2 — HsmHookRegistry wired into transition/resume too…
feat(hsm): Phase 3 — register_hook MCP tool + built-in on_ingest/on_v…
feat(pm-hooks): ProjectEventBus general event hook system + register_…
elasticdotventures commentedon Oct 11, 2026
Phase 4c complete — b00t-iface SurfaceChart (PR #267)
PR #267 (
task/b00t-lazily-surface-chart) implements thelazilyfeature forb00t-iface:surface_chart_def()—ChartDefderived directly fromvalid_transition, 6 states / 11 edges, no topology drift possibleSurfaceChart— wrapsThreadSafeStateChart+ThreadSafeContext+GovernancePolicy.send(event, crash_count, elapsed)resolves guards at call time:auto_restart_allowed:policy.auto_restart && crash_count < policy.crash_budgetttl_ok:elapsed < policy.max_ttlcargo test -p b00t-iface --features lazily→ 58 passed; 0 failedPhase summary
Phase 5 is blocked externally on ufo-types PR #13 adding
DecisionNode/ForkNode/JoinNodeRust types. When that merges, pin update + process chart completion is ~1 hour of work.elasticdotventures commentedon Oct 11, 2026
Closing: phases 1–4c complete
All agent-actionable statechart migration work is done:
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.
feat(statechart): integrate HsmChart PM hook dispatch (issue #259 pha…
feat(http-transport): replace tiny_http with axum/SSE Streamable HTTP (…
feat(b00t-iface): SurfaceChart — lazily ThreadSafeStateChart live exe…
feat(statechart): Phase 1 — add lazily::StateChart to workspace + smo…
feat(hsm): Phase 2 — HsmHookRegistry wired into transition/resume too…
feat(hsm): Phase 3 — register_hook MCP tool + built-in on_ingest/on_v…
feat(statechart): Phases 1-3 — lazily::StateChart dep + HsmHookRegist…