statekit

package module
v1.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 6, 2026 License: MIT Imports: 13 Imported by: 0

README

Statekit

Go Reference Go Report Card CI

Stop modeling order, payment, and incident lifecycles with switch statements and ad-hoc FSMs. Statekit is a typed statechart library for Go: hierarchical states, guards, actions, delayed and parallel transitions, with built-in visualization and lint.

machine, _ := statekit.NewMachine[Order]("checkout").
    WithInitial("cart").
    State("cart").On("CHECKOUT").Target("processing").Done().
    State("processing").On("PAID").Target("shipped").Done().
    State("shipped").Final().Done().
    Build()

interp := statekit.NewInterpreter(machine)
defer interp.Close()
interp.Start()
interp.Send(statekit.Event{Type: "CHECKOUT"})

A working machine in 10 lines. Hierarchy and parallel states scale from there. statekit viz renders any machine to ASCII, Mermaid, an interactive HTML simulator, or a TUI.

Two jobs

Statekit fits two adjacent shapes that look similar from outside:

  • Backend domain workflows — order lifecycles, payment sagas, incident management, KYC. The kind of state most teams scatter across switch event.Type { ... } and accumulate bugs around partial failure, retry, and idempotency. See examples/stripe_webhook for a webhook-saga template with idempotency, retry budget, and the outbox pattern.
  • Deterministic AI agent runtime — RAG pipelines, tool-call workflows, multi-step agents with human-in-the-loop. Statecharts give you reproducibility (event sourcing replays the run without re-calling the model), bounded blast radius (OnError routing), and audit-grade observability (prompt + token snapshots). See examples/llm_agent and the ai + aiplugin packages.

Both jobs benefit from the same primitives — typed context, hierarchy, lint, visualization, snapshots — so one library, one mental model.

Coming from another FSM library?

If you're using one of the incumbent Go FSM libraries and have hit a ceiling, this is the migration path:

  • Need persistence that doesn't choke on unexported fields? Statekit's Snapshot round-trips through encoding/json and encoding/gob (see snapshot serialization tests).
  • Need recovery after a service or context error without ending up in a stuck-transition limbo? Statekit's OnError routes cleanly; the interpreter accepts events again immediately (see cancellation recovery tests).
  • Need hierarchy (compound, parallel, history)? First-class. Lint catches structural bugs the flat-FSM libraries can't even check for.
  • Need typed everything? [C any] context threads through actions, guards, and services — no interface{} casts at handler time.
  • Need a Mermaid / ASCII / interactive HTML diagram? statekit viz ships them.

Step-by-step migration guides:

Why

  • Type-safe over typed[C any] context, typed events, typed actions and guards. No interface{} casts at action time.
  • Statecharts, not just FSMs — compound, parallel, history, and delayed transitions handle the workflows that flat FSM libraries can't model without manual bookkeeping.
  • Visualization as a feature — every machine renders to multiple formats from one source of truth. XState v5 export for Stately Studio round-trip editing.
  • Static analysislint.Lint(machine) catches unreachable states, dead ends, non-determinism, missing OnError on Invoke, and more — at build time.
  • Determinism for tests — inject a FakeClock to make timer-driven behavior reproducible. No time.Sleep flakes.

Core features

  • Hierarchical states with event bubbling
  • History states (shallow and deep)
  • Delayed transitions and parallel/orthogonal regions
  • Eventless (Always) transitions, Raise internal events, and state Tags
  • Wildcard (*) event handlers, internal transitions, and Choose conditional actions
  • Guards, actions, entry/exit hooks
  • Reflection DSL — define machines with struct tags
  • Build-time validation
  • Visualization: ASCII, Mermaid, interactive HTML, TUI
  • Static analysis (lint)
  • Snapshot / Restore
  • Plugin system with lifecycle hooks
  • Testing utilities (statetest)
  • HTTP integration, OpenTelemetry tracing, Prometheus metrics, Kubernetes health probes
  • Code generation from Native JSON

Advanced (Tier 2 — see stability tiers)

These ship in v1.0 but reserve room to iterate within v1.x:

  • Actor modelSpawn, supervision strategies (Escalate / Recover / Restart / Stop)
  • Persistent interpreter — event-sourced state, snapshot-on-final, configurable strategies
  • Distributed executionStreamLock interface (Redis / etcd / PostgreSQL) + DistributedInterpreter
  • Machine compositionInvokeMachine for typed child-machine composition
  • MCP integrationmcp.NewServer for AI-assisted authoring; mcp.ExposeInterpreter to drive a running machine from an agent
  • AI pluginsaiplugin.TokenCounter, aiplugin.PromptRecorder for LLM observability and replay-based debugging

Installation

go get go.klarlabs.de/statekit

Requires Go 1.25 or later.

Quick Start

package main

import (
    "fmt"
    "go.klarlabs.de/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)
    defer interp.Close()
    interp.Start()

    fmt.Println(interp.State().Value) // "green"
    interp.Send(statekit.Event{Type: "TIMER"})
    fmt.Println(interp.State().Value) // "yellow"
}

Hierarchical States

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)
defer interp.Close()
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"

History States

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

Delayed Transitions

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)
defer interp.Close() // Always clean up timers
interp.Start()
// Timer starts automatically, canceled if LOADED received

Parallel States

Multiple 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)
defer interp.Close()
interp.Start()
interp.Send(statekit.Event{Type: "TOGGLE_BOLD"})
// bold: on, italic: off (independent regions)

Eventless Transitions, Raise & Tags

Always transitions fire automatically on state entry (and after every transition), choosing the first whose guard passes — ideal for conditional routing without an explicit event. Raise enqueues an internal event that is processed in the same step, before control returns and before any external event. Tags categorize states for lightweight querying via HasTag.

machine, _ := statekit.NewMachine[Ctx]("checkout").
    WithInitial("validating").
    WithGuard("ok", func(c Ctx, e statekit.Event) bool { return c.Valid }).
    State("validating").
        Tags("busy").
        Always().Target("approved").Guard("ok").End().
        Always().Target("rejected").End().            // guardless fallback
        Done().
    State("approved").
        On("SHIP").Target("shipping").Raise("NOTIFY").End().  // raise internal event
        Done().
    State("shipping").
        On("NOTIFY").Target("done").End().            // handled in the same step
        Done().
    State("rejected").Final().Done().
    State("done").Final().Done().
    Build()

interp := statekit.NewInterpreter(machine)
interp.Start()
// validating → approved|rejected resolved automatically via Always

fmt.Println(interp.HasTag("busy"))  // true while in a tagged active state

Key behaviors:

  • Always transitions are evaluated in declaration order; the first enabled wins. A target is required (build-time validated) to prevent infinite loops.
  • A macrostep settles all eventless transitions and drains raised events before Start()/Send() returns (bounded to guard against always-true cycles).
  • HasTag matches the active leaf, its ancestors, and active parallel-region leaves.
  • All three round-trip through the Native JSON and XState v5 exporters (always, tags, and xstate.raise action descriptors).

Wildcard Events, Internal Transitions & Choose

Wildcard * catches any event not handled by a specific transition (exact matches always win; it bubbles like any handler). Internal() runs a transition's actions without exiting or re-entering the state — no entry/exit hooks, no state change — in contrast to an external self-transition. Choose is a conditional-action combinator: it runs the first branch whose guard passes.

machine, _ := statekit.NewMachine[Ctx]("ops").
    WithInitial("running").
    WithAction("audit", statekit.Choose(
        statekit.ChooseBranch[Ctx]{When: isAdmin, Then: logAdmin},
        statekit.ChooseBranch[Ctx]{Then: logUser}, // else
    )).
    State("running").
        On("TICK").Internal().Do("audit").End().  // no exit/entry, no state change
        On("*").Target("unknown").End().           // catch-all fallback
        On("STOP").Target("stopped").End().         // exact match beats "*"
        Done().
    State("unknown").Final().Done().
    State("stopped").Final().Done().
    Build()

Key behaviors:

  • Wildcard * is lowest priority within a state and honors guards; child handlers still take priority over ancestors.
  • Internal transitions accept an empty target (or the owning state); build-time validated.
  • Choose is a plain Action[C] — register it and reference it anywhere an action is used; a branch with a nil When is the else.
  • Wildcard and internal transitions round-trip through both exporters (on["*"], internal: true).

Reflection DSL

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)

Guards & Actions

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()

Visualization

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. The landing page has the project overview.

# 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 mermaid

It 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 "go.klarlabs.de/statekit/export"

exporter := export.NewNativeExporter(machine)
jsonStr, _ := exporter.ExportJSONIndent("", "  ")
fmt.Println(jsonStr)

MCP Server

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 go.klarlabs.de/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

Inverting the loop: if you already have a typed *statekit.Interpreter[C] running in your service, mcp.ExposeInterpreter registers <prefix>.send_event, <prefix>.get_state, <prefix>.get_context, and <prefix>.matches so an MCP-speaking agent can drive your machine from outside.

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.

