Skip to content

stablekernel/crucible

Crucible

Go Reference Go Report Card Go Version Docs

PRs welcome Contributor Covenant 2.1 OpenSSF Scorecard License: Apache 2.0

The Crucible sky-squid mascot

Forge event-driven services in Go.

Crucible is a multi-module Go toolkit for building event-driven services. Its design philosophy is thin seams, no-op defaults, no forced dependencies: every cross-cutting concern (logging, tracing, metrics, IDs, time) is a small, consumer-providable interface with a do-nothing default. You bring your logger, your tracer, your clock. Crucible never makes you adopt its choices, and never leaks a third-party type into a public signature.

The state engine is the extreme end of this: stdlib-only, with no injected IO at all. The IO modules carry the heavier seams via injection, but follow the same rule. Defaults are no-ops, nothing third-party is forced on the consumer.

This "stdlib-only" guarantee is about the library you import: the state engine and its seams pull in nothing third-party. The standalone crucible CLI is a leaf tool, not a library, and is the one exception — it embeds a pure-Go (WebAssembly) Graphviz only for render -format svg|png, so you can render images without installing Graphviz. That convenience lives entirely in the CLI binary; it never enters the state engine or any module you import. See THIRD_PARTY_NOTICES.md for attribution.

Documentation

Guides, concepts, the food-delivery example, and the generated API reference live in the documentation site:

Architecture

Three core modules form the ingest → drive → emit spine: source brings events in, state decides what happens, and sink fans the resulting effects out. Each is a thin seam you can adopt on its own, and none imports another.

%%{init: {'theme':'base','themeVariables':{'background':'transparent','primaryColor':'#8a929c','primaryBorderColor':'#d9620a','primaryTextColor':'#16191d','lineColor':'#b06a28','defaultLinkColor':'#b06a28','titleColor':'#b5500c','mainBkg':'#8a929c','nodeBorder':'#d9620a','nodeTextColor':'#16191d','labelColor':'#16191d','edgeLabelBackground':'#c0c7cf','altBackground':'#aab1ba','clusterBkg':'#aab1ba','clusterBorder':'#d9620a'}}}%%
flowchart LR
    streams[(external streams)] -->|source| engine[state engine]
    engine -->|sink| destinations[(destinations)]
Loading

state — the statechart engine

A stdlib-only statechart engine with no injected IO. Machines are pure: a Fire folds an event into a new instance and emits effects as plain data, leaving persistence and dispatch to the host.

%%{init: {'theme':'base','themeVariables':{'background':'transparent','primaryColor':'#8a929c','primaryBorderColor':'#d9620a','primaryTextColor':'#16191d','lineColor':'#b06a28','defaultLinkColor':'#b06a28','titleColor':'#b5500c','mainBkg':'#8a929c','nodeBorder':'#d9620a','nodeTextColor':'#16191d','labelColor':'#16191d','edgeLabelBackground':'#c0c7cf','altBackground':'#aab1ba','clusterBkg':'#aab1ba','clusterBorder':'#d9620a'}}}%%
stateDiagram-v2
    [*] --> Idle
    Idle --> Working: Start [guard]
    Working --> Working: Progress / emit effect
    Working --> Done: Finish
    Working --> Idle: Reset
    Done --> [*]
Loading

source — the ingress seam

Consumes external streams (Kafka, JetStream, Redis, CDC, and more) and drives a machine, with the ack tied to a durable transition so redelivery is safe.

%%{init: {'theme':'base','themeVariables':{'background':'transparent','primaryColor':'#8a929c','primaryBorderColor':'#d9620a','primaryTextColor':'#16191d','lineColor':'#b06a28','defaultLinkColor':'#b06a28','titleColor':'#b5500c','mainBkg':'#8a929c','nodeBorder':'#d9620a','nodeTextColor':'#16191d','labelColor':'#16191d','edgeLabelBackground':'#c0c7cf','altBackground':'#aab1ba','clusterBkg':'#aab1ba','clusterBorder':'#d9620a'}}}%%
flowchart LR
    stream[(stream)] --> decode[decode / codec] --> route["route to (key, event)"] --> fire["Fire on instance"] --> commit[durable commit] --> ack[ack]
Loading

sink — the egress seam

Fans emitted effects out to many destinations through a Manifold, fire-and-forget; one outlet's failure never stops the rest.

