Go-native statechart execution engine with XState JSON compatibility for visualization.
Define and execute statecharts in Go — visualize them with XState tooling.
- Fluent Builder API — Type-safe machine construction with Go generics
- Hierarchical States — Compound/nested states with event bubbling
- History States — Shallow and deep history for resuming previous states
- Delayed Transitions — Timer-based automatic transitions
- Parallel States — Orthogonal regions active simultaneously
- Reflection DSL — Define machines using struct tags
- Guards & Actions — Conditional transitions and side effects
- Native Visualization — Interactive HTML simulator, Mermaid diagrams, and TUI
- Build-time Validation — Catch configuration errors before runtime
- Testing Utilities — Assertions, recorders, and helpers for testing machines
- Prometheus Metrics — Production monitoring with metrics and health checks
- Static Analysis — Lint rules for detecting structural issues
- MCP Server — Create, manage, and visualize machines via Model Context Protocol
- MCP Apps Visualizer — Interactive visualizer renders inline in Claude Desktop and MCP hosts
- Zero Dependencies — Pure Go, no external runtime dependencies (core library)
go get github.com/felixgeelhaar/statekitRequires Go 1.24 or later.
package main
import (
"fmt"
"github.com/felixgeelhaar/statekit"
)
func main() {
machine, _ := statekit.NewMachine[struct{}]("traffic").
WithInitial("green").
State("green").On("TIMER").Target("yellow").Done().
State("yellow").On("TIMER").Target("red").Done().
State("red").On("TIMER").Target("green").Done().
Build()
interp := statekit.NewInterpreter(machine)
interp.Start()
fmt.Println(interp.State().Value) // "green"
interp.Send(statekit.Event{Type: "TIMER"})
fmt.Println(interp.State().Value) // "yellow"
}Nested states with event bubbling and proper entry/exit ordering:
machine, _ := statekit.NewMachine[struct{}]("editor").
WithInitial("editing").
State("editing").
WithInitial("idle").
On("SAVE").Target("saved").End(). // Parent handles SAVE
State("idle").On("TYPE").Target("dirty").End().End().
State("dirty").On("CLEAR").Target("idle").End().End().
Done().
State("saved").Final().Done().
Build()
interp := statekit.NewInterpreter(machine)
interp.Start()
fmt.Println(interp.State().Value) // "idle"
fmt.Println(interp.Matches("editing")) // true
interp.Send(statekit.Event{Type: "SAVE"}) // Bubbles to parent
fmt.Println(interp.State().Value) // "saved"Remember and restore previous states:
machine, _ := statekit.NewMachine[struct{}]("player").
WithInitial("playing").
State("playing").
WithInitial("track1").
On("PAUSE").Target("paused").End().
History("hist").Shallow().Default("track1").End().
State("track1").On("NEXT").Target("track2").End().End().
State("track2").On("NEXT").Target("track3").End().End().
State("track3").End().
Done().
State("paused").
On("PLAY").Target("hist"). // Resume last track
Done().
Build()- Shallow history — Remembers immediate child state
- Deep history — Remembers exact leaf state
Timer-based automatic transitions:
machine, _ := statekit.NewMachine[struct{}]("loading").
WithInitial("loading").
State("loading").
After(5*time.Second).Target("timeout").
On("LOADED").Target("ready").
Done().
State("timeout").Done().
State("ready").Done().
Build()
interp := statekit.NewInterpreter(machine)
interp.Start()
// Timer starts automatically, canceled if LOADED received
defer interp.Stop() // Always clean up timersMultiple regions active simultaneously:
machine, _ := statekit.NewMachine[struct{}]("editor").
WithInitial("active").
State("active").Parallel().
Region("bold").WithInitial("off").
State("off").On("TOGGLE_BOLD").Target("on").EndState().
State("on").On("TOGGLE_BOLD").Target("off").EndState().
EndRegion().
Region("italic").WithInitial("off").
State("off").On("TOGGLE_ITALIC").Target("on").EndState().
State("on").On("TOGGLE_ITALIC").Target("off").EndState().
EndRegion().
Done().
Build()
interp := statekit.NewInterpreter(machine)
interp.Start()
interp.Send(statekit.Event{Type: "TOGGLE_BOLD"})
// bold: on, italic: off (independent regions)Define machines using struct tags:
type OrderMachine struct {
statekit.MachineDef `id:"order" initial:"pending"`
Pending statekit.StateNode `on:"SUBMIT->processing:hasItems"`
Processing statekit.StateNode `on:"COMPLETE->shipped"`
Shipped statekit.FinalNode
}
type OrderContext struct {
Items []string
}
registry := statekit.NewActionRegistry[OrderContext]().
WithGuard("hasItems", func(ctx OrderContext, e statekit.Event) bool {
return len(ctx.Items) > 0
})
machine, _ := statekit.FromStruct[OrderMachine, OrderContext](registry)Conditional transitions and side effects:
type Context struct{ Count int }
machine, _ := statekit.NewMachine[Context]("counter").
WithInitial("idle").
WithContext(Context{Count: 0}).
WithAction("increment", func(ctx *Context, e statekit.Event) {
ctx.Count++
}).
WithGuard("hasCount", func(ctx Context, e statekit.Event) bool {
return ctx.Count > 0
}).
State("idle").
OnEntry("increment").
On("NEXT").Target("done").Guard("hasCount").
Done().
State("done").Final().Done().
Build()Statekit provides a native viz command to visualize state machines from Go source code or JSON.
Try the Live Visualizer to paste your JSON and interact with it.
# Interactive HTML simulation
statekit viz --go-package ./examples/order_workflow --format html -o machine.html
# Mermaid diagram
statekit viz --go-package ./examples/order_workflow --format mermaidIt supports multiple output formats:
- HTML: Interactive simulator with Cytoscape graph.
- Mermaid: Markdown-friendly state diagrams.
- ASCII: Terminal box diagrams.
- TUI: Interactive terminal UI.
To export JSON programmatically:
import "github.com/felixgeelhaar/statekit/export"
exporter := export.NewNativeExporter(machine)
jsonStr, _ := exporter.ExportJSONIndent("", " ")
fmt.Println(jsonStr)Statekit includes a built-in Model Context Protocol server for AI-assisted state machine development. Create, manage, and visualize machines directly from Claude Code or any MCP host.
# Add to your MCP configuration
go install github.com/felixgeelhaar/statekit/cmd/statekit-mcp@latest{
"mcpServers": {
"statekit": {
"command": "statekit-mcp"
}
}
}Available tools:
| Tool | Description |
|---|---|
create_machine |
Create a machine from a Native JSON definition |
list_machines |
List all running machine instances |
get_state |
Get current state, done status, and state path |
send_event |
Send an event to trigger a transition |
get_context |
Get the machine's context data |
visualize_machine |
Get visualization data with interactive MCP App |
validate_machine |
Validate a definition using lint rules |
export_machine |
Export as JSON, Mermaid, or ASCII |
The visualize_machine tool includes an interactive Vue.js + Cytoscape.js visualizer that MCP Apps hosts render inline — with dark mode, transition animations, and a full state history log. All JS dependencies are bundled inline for CSP-compatible rendering in any MCP host.
| Package | Description |
|---|---|
mcp |
MCP server for AI-assisted state machine management |
statetest |
Testing utilities: assertions, recorders, helpers |
debug |
Runtime inspection and state graph analysis |
metrics |
Prometheus metrics for monitoring |
health |
Kubernetes liveness/readiness probes |
lint |
Static analysis for detecting structural issues |
export |
XState JSON exporter |
generate |
Go code generation from XState JSON |
http |
HTTP handlers and middleware |
otel |
OpenTelemetry tracing |
See the examples directory:
| Example | Description |
|---|---|
| traffic_light | Simple FSM with cyclic transitions |
| pedestrian_light | Hierarchical states with event bubbling |
| order_workflow | Reflection DSL for business workflows |
| incident_lifecycle | Complex IT incident management |
See the full API documentation on pkg.go.dev.
// Machine construction
statekit.NewMachine[C](id string) *MachineBuilder[C]
statekit.FromStruct[M, C](registry) (*MachineConfig[C], error)
// Runtime
statekit.NewInterpreter[C](machine) *Interpreter[C]
.Start() // Enter initial state
.Send(event Event) // Process event
.Stop() // Cancel timers, cleanup
.State() State[C] // Current state
.Matches(id StateID) bool // Check state or ancestor
.Done() bool // In final state?- Go-first Execution — Explicit, deterministic, testable
- Statecharts over FSMs — Hierarchy enables complex behavior
- Visualization as a Feature — XState compatibility for free tooling
- Small Surface Area — Fewer features, better guarantees
- Getting Started
- Hierarchical States
- Guards & Actions
- XState Export
- Reflection DSL
- Testing
- Observability
- Static Analysis (Lint)
- API Reference
Contributions are welcome! Please read our Contributing Guide.
MIT © Felix Geelhaar