Additional Packages

Package Description
ai LLM-driven transitions — Drive + Tool schema (Tier 2)
aiplugin AI plugins — TokenCounter, PromptRecorder, TransitionBudget (Tier 2)
mcp MCP server + ExposeInterpreter for agent-driven workflows (Tier 2)
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 Native + XState v5 JSON exporters
generate Go code generation from Native JSON
http HTTP handlers and middleware
otel OpenTelemetry tracing

Examples

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
llm_agent Deterministic RAG pipeline with HITL approval

API Reference

See the full API documentation on pkg.go.dev.

Core Types
// 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?

Design Philosophy

  • Go-first Execution — Explicit, deterministic, testable
  • Statecharts over FSMs — Hierarchy, parallel, history enable complex behavior without manual bookkeeping
  • Visualization as a Feature — Multiple renderers + XState v5 export for Stately Studio round-trip
  • Determinism for tests — Inject FakeClock to remove timer flakes
  • Stable core, experimental edge — Tier-1 surface follows semver; Tier-2 features (actor, persistent, distributed, MCP, AI) reserve room to iterate

Documentation

The docs index organizes everything by Diataxis category (tutorials, how-to, reference, explanation). Quick links:

Contributing

Contributions are welcome! Please read our Contributing Guide.

License

MIT © Felix Geelhaar

Documentation

Overview

Package statekit provides a Go-native statechart execution engine.

Define and execute statecharts in Go — visualize them with built-in tools.

Features

  • Fluent Builder API
  • Hierarchical States (Compound/Nested)
  • History States (Shallow and Deep)
  • Delayed Transitions (Timers)
  • Parallel States (Orthogonal Regions)
  • Reflection DSL (Struct tags)
  • Native Visualization (HTML simulator, Mermaid, ASCII, TUI)

Quick Start

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()

interp.Send(statekit.Event{Type: "TIMER"})

Visualization

Export your machine to Statekit Native JSON for visualization:

exporter := export.NewNativeExporter(machine)
jsonStr, _ := exporter.ExportJSONIndent("", "  ")
fmt.Println(jsonStr)

Or use the CLI:

statekit viz --go-package . --format html -o machine.html

Package statekit provides event sourcing support for state machine persistence.

Example
package main

import (
	"fmt"

	"go.klarlabs.de/statekit"
)

func main() {
	// Create a simple traffic light state machine
	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)
	interp.Send(statekit.Event{Type: "TIMER"})
	fmt.Println(interp.State().Value)

}
Output:
green
yellow

Index

Examples

Constants

View Source
const (
	ErrCodeSnapshotMachineMismatch = "SNAPSHOT_MACHINE_MISMATCH"
	ErrCodeSnapshotInvalidState    = "SNAPSHOT_INVALID_STATE"
	ErrCodeSnapshotVersionMismatch = "SNAPSHOT_VERSION_MISMATCH"
)

Error codes for snapshot operations

View Source
const (
	StateTypeAtomic   = ir.StateTypeAtomic
	StateTypeCompound = ir.StateTypeCompound
	StateTypeFinal    = ir.StateTypeFinal
	StateTypeHistory  = ir.StateTypeHistory  // v2.0
	StateTypeParallel = ir.StateTypeParallel // v2.0

	HistoryTypeShallow = ir.HistoryTypeShallow // v2.0
	HistoryTypeDeep    = ir.HistoryTypeDeep    // v2.0
)

Re-export constants

Variables

View Source
var ErrActorAlreadyExists = errors.New("actor already exists")

ErrActorAlreadyExists is returned when spawning an actor with a duplicate ID

View Source
var ErrActorNotFound = errors.New("actor not found")

ErrActorNotFound is returned when an actor ID doesn't exist in the registry

View Source
var ErrActorStopped = errors.New("actor stopped")

ErrActorStopped is returned when sending to a stopped actor

View Source
var ErrLockHeld = errors.New("lock held by another node")

ErrLockHeld is returned when attempting to acquire a lock held by another node.

View Source
var ErrLockLost = errors.New("lock lost")

ErrLockLost is returned when a lock is lost unexpectedly.

View Source
var ErrNoParent = errors.New("no parent actor")

ErrNoParent is returned when SendParent is called on a root interpreter

Functions

func FromStruct

func FromStruct[M any, C any](registry *ActionRegistry[C]) (*ir.MachineConfig[C], error)

FromStruct builds a MachineConfig from a struct definition using the reflection DSL.

The struct M must embed MachineDef and define states using StateNode, CompoundNode, or FinalNode marker types with appropriate struct tags.

Actions and guards referenced in tags must be registered in the provided ActionRegistry.

Example:

type MyMachine struct {
    statekit.MachineDef `id:"example" initial:"idle"`
    Idle    statekit.StateNode `on:"START->running:canStart" entry:"logStart"`
    Running statekit.StateNode `on:"STOP->idle"`
}

registry := statekit.NewActionRegistry[MyContext]().
    WithAction("logStart", func(ctx *MyContext, e statekit.Event) { ... }).
    WithGuard("canStart", func(ctx MyContext, e statekit.Event) bool { ... })

machine, err := statekit.FromStruct[MyMachine, MyContext](registry)

func FromStructWithContext

func FromStructWithContext[M any, C any](registry *ActionRegistry[C], ctx C) (*ir.MachineConfig[C], error)

FromStructWithContext builds a MachineConfig with an initial context value.

Types

type Action

type Action[C any] func(ctx *C, event Event)

Action is a side-effect function executed during transitions. It receives a pointer to the context for modification and the triggering event.

func Choose

func Choose[C any](branches ...ChooseBranch[C]) Action[C]

Choose builds an Action that runs the Then of the first branch whose When guard passes, then stops — the action equivalent of XState's choose(). A branch with a nil When acts as an else. If no branch matches, it is a no-op.

Register it like any named action and reference it from transitions or entry/exit hooks:

WithAction("classify", statekit.Choose(
    statekit.ChooseBranch[Ctx]{When: isGold, Then: tagGold},
    statekit.ChooseBranch[Ctx]{When: isSilver, Then: tagSilver},
    statekit.ChooseBranch[Ctx]{Then: tagBronze}, // else
))

type ActionRegistry

type ActionRegistry[C any] struct {
	// contains filtered or unexported fields
}

ActionRegistry holds action and guard function implementations that are referenced by name in the reflection DSL.

ActionRegistry is not safe for concurrent use. It should be fully configured before calling FromStruct or FromStructWithContext.

func NewActionRegistry

func NewActionRegistry[C any]() *ActionRegistry[C]

NewActionRegistry creates a new empty action registry.

func (*ActionRegistry[C]) WithAction

func (r *ActionRegistry[C]) WithAction(name ActionType, action Action[C]) *ActionRegistry[C]

WithAction registers an action function by name. Returns the registry for method chaining.

func (*ActionRegistry[C]) WithGuard

func (r *ActionRegistry[C]) WithGuard(name GuardType, guard Guard[C]) *ActionRegistry[C]

WithGuard registers a guard function by name. Returns the registry for method chaining.

type ActionType

type ActionType = ir.ActionType

ActionType identifies a named action

type ActorID

type ActorID string

ActorID uniquely identifies a spawned child actor

type ActorMetadata

type ActorMetadata struct {
	// ID is the actor's unique identifier
	ID ActorID `json:"id"`

	// SpawnedInState is the state that spawned this actor
	SpawnedInState StateID `json:"spawned_in_state"`

	// Supervision is the supervision strategy for this actor
	Supervision SupervisionStrategy `json:"supervision"`

	// AutoForward lists event types that were auto-forwarded to this actor
	AutoForward []EventType `json:"auto_forward,omitempty"`
}

ActorMetadata captures information about a spawned actor for persistence (v0.14). This is a metadata-only snapshot - the actor's internal state is not captured. When restoring, actors must be manually respawned by the application.

type ActorRef

type ActorRef struct {
	// contains filtered or unexported fields
}

ActorRef provides a handle to communicate with a spawned child actor. It is safe to use concurrently from multiple goroutines.

func Spawn

func Spawn[ParentC, ChildC any](
	parent *Interpreter[ParentC],
	id ActorID,
	childMachine *ir.MachineConfig[ChildC],
	opts ...SpawnOption,
) (*ActorRef, error)

Spawn creates and starts a child actor from a machine configuration. The actor is associated with the current state and will be stopped when that state is exited (state-scoped lifecycle).

Options can configure supervision strategy, auto-forwarding, and completion handlers.

func SpawnWithContext

func SpawnWithContext[ParentC, ChildC any](
	parent *Interpreter[ParentC],
	id ActorID,
	childMachine *ir.MachineConfig[ChildC],
	initContext func(ParentC) ChildC,
	opts ...SpawnOption,
) (*ActorRef, error)

SpawnWithContext is like Spawn but allows initializing the child context from the parent context.

func (*ActorRef) Done

func (a *ActorRef) Done() <-chan struct{}