%%{init: {'theme':'base','themeVariables':{'background':'transparent','primaryColor':'#8a929c','primaryBorderColor':'#d9620a','primaryTextColor':'#16191d','lineColor':'#b06a28','defaultLinkColor':'#b06a28','titleColor':'#b5500c','mainBkg':'#8a929c','nodeBorder':'#d9620a','nodeTextColor':'#16191d','labelColor':'#16191d','edgeLabelBackground':'#c0c7cf','altBackground':'#aab1ba','clusterBkg':'#aab1ba','clusterBorder':'#d9620a'}}}%%
flowchart LR
    effect[emitted effect] --> manifold[Manifold]
    manifold --> a[destination A]
    manifold --> b[destination B]
    manifold --> c[destination C]
Loading

Modules

Each module is independently versioned (per-module SemVer) and carries its own stability label.

Module What it is Status
state Domain-agnostic statechart engine. Stdlib-only, no IO. v1.0.0 (stable)
state subpackages analysis, evolution, conformance, verify: diagnostics over the IR. advisory
state/expr CEL-backed guards type-checked against the context schema. stable contract (v0.1.0)
gen Eject codegen: a machine's IR into typed Go stubs and registry wiring. v0.1.0
cmd/crucible Headless IR CLI: lint, render, diff, validate, eject. v0.1.0
telemetry Vendor-neutral tracing/metrics seam, plus slog, otel, datadog adapters. experimental
sink Egress fan-out, fire-and-forget. 20+ destinations: SQL, Dynamo, S3, Kafka, NATS, Redis, StatsD, … experimental
source Ingress: consume streams and drive machines, ack on durable transition. Inlets: Kafka, JetStream, Redis, CloudEvents, CDC; opt-in retry/DLQ/idempotency/schema middleware. experimental
durable Durable-execution runtime: record and replay to survive a crash. experimental
cluster Distribution runtime: remote actors, supervision, and live migration over a pluggable transport (in-memory transport tested; gRPC transport module behind the same interface). experimental
wasm Run state behaviors as WebAssembly: polyglot guards over a JSON ABI via wazero. experimental
broker Message broker seam: publish/subscribe transport with injected adapters. planned

Status

state is released at v1.0.0 with a frozen public contract. It is a complete, embeddable statechart engine: hierarchical, parallel, and final states, history, guard combinators, delayed transitions, invoked services, an actor model, snapshots, and JSON (de)serialization.

  • state subpackages (analysis, evolution, conformance, verify): advisory. They ship inside v1.0 but sit outside the frozen contract and may change in a minor release.
  • state/expr: a separate module at v0.1.0. The module version is pre-1.0, but its expression semantics are a committed, stable contract.
  • gen and cmd/crucible: released at v0.1.0, versioned independently of state and free to move at their own pace.
  • Everything else is experimental and may change before it reaches v1: telemetry, sink, and source (with all adapters, codecs, and middleware), plus the host-side runtimes durable, cluster, transport, and wasm. broker is planned.

See STABILITY.md for what each label promises, and CHANGELOG.md for the per-module release index.

Roadmap

Two kinds of seam frame the work ahead, and both build on the engine without reaching into the kernel: the IO edges where effects leave and events arrive, and the serializable IR as a first-class artifact anything can read or write.

The kernel emits effects as pure data; a small family of bring-your-own-adapter IO seams moves events to and from the outside world, each defaulting to a no-op and forcing nothing third-party on the consumer:

  • broker (planned): pub/sub transport. Publish emitted events and subscribe machines to external streams.
  • sink: egress fan-out. Dispatch emitted effects to many outlets (SQL, Dynamo, StatsD, and more), fire-and-forget. Docs.
  • source: ingress. Subscribe external streams and drive machines, with the ack tied to a durable transition; the symmetric counterpart to sink. Docs. The source/cdc codec decodes Debezium/OpenCDC change-event topics into typed change events; a native database write-ahead-log connector (logical replication slot, binlog) remains future work.

A small set of tools works the IR directly:

  • IR CLI (cmd/crucible): headless IR tooling for CI. Lint reachability and nondeterminism, render diagrams, diff and validate, and classify version diffs straight from a machine's serialized IR, no behavior bound.
  • Eject codegen (gen): turn a machine's IR into typed Go stub source. Each referenced behavior becomes a panic-bodied stub typed to the exact engine signature, plus a Provide function that wires them against the registry, so the host fills in bodies against a contract the compiler already checks.
  • Visual editor (planned): a browser workbench over the IR. Author, simulate, and inspect machines, with reachability and version-diff overlays from the existing analysis and evolution packages.

Durable state and event persistence is tracked separately with the durable runtime, not here.

Contributing

Contributions are welcome. See CONTRIBUTING.md for dev setup, the Mage targets, conventional commits, and the DCO sign-off requirement. By participating you agree to the Code of Conduct.

License

Licensed under the Apache License, Version 2.0. See NOTICE for attribution.

About

Forge event-driven services in Go

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors

Languages