Done returns a channel that's closed when the actor stops. Use this to wait for actor completion.

func (*ActorRef) ID

func (a *ActorRef) ID() ActorID

ID returns the actor's unique identifier

func (*ActorRef) Send

func (a *ActorRef) Send(e Event) error

Send sends an event to the child actor. Returns ErrActorStopped if the actor has been stopped.

func (*ActorRef) Stop

func (a *ActorRef) Stop()

Stop gracefully stops the child actor. Safe to call multiple times.

type ChooseBranch

type ChooseBranch[C any] struct {
	// When gates this branch. A nil When always matches — use it as the
	// final else branch.
	When Guard[C]
	// Then is the action executed when this branch is selected. May be nil.
	Then Action[C]
}

ChooseBranch is one arm of a Choose action: when When passes (or is nil, acting as an "else"), Then runs and evaluation stops (v1.x).

type Clock

type Clock interface {
	// AfterFunc schedules fn to run after d. The returned Timer.Stop
	// cancels the callback if it has not yet fired.
	AfterFunc(d time.Duration, fn func()) Timer
}

Clock abstracts wall-clock timing so timer-driven behavior (delayed transitions, supervision timers) can be deterministically controlled in tests via FakeClock.

Production code uses the default systemClock; pass FakeClock via WithClock for tests.

func SystemClock

func SystemClock() Clock

SystemClock returns the default wall-clock implementation.

type ClusterMembership

type ClusterMembership interface {
	// Join registers this node with the cluster.
	Join(ctx context.Context, node ClusterNode) error

	// Leave removes this node from the cluster.
	Leave(ctx context.Context) error

	// Members returns all known cluster members.
	Members(ctx context.Context) ([]ClusterNode, error)

	// Watch returns a channel that receives membership changes.
	Watch(ctx context.Context) (<-chan MembershipEvent, error)
}

ClusterMembership provides cluster membership management. Implementations can use etcd, consul, or a custom gossip protocol.

type ClusterNode

type ClusterNode struct {
	ID       string
	Address  string
	JoinedAt time.Time
	LastSeen time.Time
	Metadata map[string]string
}

ClusterNode represents a node in the cluster.

type CompoundNode

type CompoundNode struct{}

CompoundNode is a marker type for defining compound (nested) states.

Use struct tags to configure the compound state:

  • initial:"childState" - Required initial child state
  • on:"EVENT->target" - Parent-level transitions
  • entry:"action" - Parent entry actions
  • exit:"action" - Parent exit actions

Child states are defined as fields within the struct that embeds CompoundNode.

Example:

type ActiveState struct {
    statekit.CompoundNode `initial:"idle" on:"RESET->done"`
    Idle    statekit.StateNode `on:"START->working"`
    Working statekit.StateNode `on:"STOP->idle"`
}

type ConsistentHashRouter

type ConsistentHashRouter struct {
	// Replicas is the number of virtual nodes per physical node.
	Replicas int
}

ConsistentHashRouter routes streams using consistent hashing.

func NewConsistentHashRouter

func NewConsistentHashRouter(replicas int) *ConsistentHashRouter

NewConsistentHashRouter creates a new consistent hash router.

func (*ConsistentHashRouter) IsLocal

func (r *ConsistentHashRouter) IsLocal(streamID string, members []ClusterNode, localNodeID string) bool

IsLocal returns true if this node should handle the stream.

func (*ConsistentHashRouter) RouteStream

func (r *ConsistentHashRouter) RouteStream(streamID string, members []ClusterNode) string

RouteStream returns the node that should handle the stream using consistent hashing.

type DistributedInterpreter

type DistributedInterpreter[C any] struct {
	// contains filtered or unexported fields
}

DistributedInterpreter wraps a PersistentInterpreter with distributed locking. It ensures only one node processes events for a stream at a time.

func NewDistributedInterpreter

func NewDistributedInterpreter[C any](
	ctx context.Context,
	streamID string,
	machine *ir.MachineConfig[C],
	eventStore EventStore,
	streamLock StreamLock,
	opts ...DistributedInterpreterOption[C],
) (*DistributedInterpreter[C], error)

NewDistributedInterpreter creates a new distributed interpreter. It acquires a lock for the stream before hydrating state.

func (*DistributedInterpreter[C]) Commit

func (di *DistributedInterpreter[C]) Commit(ctx context.Context) (int, error)

Commit persists uncommitted events.

func (*DistributedInterpreter[C]) Context

func (di *DistributedInterpreter[C]) Context() C

Context returns the current context.

func (*DistributedInterpreter[C]) Done

func (di *DistributedInterpreter[C]) Done() bool

Done returns true if in a final state.

func (*DistributedInterpreter[C]) ForceSnapshot

func (di *DistributedInterpreter[C]) ForceSnapshot(ctx context.Context) error

ForceSnapshot creates a snapshot.

func (*DistributedInterpreter[C]) LockHeld

func (di *DistributedInterpreter[C]) LockHeld() bool

LockHeld returns true if the lock is still held.

func (*DistributedInterpreter[C]) LockLost

func (di *DistributedInterpreter[C]) LockLost() <-chan struct{}

LockLost returns a channel that's closed when the lock is lost.

func (*DistributedInterpreter[C]) Matches

func (di *DistributedInterpreter[C]) Matches(stateID StateID) bool

Matches checks if current state matches or is descendant of given state.

func (*DistributedInterpreter[C]) Send

func (di *DistributedInterpreter[C]) Send(event Event) error

Send processes an event if the lock is still held.

func (*DistributedInterpreter[C]) SendAll

func (di *DistributedInterpreter[C]) SendAll(events []Event) error

SendAll processes multiple events.

func (*DistributedInterpreter[C]) State

func (di *DistributedInterpreter[C]) State() State[C]

State returns the current state.

func (*DistributedInterpreter[C]) Stop

func (di *DistributedInterpreter[C]) Stop(ctx context.Context) error

Stop releases the lock and stops the interpreter.

func (*DistributedInterpreter[C]) StreamID

func (di *DistributedInterpreter[C]) StreamID() string

StreamID returns the stream identifier.

func (*DistributedInterpreter[C]) UncommittedCount

func (di *DistributedInterpreter[C]) UncommittedCount() int

UncommittedCount returns the number of uncommitted events.

func (*DistributedInterpreter[C]) Version

func (di *DistributedInterpreter[C]) Version() int

Version returns the current stream version.

type DistributedInterpreterOption

type DistributedInterpreterOption[C any] func(*DistributedInterpreter[C])

DistributedInterpreterOption configures a DistributedInterpreter.

func WithDistributedSnapshotConfig

func WithDistributedSnapshotConfig[C any](config SnapshotConfig) DistributedInterpreterOption[C]

WithDistributedSnapshotConfig sets the snapshot configuration.

func WithDistributedSnapshotStore

func WithDistributedSnapshotStore[C any](store SnapshotStore[C]) DistributedInterpreterOption[C]

WithDistributedSnapshotStore sets the snapshot store.

func WithLockTTL

func WithLockTTL[C any](ttl time.Duration) DistributedInterpreterOption[C]

WithLockTTL sets the lock TTL and renewal interval. Defaults: TTL=30s, renewInterval=10s (renew at 1/3 of TTL).

type ErrConcurrencyConflict

type ErrConcurrencyConflict struct {
	StreamID        string
	ExpectedVersion int
	ActualVersion   int
}

ErrConcurrencyConflict is returned when optimistic concurrency check fails.

func (*ErrConcurrencyConflict) Error

func (e *ErrConcurrencyConflict) Error() string

type Event

type Event = ir.Event

Event represents a runtime event with optional payload

type EventStore

type EventStore interface {
	// AppendEvents atomically appends events to a stream.
	// Returns ErrConcurrencyConflict if expectedVersion doesn't match.
	AppendEvents(ctx context.Context, streamID string, expectedVersion int, events []PersistedEvent) error

	// LoadEvents loads all events for a stream from a specific version.
	// Pass fromVersion=0 to load all events.
	LoadEvents(ctx context.Context, streamID string, fromVersion int) ([]PersistedEvent, error)

	// GetStreamVersion returns the current version of a stream.
	// Returns 0 if the stream doesn't exist.
	GetStreamVersion(ctx context.Context, streamID string) (int, error)
}

EventStore defines the interface for event persistence. Implementations handle storage of events for event sourcing.

type EventType

type EventType = ir.EventType

EventType is a named event identifier

type FakeClock

type FakeClock struct {
	// contains filtered or unexported fields
}

FakeClock is a test-only Clock whose passage of time is controlled by Advance and Now. Safe for concurrent use; callbacks run on the goroutine that calls Advance.

func NewFakeClock

func NewFakeClock(anchor time.Time) *FakeClock

NewFakeClock returns a FakeClock anchored at the given time. Use time.Now() (or any deterministic timestamp) as the anchor.

func (*FakeClock) Advance

func (c *FakeClock) Advance(d time.Duration)

Advance moves the clock forward by d, firing any callbacks whose deadline falls within the new interval. Callbacks fire in deadline order; ties resolve by registration order.

Callbacks run synchronously on the caller's goroutine before Advance returns. This makes timer-driven tests deterministic.

func (*FakeClock) AfterFunc

func (c *FakeClock) AfterFunc(d time.Duration, fn func()) Timer

AfterFunc schedules fn to fire when the clock advances past d.

func (*FakeClock) Now

func (c *FakeClock) Now() time.Time

Now returns the FakeClock's current time. The clock does not advance on its own — call Advance to move it forward.

func (*FakeClock) PendingTimers

func (c *FakeClock) PendingTimers() int

PendingTimers reports how many timers are scheduled and not yet fired or stopped. Useful for test invariants.

type FinalNode

type FinalNode struct{}

FinalNode is a marker type for defining final states.

Final states indicate the machine has completed. They typically have no outgoing transitions.

Example:

Completed statekit.FinalNode

type Guard

type Guard[C any] func(ctx C, event Event) bool

Guard is a predicate that determines if a transition should occur. It receives the current context (by value) and the triggering event.

type GuardType

type GuardType = ir.GuardType

GuardType identifies a named guard

type HistoryBuilder

type HistoryBuilder[C any] struct {
	// contains filtered or unexported fields
}

HistoryBuilder provides a fluent API for constructing history states

func (*HistoryBuilder[C]) Deep

func (b *HistoryBuilder[C]) Deep() *HistoryBuilder[C]

Deep sets the history type to deep (remembers full leaf path)

func (*HistoryBuilder[C]) Default

func (b *HistoryBuilder[C]) Default(target StateID) *HistoryBuilder[C]

Default sets the default target state if no history is recorded

func (*HistoryBuilder[C]) End

func (b *HistoryBuilder[C]) End() *StateBuilder[C]

End completes the history state definition and returns to the parent StateBuilder

func (*HistoryBuilder[C]) Shallow

func (b *HistoryBuilder[C]) Shallow() *HistoryBuilder[C]

Shallow sets the history type to shallow (remembers immediate child)

type HistoryType

type HistoryType = ir.HistoryType

HistoryType specifies how history states remember previous states (v2.0)

type Interpreter

type Interpreter[C any] struct {
	// contains filtered or unexported fields
}

Interpreter is the statechart runtime that processes events and manages state

func NewInterpreter

func NewInterpreter[C any](machine *ir.MachineConfig[C], opts ...Option[C]) *Interpreter[C]

NewInterpreter creates a new interpreter for the given machine configuration

func ReplayEvents

func ReplayEvents[C any](
	ctx context.Context,
	machine *ir.MachineConfig[C],
	eventStore EventStore,
	streamID string,
) (*Interpreter[C], error)

ReplayEvents replays events from the store to a new interpreter. Useful for debugging or creating a read model.

func ReplayToVersion

func ReplayToVersion[C any](
	ctx context.Context,
	machine *ir.MachineConfig[C],
	eventStore EventStore,
	streamID string,
	targetVersion int,
) (*Interpreter[C], error)

ReplayToVersion replays events up to a specific version.

func (*Interpreter[C]) Close

func (i *Interpreter[C]) Close() error

Close implements io.Closer by calling Stop. Enables idiomatic `defer interp.Close()` cleanup.

func (*Interpreter[C]) Done

func (i *Interpreter[C]) Done() bool

Done returns true if the machine is in a final state

func (*Interpreter[C]) GetActor

func (i *Interpreter[C]) GetActor(id ActorID) *ActorRef

GetActor returns the ActorRef for the given ID, or nil if not found

func (*Interpreter[C]) HasTag

func (i *Interpreter[C]) HasTag(tag string) bool

HasTag reports whether any currently active state carries the given tag. "Active" means the current leaf state, its ancestors, and — when inside a parallel state — every active region leaf and its ancestors (v1.x).

func (*Interpreter[C]) Matches

func (i *Interpreter[C]) Matches(id StateID) bool

Matches checks if the current state matches the given state ID For hierarchical states, returns true if current state equals id or is a descendant of id For parallel states, also checks all active region states

Example
package main

import (
	"fmt"

	"go.klarlabs.de/statekit"
)

func main() {
	machine, _ := statekit.NewMachine[struct{}]("nested").
		WithInitial("parent").
		State("parent").
		WithInitial("child").
		State("child").End().
		Done().
		Build()

	interp := statekit.NewInterpreter(machine)
	interp.Start()

	// Current state is "child"
	fmt.Println(interp.State().Value)
	// Matches both the current state and its ancestors
	fmt.Println(interp.Matches("child"))
	fmt.Println(interp.Matches("parent"))

}
Output:
child
true
true

func (*Interpreter[C]) Restore

func (i *Interpreter[C]) Restore(s Snapshot[C]) error

Restore restores the interpreter to the state captured in the snapshot. The machine configuration must match (same machine ID). Returns an error if the snapshot is incompatible.

func (*Interpreter[C]) Send

func (i *Interpreter[C]) Send(event Event)

Send processes an event and potentially transitions to a new state

func (*Interpreter[C]) SendParent

func (i *Interpreter[C]) SendParent(event Event) error

SendParent sends an event to the parent interpreter. Returns ErrNoParent if this is a root interpreter (no parent).

func (*Interpreter[C]) SendTo

func (i *Interpreter[C]) SendTo(id ActorID, event Event) error

SendTo sends an event to a specific child actor by ID

func (*Interpreter[C]) Snapshot

func (i *Interpreter[C]) Snapshot() Snapshot[C]

Snapshot creates a snapshot of the current interpreter state. The snapshot captures all information needed to restore the interpreter to this exact state later.

Note on actors: Actor metadata is captured but actors are NOT automatically restored. The SpawnedActors field contains metadata about what actors were running, allowing the application to manually respawn them if needed.

func (*Interpreter[C]) Start

func (i *Interpreter[C]) Start()

Start initializes the interpreter and enters the initial state

func (*Interpreter[C]) State

func (i *Interpreter[C]) State() State[C]

State returns the current state of the interpreter

func (*Interpreter[C]) Stop

func (i *Interpreter[C]) Stop()

Stop cancels all active timers, services, invoked machines, and actors, then stops the interpreter

func (*Interpreter[C]) UpdateContext

func (i *Interpreter[C]) UpdateContext(fn func(ctx *C))

UpdateContext allows updating the context with a function

func (*Interpreter[C]) Use

func (i *Interpreter[C]) Use(p plugin.Plugin[C])

Use registers a plugin with the interpreter. Plugins receive callbacks during interpreter execution. Multiple plugins can be registered; they are called in registration order.

type InvokeBuilder

type InvokeBuilder[C any] struct {
	// contains filtered or unexported fields
}

InvokeBuilder provides a fluent API for constructing service invocations (v3.0)

The name is kept for backward compatibility; use the InvokeServiceBuilder alias for clearer intent next to InvokeMachineBuilder.

func (*InvokeBuilder[C]) Done

func (b *InvokeBuilder[C]) Done() *MachineBuilder[C]

Done completes the state definition and returns to the machine builder

func (*InvokeBuilder[C]) End

func (b *InvokeBuilder[C]) End() *StateBuilder[C]

End completes the invocation definition and returns to the StateBuilder

func (*InvokeBuilder[C]) ID

func (b *InvokeBuilder[C]) ID(id string) *InvokeBuilder[C]

ID sets a custom ID for this invocation

func (*InvokeBuilder[C]) OnDone

func (b *InvokeBuilder[C]) OnDone(target StateID) *InvokeBuilder[C]

OnDone sets the transition target when the service completes successfully

func (*InvokeBuilder[C]) OnDoneAction

func (b *InvokeBuilder[C]) OnDoneAction(action ActionType) *InvokeBuilder[C]

OnDoneAction sets an action to execute when the service completes successfully

func (*InvokeBuilder[C]) OnError

func (b *InvokeBuilder[C]) OnError(target StateID) *InvokeBuilder[C]

OnError sets the transition target when the service fails

func (*InvokeBuilder[C]) OnErrorAction

func (b *InvokeBuilder[C]) OnErrorAction(action ActionType) *InvokeBuilder[C]

OnErrorAction sets an action to execute when the service fails

type InvokeMachineBuilder

type InvokeMachineBuilder[C any] = MachineInvokeBuilder[C]

InvokeMachineBuilder is a clearer alias for MachineInvokeBuilder. Prefer this name in new code — it parallels InvokeServiceBuilder so the two service/machine invocation paths are self-documenting.

type InvokeServiceBuilder

type InvokeServiceBuilder[C any] = InvokeBuilder[C]

InvokeServiceBuilder is a clearer alias for InvokeBuilder. Prefer this name in new code — it parallels InvokeMachineBuilder so the two service/machine invocation paths are self-documenting.

type Lock

type Lock interface {
	// Renew extends the lock's TTL.
	Renew(ctx context.Context, ttl time.Duration) error

	// Release releases the lock.
	Release(ctx context.Context) error

	// Done returns a channel that's closed when the lock is lost.
	Done() <-chan struct{}
}

Lock represents a held distributed lock.

type MachineBuilder

type MachineBuilder[C any] struct {
	// contains filtered or unexported fields
}

MachineBuilder provides a fluent API for constructing state machines

func NewMachine

func NewMachine[C any](id string) *MachineBuilder[C]

NewMachine creates a new MachineBuilder with the given ID

Example (Hierarchical)
package main

import (
	"fmt"

	"go.klarlabs.de/statekit"
)

func main() {
	// Hierarchical state machine with nested states
	machine, _ := statekit.NewMachine[struct{}]("editor").
		WithInitial("editing").
		State("editing").
		WithInitial("idle").
		On("SAVE").Target("saved").End(). // Parent handles SAVE for all children
		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)      // Starts in leaf state
	fmt.Println(interp.Matches("editing")) // Matches parent
	interp.Send(statekit.Event{Type: "TYPE"})
	fmt.Println(interp.State().Value)
	interp.Send(statekit.Event{Type: "SAVE"}) // Bubbles up to parent
	fmt.Println(interp.State().Value)

}
Output:
idle
true
dirty
saved
Example (WithAction)
package main

import (
	"fmt"

	"go.klarlabs.de/statekit"
)

func main() {
	type Context struct {
		Count int
	}

	machine, _ := statekit.NewMachine[Context]("counter").
		WithInitial("idle").
		WithAction("increment", func(ctx *Context, e statekit.Event) {
			ctx.Count++
		}).
		State("idle").
		OnEntry("increment").
		On("COUNT").Target("idle").
		Done().
		Build()

	interp := statekit.NewInterpreter(machine)
	interp.Start()

	fmt.Println(interp.State().Context.Count) // Entry action runs on Start
	interp.Send(statekit.Event{Type: "COUNT"})
	fmt.Println(interp.State().Context.Count) // Entry action runs again on self-transition

}
Output:
1
2
Example (WithGuard)
package main

import (
	"fmt"

	"go.klarlabs.de/statekit"
)

func main() {
	type Context struct {
		Approved bool
	}

	machine, _ := statekit.NewMachine[Context]("approval").
		WithInitial("pending").
		WithContext(Context{Approved: true}).
		WithGuard("isApproved", func(ctx Context, e statekit.Event) bool {
			return ctx.Approved
		}).
		State("pending").
		On("SUBMIT").Target("approved").Guard("isApproved").
		Done().
		State("approved").Final().Done().
		Build()

	interp := statekit.NewInterpreter(machine)
	interp.Start()
	interp.Send(statekit.Event{Type: "SUBMIT"})

	fmt.Println(interp.State().Value)
}
Output:
approved

func (*MachineBuilder[C]) Build

func (b *MachineBuilder[C]) Build() (*ir.MachineConfig[C], error)

Build constructs the final MachineConfig from the builder

func (*MachineBuilder[C]) State

func (b *MachineBuilder[C]) State(id StateID) *StateBuilder[C]

State starts building a new state with the given ID

func (*MachineBuilder[C]) WithAction

func (b *MachineBuilder[C]) WithAction(name ActionType, action Action[C]) *MachineBuilder[C]

WithAction registers a named action

func (*MachineBuilder[C]) WithChildMachine

func (b *MachineBuilder[C]) WithChildMachine(name string, factory ir.ChildMachineFactory[C]) *MachineBuilder[C]

WithChildMachine registers a child machine factory for machine composition (v0.14). The factory creates a child interpreter when the machine is invoked.

func (*MachineBuilder[C]) WithContext

func (b *MachineBuilder[C]) WithContext(ctx C) *MachineBuilder[C]

WithContext sets the initial context value

func (*MachineBuilder[C]) WithGuard

func (b *MachineBuilder[C]) WithGuard(name GuardType, guard Guard[C]) *MachineBuilder[C]

WithGuard registers a named guard

func (*MachineBuilder[C]) WithInitial

func (b *MachineBuilder[C]) WithInitial(initial StateID) *MachineBuilder[C]

WithInitial sets the initial state ID

func (*MachineBuilder[C]) WithService

func (b *MachineBuilder[C]) WithService(name ServiceType, service Service[C]) *MachineBuilder[C]

WithService registers a named service (v3.0)

type MachineConfig

type MachineConfig[C any] = ir.MachineConfig[C]

MachineConfig is the immutable internal representation of a statechart

type MachineDef

type MachineDef struct{}

MachineDef is a marker type that must be embedded in a struct to define a state machine using the reflection DSL.

Use struct tags to configure the machine:

  • id:"machineId" - Required machine identifier
  • initial:"stateName" - Required initial state name

Example:

type MyMachine struct {
    statekit.MachineDef `id:"myMachine" initial:"idle"`
    Idle    statekit.StateNode `on:"START->running"`
    Running statekit.StateNode `on:"STOP->idle"`
}

type MachineInvokeBuilder

type MachineInvokeBuilder[C any] struct {
	// contains filtered or unexported fields
}

MachineInvokeBuilder provides a fluent API for constructing child machine invocations (v0.14)

The name is kept for backward compatibility; use the InvokeMachineBuilder alias for clearer intent next to InvokeServiceBuilder.

func (*MachineInvokeBuilder[C]) AutoForward

func (b *MachineInvokeBuilder[C]) AutoForward(events ...EventType) *MachineInvokeBuilder[C]

AutoForward adds event types that should be auto-forwarded to the child machine

func (*MachineInvokeBuilder[C]) Done

func (b *MachineInvokeBuilder[C]) Done() *MachineBuilder[C]

Done completes the state definition and returns to the machine builder

func (*MachineInvokeBuilder[C]) End

func (b *MachineInvokeBuilder[C]) End() *StateBuilder[C]

End completes the machine invocation definition and returns to the StateBuilder

func (*MachineInvokeBuilder[C]) ID

ID sets a custom ID for this machine invocation

func (*MachineInvokeBuilder[C]) OnDone

func (b *MachineInvokeBuilder[C]) OnDone(target StateID) *MachineInvokeBuilder[C]

OnDone sets the transition target when the child machine reaches a final state

func (*MachineInvokeBuilder[C]) OnDoneAction

func (b *MachineInvokeBuilder[C]) OnDoneAction(action ActionType) *MachineInvokeBuilder[C]

OnDoneAction sets an action to execute when the child machine completes

func (*MachineInvokeBuilder[C]) OnError

func (b *MachineInvokeBuilder[C]) OnError(target StateID) *MachineInvokeBuilder[C]

OnError sets the transition target when the child machine encounters an error

func (*MachineInvokeBuilder[C]) OnErrorAction

func (b *MachineInvokeBuilder[C]) OnErrorAction(action ActionType) *MachineInvokeBuilder[C]

OnErrorAction sets an action to execute when the child machine fails

type MachineSnapshot

type MachineSnapshot[C any] struct {
	// Current state value
	StateValue ir.StateID `json:"state_value"`

	// Full state path for hierarchical states
	StatePath []ir.StateID `json:"state_path,omitempty"`

	// Machine context
	Context C `json:"context"`

	// History memory for shallow history states
	HistoryShallow map[ir.StateID]ir.StateID `json:"history_shallow,omitempty"`

	// History memory for deep history states
	HistoryDeep map[ir.StateID]ir.StateID `json:"history_deep,omitempty"`

	// Parallel region states
	ActiveInParallel map[ir.StateID]ir.StateID `json:"active_in_parallel,omitempty"`

	// Current parallel state (if any)
	CurrentParallel ir.StateID `json:"current_parallel,omitempty"`

	// Timestamp when snapshot was taken
	Timestamp time.Time `json:"timestamp"`
}

MachineSnapshot captures the complete state of an interpreter for persistence.

type MembershipEvent

type MembershipEvent struct {
	Type MembershipEventType
	Node ClusterNode
}

MembershipEvent represents a cluster membership change.

type MembershipEventType

type MembershipEventType int

MembershipEventType indicates the type of membership change.

const (
	// MemberJoined indicates a new member joined.
	MemberJoined MembershipEventType = iota
	// MemberLeft indicates a member left gracefully.
	MemberLeft
	// MemberFailed indicates a member failed (no heartbeat).
	MemberFailed
)

type MemoryEventStore

type MemoryEventStore struct {
	// contains filtered or unexported fields
}

MemoryEventStore is an in-memory implementation of EventStore for testing.

func NewMemoryEventStore

func NewMemoryEventStore() *MemoryEventStore

NewMemoryEventStore creates a new in-memory event store.

func (*MemoryEventStore) AppendEvents

func (s *MemoryEventStore) AppendEvents(ctx context.Context, streamID string, expectedVersion int, events []PersistedEvent) error

AppendEvents atomically appends events to a stream.

func (*MemoryEventStore) GetStreamVersion

func (s *MemoryEventStore) GetStreamVersion(ctx context.Context, streamID string) (int, error)

GetStreamVersion returns the current version of a stream.

func (*MemoryEventStore) LoadEvents

func (s *MemoryEventStore) LoadEvents(ctx context.Context, streamID string, fromVersion int) ([]PersistedEvent, error)

LoadEvents loads all events for a stream from a specific version.

type MemorySnapshotStore

type MemorySnapshotStore[C any] struct {
	// contains filtered or unexported fields
}

MemorySnapshotStore is an in-memory implementation of SnapshotStore for testing.

func NewMemorySnapshotStore

func NewMemorySnapshotStore[C any]() *MemorySnapshotStore[C]

NewMemorySnapshotStore creates a new in-memory snapshot store.

func (*MemorySnapshotStore[C]) LoadSnapshot

func (s *MemorySnapshotStore[C]) LoadSnapshot(ctx context.Context, streamID string, maxVersion int) (*MachineSnapshot[C], int, error)

LoadSnapshot loads the latest snapshot at or before maxVersion.

func (*MemorySnapshotStore[C]) SaveSnapshot

func (s *MemorySnapshotStore[C]) SaveSnapshot(ctx context.Context, streamID string, version int, snapshot *MachineSnapshot[C]) error

SaveSnapshot saves a state snapshot at the given version.

type MemoryStreamLock

type MemoryStreamLock struct {
	// contains filtered or unexported fields
}

MemoryStreamLock is an in-memory implementation of StreamLock for testing. In production, use Redis, etcd, or PostgreSQL advisory locks.

func NewMemoryStreamLock

func NewMemoryStreamLock() *MemoryStreamLock

NewMemoryStreamLock creates a new in-memory stream lock.

func (*MemoryStreamLock) Acquire

func (s *MemoryStreamLock) Acquire(ctx context.Context, streamID string, ttl time.Duration) (Lock, error)

Acquire blocks until the lock is acquired or context is cancelled.

func (*MemoryStreamLock) TryAcquire

func (s *MemoryStreamLock) TryAcquire(ctx context.Context, streamID string, ttl time.Duration) (Lock, error)

TryAcquire attempts to acquire a lock without blocking.

type Option

type Option[C any] func(*Interpreter[C])

Option configures a new Interpreter at construction time.

func WithClock

func WithClock[C any](c Clock) Option[C]

WithClock overrides the interpreter's Clock. The default is the wall-clock; pass a FakeClock to make timer-driven behavior deterministic in tests.

type PendingTimer

type PendingTimer struct {
	// StateID is the state that owns this delayed transition
	StateID StateID `json:"state_id"`

	// TransitionIndex identifies which transition in the state
	TransitionIndex int `json:"transition_index"`

	// Target is the destination state
	Target StateID `json:"target"`

	// Remaining is how much time is left until the timer fires
	Remaining time.Duration `json:"remaining_ns"`
}

PendingTimer represents an active delayed transition that hasn't fired yet.

type PersistedEvent

type PersistedEvent struct {
	// ID is a unique identifier for this event (e.g., UUID)
	ID string `json:"id"`

	// StreamID identifies the aggregate/stream this event belongs to
	StreamID string `json:"stream_id"`

	// Type is the event type that triggered the transition
	Type EventType `json:"type"`

	// Version is the stream version (monotonically increasing per stream)
	Version int `json:"version"`

	// Timestamp when the event was recorded
	Timestamp time.Time `json:"timestamp"`

	// Payload contains serialized event data
	Payload json.RawMessage `json:"payload,omitempty"`

	// Metadata holds additional context (correlation ID, user ID, etc.)
	Metadata map[string]any `json:"metadata,omitempty"`

	// StateAfter is the state after processing this event
	StateAfter ir.StateID `json:"state_after"`
}

PersistedEvent represents a stored event in the event store.

type PersistentInterpreter

type PersistentInterpreter[C any] struct {
	// contains filtered or unexported fields
}

PersistentInterpreter wraps an Interpreter with event sourcing capabilities. All state transitions are persisted to an EventStore and can be replayed.

func NewPersistentInterpreter

func NewPersistentInterpreter[C any](
	ctx context.Context,
	streamID string,
	machine *ir.MachineConfig[C],
	eventStore EventStore,
	opts ...PersistentInterpreterOption[C],
) (*PersistentInterpreter[C], error)

NewPersistentInterpreter creates a new persistent interpreter. It automatically hydrates state from the event store if events exist.

func (*PersistentInterpreter[C]) Commit

func (pi *PersistentInterpreter[C]) Commit(ctx context.Context) (int, error)

Commit persists all uncommitted events to the event store. Returns the number of events committed.

func (*PersistentInterpreter[C]) Context

func (pi *PersistentInterpreter[C]) Context() C

Context returns a copy of the current context.

func (*PersistentInterpreter[C]) Done

func (pi *PersistentInterpreter[C]) Done() bool

Done returns true if the interpreter is in a final state.

func (*PersistentInterpreter[C]) ForceSnapshot

func (pi *PersistentInterpreter[C]) ForceSnapshot(ctx context.Context) error

ForceSnapshot creates a snapshot regardless of the configured strategy.

func (*PersistentInterpreter[C]) Matches

func (pi *PersistentInterpreter[C]) Matches(stateID StateID) bool

Matches checks if the current state matches or is a descendant of the given state.

func (*PersistentInterpreter[C]) Rollback

func (pi *PersistentInterpreter[C]) Rollback()

Rollback discards all uncommitted events.

func (*PersistentInterpreter[C]) Send

func (pi *PersistentInterpreter[C]) Send(event Event)

Send processes an event and records it for persistence. Events are recorded if they match a transition (including self-transitions). Events that don't match any transition are not recorded. Call Commit() to persist uncommitted events.

func (*PersistentInterpreter[C]) SendAll

func (pi *PersistentInterpreter[C]) SendAll(events []Event)

SendAll processes multiple events.

func (*PersistentInterpreter[C]) State

func (pi *PersistentInterpreter[C]) State() State[C]

State returns the current state.

func (*PersistentInterpreter[C]) Stop

func (pi *PersistentInterpreter[C]) Stop()

Stop stops the underlying interpreter.

func (*PersistentInterpreter[C]) StreamID

func (pi *PersistentInterpreter[C]) StreamID() string

StreamID returns the stream identifier.

func (*PersistentInterpreter[C]) UncommittedCount

func (pi *PersistentInterpreter[C]) UncommittedCount() int

UncommittedCount returns the number of uncommitted events.

func (*PersistentInterpreter[C]) Version

func (pi *PersistentInterpreter[C]) Version() int

Version returns the current stream version.

type PersistentInterpreterOption

type PersistentInterpreterOption[C any] func(*PersistentInterpreter[C])

PersistentInterpreterOption configures a PersistentInterpreter.

func WithSnapshotConfig

func WithSnapshotConfig[C any](config SnapshotConfig) PersistentInterpreterOption[C]

WithSnapshotConfig sets the snapshot configuration.

func WithSnapshotStore

func WithSnapshotStore[C any](store SnapshotStore[C]) PersistentInterpreterOption[C]

WithSnapshotStore sets the snapshot store for the interpreter.

type RegionBuilder

type RegionBuilder[C any] struct {
	// contains filtered or unexported fields
}

RegionBuilder provides a fluent API for constructing parallel regions (v2.0)

func (*RegionBuilder[C]) EndRegion

func (b *RegionBuilder[C]) EndRegion() *StateBuilder[C]

EndRegion completes the region and returns to the parent parallel state

func (*RegionBuilder[C]) State

func (b *RegionBuilder[C]) State(id StateID) *StateBuilder[C]

State starts building a state within this region

func (*RegionBuilder[C]) WithInitial

func (b *RegionBuilder[C]) WithInitial(initial StateID) *RegionBuilder[C]

WithInitial sets the initial state for this region

type RestoreError

type RestoreError struct {
	Code    string
	Message string
}

RestoreError represents an error during snapshot restoration.

func (*RestoreError) Error

func (e *RestoreError) Error() string

type Service

type Service[C any] = ir.Service[C]

Service is an async operation invoked when entering a state. It runs in a goroutine and can send events back to the machine. The service should respect the context for cancellation.

type ServiceContext

type ServiceContext[C any] = ir.ServiceContext[C]

ServiceContext provides the execution context for a service (v3.0)

type ServiceType

type ServiceType = ir.ServiceType

ServiceType identifies a named service (v3.0)

type Snapshot

type Snapshot[C any] struct {
	// MachineID identifies which machine type this snapshot belongs to
	MachineID string `json:"machine_id"`

	// Version is the machine version for compatibility checking
	Version string `json:"version,omitempty"`

	// CurrentState is the active state ID (leaf state, or parallel state ID)
	CurrentState StateID `json:"current_state"`

	// Context is the user-defined context data
	Context C `json:"context"`

	// ShallowHistory maps compound state IDs to their last active child
	ShallowHistory map[StateID]StateID `json:"shallow_history,omitempty"`

	// DeepHistory maps compound state IDs to their last active leaf
	DeepHistory map[StateID]StateID `json:"deep_history,omitempty"`

	// ActiveInParallel maps region IDs to their current leaf states
	// Only populated when snapshot was taken from a parallel state
	ActiveInParallel map[StateID]StateID `json:"active_in_parallel,omitempty"`

	// CurrentParallel holds the parallel state ID if currently in a parallel state
	CurrentParallel StateID `json:"current_parallel,omitempty"`

	// PendingTimers captures active delayed transitions
	PendingTimers []PendingTimer `json:"pending_timers,omitempty"`

	// SpawnedActors captures metadata about spawned child actors (v0.14)
	// Note: Actors are NOT automatically restored. This metadata allows
	// the application to manually respawn actors if needed.
	SpawnedActors []ActorMetadata `json:"spawned_actors,omitempty"`

	// CreatedAt is when the snapshot was taken
	CreatedAt time.Time `json:"created_at"`
}

Snapshot captures the complete state of an interpreter for persistence. It can be serialized to JSON and later used to restore an interpreter to the exact same state.

func (Snapshot[C]) MarshalJSON

func (s Snapshot[C]) MarshalJSON() ([]byte, error)

MarshalJSON serializes the snapshot to JSON.

func (*Snapshot[C]) UnmarshalJSON

func (s *Snapshot[C]) UnmarshalJSON(data []byte) error

UnmarshalJSON deserializes the snapshot from JSON.

type SnapshotConfig

type SnapshotConfig struct {
	// Strategy determines when to create snapshots
	Strategy SnapshotStrategy

	// Interval is the number of events between snapshots (for SnapshotByInterval)
	Interval int

	// TimeInterval is the duration between snapshots (for SnapshotByTime)
	TimeInterval time.Duration
}

SnapshotConfig configures snapshot behavior.

type SnapshotStore

type SnapshotStore[C any] interface {
	// SaveSnapshot saves a state snapshot at the given version.
	SaveSnapshot(ctx context.Context, streamID string, version int, snapshot *MachineSnapshot[C]) error

	// LoadSnapshot loads the latest snapshot at or before maxVersion.
	// Returns nil if no snapshot exists.
	LoadSnapshot(ctx context.Context, streamID string, maxVersion int) (*MachineSnapshot[C], int, error)
}

SnapshotStore defines the interface for state snapshot persistence. Snapshots enable faster state reconstruction by avoiding full event replay.

type SnapshotStrategy

type SnapshotStrategy int

SnapshotStrategy determines when snapshots are created.

const (
	// SnapshotNever disables automatic snapshots
	SnapshotNever SnapshotStrategy = iota

	// SnapshotByInterval creates snapshots every N events
	SnapshotByInterval

	// SnapshotOnFinal creates a snapshot when reaching a final state
	SnapshotOnFinal

	// SnapshotByTime creates snapshots after a time interval
	SnapshotByTime
)

type SpawnOption

type SpawnOption func(*spawnOptions)

SpawnOption configures actor spawning behavior

func WithAutoForward

func WithAutoForward(events ...EventType) SpawnOption

WithAutoForward configures events to automatically forward to the child

func WithOnDone

func WithOnDone(target StateID) SpawnOption

WithOnDone sets the target state when the child reaches a final state

func WithOnError

func WithOnError(target StateID) SpawnOption

WithOnError sets the target state when the child encounters an error

func WithSupervision

func WithSupervision(s SupervisionStrategy) SpawnOption

WithSupervision sets the supervision strategy for the spawned actor

type State

type State[C any] struct {
	Value   StateID // Current state ID (leaf state, or parallel state when in parallel)
	Context C       // Current context

	// Parallel state tracking (v2.0)
	// When inside a parallel state, maps region ID to its current leaf state
	// Empty when not in a parallel state
	ActiveInParallel map[StateID]StateID
}

State represents the current runtime state of an interpreter

func (State[C]) Matches

func (s State[C]) Matches(id StateID) bool

Matches checks if the current state matches the given state ID For parallel states, also checks if any region's current state matches

type StateBuilder

type StateBuilder[C any] struct {
	// contains filtered or unexported fields
}

StateBuilder provides a fluent API for constructing states

func (*StateBuilder[C]) After

func (b *StateBuilder[C]) After(d time.Duration) *TransitionBuilder[C]

After starts building a delayed transition that triggers automatically after the specified duration (v2.0)

func (*StateBuilder[C]) Always

func (b *StateBuilder[C]) Always() *TransitionBuilder[C]

Always starts building an eventless ("always") transition (v1.x). It is evaluated when the state is entered and after every transition, in declaration order; the first whose guard passes is taken. A target is required. Use multiple Always() calls to express guarded routing with a final guardless fallback.

func (*StateBuilder[C]) Done

func (b *StateBuilder[C]) Done() *MachineBuilder[C]

Done completes the state definition and returns the root MachineBuilder.

Watch out: when called from a nested StateBuilder, Done() teleports the chain all the way back to the machine root, skipping any intermediate parent states. This is rarely what you want for nested states. Prefer End() for "back one level" or EndMachine() for "back to the root" — both are clearer at a glance.

func (*StateBuilder[C]) End

func (b *StateBuilder[C]) End() *StateBuilder[C]

End completes a nested state and returns to the parent StateBuilder Use this instead of Done() when building nested states

func (*StateBuilder[C]) EndMachine

func (b *StateBuilder[C]) EndMachine() *MachineBuilder[C]

EndMachine completes the state definition and returns the root MachineBuilder. Equivalent to Done() but its name makes the intent unambiguous when reading nested-state-builder chains.

func (*StateBuilder[C]) EndState

func (b *StateBuilder[C]) EndState() *RegionBuilder[C]

EndState completes a state within a region and returns to the RegionBuilder (v2.0) Use this instead of End() when building states inside parallel regions

func (*StateBuilder[C]) Final

func (b *StateBuilder[C]) Final() *StateBuilder[C]

Final marks this state as a final state

func (*StateBuilder[C]) History

func (b *StateBuilder[C]) History(id StateID) *HistoryBuilder[C]

History starts building a history state within this compound state (v2.0) History states remember the last active child and transition back to it

func (*StateBuilder[C]) Invoke

func (b *StateBuilder[C]) Invoke(src ServiceType) *InvokeBuilder[C]

Invoke starts building a service invocation for this state (v3.0) The service is started when entering the state and cancelled when exiting

func (*StateBuilder[C]) InvokeMachine

func (b *StateBuilder[C]) InvokeMachine(machineRef string) *MachineInvokeBuilder[C]

InvokeMachine starts building a child machine invocation for this state (v0.14). The child machine is spawned when entering the state and stopped when exiting. The machineRef must match a name registered with WithChildMachine.

func (*StateBuilder[C]) On

func (b *StateBuilder[C]) On(event EventType) *TransitionBuilder[C]

On starts building a new transition triggered by the given event

func (*StateBuilder[C]) OnEntry

func (b *StateBuilder[C]) OnEntry(action ActionType) *StateBuilder[C]

OnEntry adds an entry action to the state

func (*StateBuilder[C]) OnExit

func (b *StateBuilder[C]) OnExit(action ActionType) *StateBuilder[C]

OnExit adds an exit action to the state

func (*StateBuilder[C]) Parallel

func (b *StateBuilder[C]) Parallel() *StateBuilder[C]

Parallel marks this state as a parallel state (v2.0) Use Region() to add orthogonal regions that execute simultaneously

func (*StateBuilder[C]) Region

func (b *StateBuilder[C]) Region(id StateID) *RegionBuilder[C]

Region starts building a new region within this parallel state (v2.0)

func (*StateBuilder[C]) State

func (b *StateBuilder[C]) State(id StateID) *StateBuilder[C]

State starts building a nested child state

func (*StateBuilder[C]) Tags

func (b *StateBuilder[C]) Tags(tags ...string) *StateBuilder[C]

Tags attaches one or more tags to the state for lightweight querying via Interpreter.HasTag (v1.x).

func (*StateBuilder[C]) WithInitial

func (b *StateBuilder[C]) WithInitial(initial StateID) *StateBuilder[C]

WithInitial sets the initial child state for a compound state

type StateID

type StateID = ir.StateID

StateID uniquely identifies a state within a machine

type StateNode

type StateNode struct{}

StateNode is a marker type for defining atomic states in the reflection DSL.

Use struct tags to configure the state:

  • on:"EVENT->target" - Define a transition (can specify multiple with comma)
  • on:"EVENT->target:guard" - Transition with guard condition
  • on:"EVENT->target/action1;action2" - Transition with actions
  • on:"EVENT->target/action:guard" - Transition with action and guard
  • entry:"action1,action2" - Entry actions
  • exit:"action1,action2" - Exit actions

Example:

Idle statekit.StateNode `on:"START->running:canStart" entry:"logIdle"`

type StateType

type StateType = ir.StateType

StateType represents the kind of state node

type StreamLock

type StreamLock interface {
	// Acquire attempts to acquire a lock for the given stream.
	// Returns a Lock handle if successful, or an error if the lock is held by another node.
	// The lock should automatically expire after ttl if not renewed.
	Acquire(ctx context.Context, streamID string, ttl time.Duration) (Lock, error)

	// TryAcquire attempts to acquire a lock without blocking.
	// Returns ErrLockHeld if the lock is already held by another node.
	TryAcquire(ctx context.Context, streamID string, ttl time.Duration) (Lock, error)
}

StreamLock provides distributed locking for state machine streams. Implementations can use Redis, etcd, PostgreSQL advisory locks, etc.

type StreamRouter

type StreamRouter interface {
	// RouteStream returns the node ID that should handle the stream.
	RouteStream(streamID string, members []ClusterNode) string

	// IsLocal returns true if this node should handle the stream.
	IsLocal(streamID string, members []ClusterNode, localNodeID string) bool
}

StreamRouter determines which node should handle a given stream. This enables consistent hashing for stream distribution.

type SupervisionStrategy

type SupervisionStrategy int

SupervisionStrategy defines how parent handles child actor errors

const (
	// SupervisionEscalate bubbles the error to the parent via xstate.error.actor.<id> event
	SupervisionEscalate SupervisionStrategy = iota
	// SupervisionRecover logs the error and continues without stopping the child
	SupervisionRecover
	// SupervisionRestart stops the child and restarts it with initial state
	SupervisionRestart
	// SupervisionStop stops the child silently without generating an error event
	SupervisionStop
)

type Timer

type Timer interface {
	// Stop prevents the callback from firing. Returns true if the
	// call stops the timer, false if the timer has already expired
	// or has been stopped.
	Stop() bool
}

Timer is the cancelable handle returned by Clock.AfterFunc. Matches the Stop semantics of *time.Timer.

type TransitionBuilder

type TransitionBuilder[C any] struct {
	// contains filtered or unexported fields
}

TransitionBuilder provides a fluent API for constructing transitions

func (*TransitionBuilder[C]) After

func (b *TransitionBuilder[C]) After(d time.Duration) *TransitionBuilder[C]

After starts a new delayed transition on the same state (chainable) (v2.0)

func (*TransitionBuilder[C]) Do

func (b *TransitionBuilder[C]) Do(action ActionType) *TransitionBuilder[C]

Do adds an action to be executed during the transition

func (*TransitionBuilder[C]) Done

func (b *TransitionBuilder[C]) Done() *MachineBuilder[C]

Done completes the state definition and returns to the machine builder

func (*TransitionBuilder[C]) End

func (b *TransitionBuilder[C]) End() *StateBuilder[C]

End completes the transition and returns to the parent StateBuilder Use this instead of Done() when building transitions in nested states

func (*TransitionBuilder[C]) EndMachine

func (b *TransitionBuilder[C]) EndMachine() *MachineBuilder[C]

EndMachine completes the state definition and returns to the MachineBuilder. Equivalent to Done() but its name makes the intent unambiguous in nested-state-builder chains.

func (*TransitionBuilder[C]) EndState

func (b *TransitionBuilder[C]) EndState() *RegionBuilder[C]

EndState completes the transition and returns to the RegionBuilder (v2.0) Use this when building transitions in states inside parallel regions

func (*TransitionBuilder[C]) Guard

func (b *TransitionBuilder[C]) Guard(guard GuardType) *TransitionBuilder[C]

Guard sets the guard condition for the transition

func (*TransitionBuilder[C]) Internal

func (b *TransitionBuilder[C]) Internal() *TransitionBuilder[C]

Internal marks the transition as internal (v1.x): its actions run without exiting or re-entering the source state — entry/exit hooks do not fire and the active state does not change. Target is optional; when set it must be the owning state. Contrast with an external self-transition (a plain Target back to the same state), which does exit and re-enter.

func (*TransitionBuilder[C]) On

func (b *TransitionBuilder[C]) On(event EventType) *TransitionBuilder[C]

On starts a new transition on the same state (chainable)

func (*TransitionBuilder[C]) Raise

func (b *TransitionBuilder[C]) Raise(events ...EventType) *TransitionBuilder[C]

Raise enqueues internal events emitted when this transition is taken (v1.x). Raised events are processed in the same macrostep — before control returns to the caller and before any externally sent event.

func (*TransitionBuilder[C]) Target

func (b *TransitionBuilder[C]) Target(target StateID) *TransitionBuilder[C]

Target sets the target state for the transition

Directories

Path Synopsis
Package ai provides primitives for LLM-driven state transitions.
Package ai provides primitives for LLM-driven state transitions.
Package aiplugin provides plugins for AI/LLM-driven state machines.
Package aiplugin provides plugins for AI/LLM-driven state machines.
cmd
statekit command
Package main provides the statekit CLI tool for visualizing state machines.
Package main provides the statekit CLI tool for visualizing state machines.
statekit-mcp command
statekit/commands
Package commands provides CLI commands for the statekit tool.
Package commands provides CLI commands for the statekit tool.
Package debug provides utilities for debugging and inspecting statekit state machines.
Package debug provides utilities for debugging and inspecting statekit state machines.
examples
actor_supervisor command
Package main demonstrates the actor model features of statekit.
Package main demonstrates the actor model features of statekit.
form_wizard command
Package main demonstrates history states with a multi-step form wizard.
Package main demonstrates history states with a multi-step form wizard.
game_save command
Package main demonstrates persistence with snapshots for a game save system.
Package main demonstrates persistence with snapshots for a game save system.
incident_lifecycle command
Package main demonstrates an SRE incident lifecycle using hierarchical states.
Package main demonstrates an SRE incident lifecycle using hierarchical states.
llm_agent command
Package main demonstrates a deterministic LLM agent runtime using statekit + aiplugin.
Package main demonstrates a deterministic LLM agent runtime using statekit + aiplugin.
logging_plugin command
Package main demonstrates the plugin system for extending interpreter behavior.
Package main demonstrates the plugin system for extending interpreter behavior.
order_workflow command
Package main demonstrates an e-commerce order workflow using the reflection DSL.
Package main demonstrates an e-commerce order workflow using the reflection DSL.
pedestrian_light
Package pedestrianlight implements a pedestrian crossing signal with hierarchical states.
Package pedestrianlight implements a pedestrian crossing signal with hierarchical states.
session_timeout command
Package main demonstrates delayed (timed) transitions with a session timeout.
Package main demonstrates delayed (timed) transitions with a session timeout.
stripe_webhook command
Package main models a Stripe webhook saga as a statechart with the outbox pattern.
Package main models a Stripe webhook saga as a statechart with the outbox pattern.
text_editor command
Package main demonstrates parallel (orthogonal) states with a text editor.
Package main demonstrates parallel (orthogonal) states with a text editor.
Package generate provides Go code generation from Statekit Native JSON definitions.
Package generate provides Go code generation from Statekit Native JSON definitions.
Package health provides liveness and readiness probes for statekit state machines.
Package health provides liveness and readiness probes for statekit state machines.
Package http provides HTTP middleware and handlers for statekit state machines.
Package http provides HTTP middleware and handlers for statekit state machines.
internal
ir
parser
Package parser provides reflection-based parsing for struct-defined state machines.
Package parser provides reflection-based parsing for struct-defined state machines.
Package lint provides static analysis for statekit state machines.
Package lint provides static analysis for statekit state machines.
Package mcp provides an MCP (Model Context Protocol) server for creating, managing, and visualizing statekit state machines.
Package mcp provides an MCP (Model Context Protocol) server for creating, managing, and visualizing statekit state machines.
Package metrics provides Prometheus metrics integration for statekit state machines.
Package metrics provides Prometheus metrics integration for statekit state machines.
Package otel provides OpenTelemetry tracing integration for statekit state machines.
Package otel provides OpenTelemetry tracing integration for statekit state machines.
Package plugin provides a hook system for extending interpreter behavior.
Package plugin provides a hook system for extending interpreter behavior.
Package statetest provides utilities for testing statekit state machines.
Package statetest provides utilities for testing statekit state machines.
viz
Package viz provides visualization models and renderers for state machines.
Package viz provides visualization models and renderers for state machines.
ascii
Package ascii provides ASCII/Unicode box diagram rendering for state machines.
Package ascii provides ASCII/Unicode box diagram rendering for state machines.
goparser
Package goparser provides Go source code parsing to extract state machine definitions.
Package goparser provides Go source code parsing to extract state machine definitions.
mermaid
Package mermaid provides Mermaid stateDiagram-v2 rendering for state machines.
Package mermaid provides Mermaid stateDiagram-v2 rendering for state machines.
tui
Package tui provides an interactive terminal UI for visualizing state machines.
Package tui provides an interactive terminal UI for visualizing state machines.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL