asyncmachine-go

repository·main·Indexed 41 days ago

https://github.com/pancsta/asyncmachine-go

A distributed workflow engine and declarative execution model for Go that uses a clock-based state machine to manage complex, asynchronous logic. It implements the Actor model and Aspect-Oriented Programming (AOP) by binding code to states rather than transitions. Features include aRPC for state machine mutation, support for multi-states, and a TUI debugger called am-dbg.

Tokens
63.4K
Snippets
173
Records
264
Agent score
51%

What's inside asyncmachine-go

  1. What is asyncmachine-go?

    main

    /pkg/machine is a nondeterministic, multi-state, clock-based, relational, optionally accepting, and non-blocking state machine. It functions as a rules engine designed to orchestrate blocking APIs into controllable asynchronous state machines.

    Key operational concepts:

    • State Mutations: Write operations.
    • State Checking: Read operations (checking active states).
    • State Waiting: Subscriptions to state changes.

    Unlike a standard Finite State Machine (FSM) that transitions linearly (e.g., A -> B -> C), asyncmachine can transition between sets of states (e.g., AB -> BC), executing a complex lifecycle of handlers including Enter, Exit, State, and End for negotiation and consensus.

  2. Overview of asyncmachine-go packages

    main

    The project is organized into several specialized packages:

    • pkg/machine: The core state machine engine. It is dependency-free and semver compatible.
    • pkg/states: Provides reusable state schemas, handlers, and piping utilities.
    • pkg/helpers: Utility functions for working with asynchronous state machines.
    • pkg/telemetry: Exporters for debugging, metrics, traces, and logs.
    • pkg/rpc: Enables remote state machines with an API identical to local ones.
    • pkg/history: Tools for tracking and traversing state history in memory, KV stores, or SQL.
    • pkg/integrations: Connectors for JSON, NATS, MCP, and yaegi.
    • pkg/graph: A directional multigraph for managing connected state machines.
    • pkg/node: Implements distributed worker pools with supervisors.
    • pkg/pubsub: Decentralized PubSub based on libp2p gossipsub.
  3. Explore AI-related tools and integrations for asyncmachine-go

    main

    Several external and internal resources support AI workflows with asyncmachine-go:

    • secai: An AI layer built on top of asyncmachine.
    • context7: Provides a search engine, chatbot, and an MCP server specifically for asyncmachine-go documentation.
    • SKILL.md files: Located in /docs/editors, these files provide varying levels of detail for AI-assisted development.

    Note: While these tools exist, the project recommends using AI primarily for boilerplate generation, search, and indexing rather than core logic development.

  4. Explore asyncmachine-go packages and tools

    main

    The project is modular. Beyond the core, you can use various packages and tools to extend functionality:

    Core Extensions (/pkg)

    • pkg/states: Reusable state schemas, handlers, and piping.
    • pkg/helpers: Useful functions for working with async state machines.
    • pkg/telemetry: Telemetry exporters for debugging, metrics, traces, and logs.
    • pkg/rpc: Remote state machines using the same API as local ones.
    • pkg/history: History tracking and traversal (supports Key-Value and SQL).
    • pkg/integrations: Integrations for JSON, NATS, and MCP.
    • pkg/graph: Directional multigraph of connected state machines.
    • pkg/node: Distributed worker pools with supervisors.
    • pkg/pubsub: Decentralized PubSub based on libp2p gossipsub.

    Developer Tools (/tools)

    • tools/cmd/am-dbg: Multi-client TUI debugger.
    • tools/cmd/am-gen: Generates schema files and Grafana dashboards.
    • tools/cmd/arpc: Network-native REPL and CLI.
    • tools/cmd/am-vis: Generates D2 diagrams.
    • tools/cmd/am-relay: Rotates logs and relays WASM.
  5. What is piping in asyncmachine-go?

    main

    A pipe binds a transition handler from a source state-machine to a target state-machine, causing the target to mutate when the source transitions.

    Key characteristics:

    • Only final handlers are subject to piping.
    • Mutations are non-blocking for the source transition; they are queued instead.
    • The target state-machine can reject the mutation during the negotiation phase.
    • Pipes work within the same Golang process. For network-based piping, combine with /pkg/rpc.
  6. What is pkg/history and how does it work?

    main

    /pkg/history provides mutation history tracking and traversal for state machines. It tracks rich machine time information, including subsets for tracked states, various diffs, sums, and human time (UTC). This data is essential for making informed decisions about state flow and can be used to resurrect state machines after a restart.

    Key Concepts:

    • Resurrection: State machines can be restored using Machine.Import(), which adds a MachineRestored state. During import, MachineTick increments by +1.
    • MachineRecord: Can optionally store the full schema and an ordered list of state names to restore dynamic state machines.
    • Tracer API: Works with both local and network machines via the Tracer API.
    • Thread Safety: All methods are thread-safe and IDs are deterministic.
  7. What is aRPC and how does it work?

    main

    aRPC is a transparent RPC implementation for state machines built on top of asyncmachine-go. It is clock-based and optimized for performance by executing most API methods locally, as state changes are regularly pushed from the server to the client.

    Key characteristics:

    • Clock-based synchronization: Uses machine time pushes to keep client and server in sync.
    • Local execution: Many methods (like Is, When, NewStateCtx) are evaluated locally on the client using the synchronized state.
    • Built on: cenkalti/rpc2 (a net/rpc fork) and soheilhy/cmux, with optional tmc/go-iroh overlay for UDP/QUIC, encryption, and NAT bypass.
    • Topology: While one RPC server handles one client at a time, a single state source can have multiple RPC servers attached via the Tracer API, allowing for tree-like structures of remote network-machines.
  8. What is a "state" in asyncmachine?

    main

    A state is a binary ID representing a status, switch, or flag (e.g., process RUNNING or car BROKEN).

    Design Guidance: Only use states for flow-related nodes. If a piece of logic does not need to be treated as a separate "entity-in-time," it should not be orchestrated as a separate state. If you are unsure how to implement a feature, the general rule of thumb is: "Make it a state."

  9. Understand the PubSub Host machine architecture

    main

    The PubSub host is implemented as a simple event loop using multi-states. It relies heavily on Machine.Eval() to drive transitions.

    Key characteristics of the PubSub host machine include:

    • Multi-states: It uses multiple concurrent states to manage different aspects of the protocol.
    • Communication: States receive responses via argument channels.
    • State Categories: The machine manages states related to peers (e.g., PeersPending, GetPeers), peer interactions (e.g., PublishMessage, BlacklistPeer), topic management (e.g., AddTopic, AnnouncingTopic), subscription handling, and miscellaneous tasks like IncomingRPC or AddValidator.
    // Example of the PubSub host state schema
    var States = am.Schema{
        PeersPending: {},
        GetPeers:     {Multi: true},
        PublishMessage: {Multi: true},
        AddTopic:        {Multi: true},
        // ... other states
    }
  10. Understand the structure of an am-vis inspect-dump output

    main

    When running am-vis inspect-dump, the output is organized into machine components. Each component contains:

    States

    An enumeration of all possible states the component can occupy. Common states in the wasm_workflow include:

    • Lifecycle states: Start, Working, Completed, Disposed.
    • Error states: Exception, Failed, ErrNetwork, ErrOnClient.
    • Connectivity states: ClientConnected, Handshaking, Ready, RpcReady.

    Pipes

    Describes the transition rules. Each entry follows the pattern:

    • [add] SourceState -> TargetState: Adds a transition from SourceState to TargetState.
    • [remove] SourceState -> TargetState: Removes an existing transition.

    Example pipe entry from an orchestrator: [add] Browser3Completed -> Browser3Completed

  11. How asyncmachine works (State-Oriented Programming)

    main

    Unlike traditional Finite State Machines (FSMs) where code is bound to transitions (edges), asyncmachine binds code to states (nodes).

    When a state (e.g., Foo) activates, the following lifecycle occurs:

    1. FooEnter(e) is checked/executed.
    2. FooState(e) is executed. This method binds context, collects data, and can fork background processes bound to that specific state instance.

    When a state deactivates:

    1. FooExit(e) is checked/executed.
    2. FooEnd(e) is executed.

    Key Characteristics:

    • Concurrency: Transitions are not strictly defined; multiple states can be active simultaneously.
    • Composition: Code executed during a transition is a dynamic composition of deactivating and activating states.
    • Purpose: Designed to solve complexity in time by creating autonomous workflows with organic control flow.
  12. Configure state relations (Add, Remove, Require, After)

    main

    Relations define the rules of the flow and guarantee consistency among active states. Each state in the am.Schema can have four types of relations:

    • Add: Automatically attempts to activate the listed states whenever the owner state is activated. Activation is optional; if a listed state cannot be accepted, the transition still completes as Executed.
    • Remove: Prevents the activation of, or deactivates, the listed states. If a Remove relation conflicts with other called states or currently active states, the transition will be Canceled.
    • Require: Specifies states that must be active for the owner state to be activated. If requirements are not met, the transition is Canceled.
    • After: Controls the execution order of transition handlers. Handlers from the defined state will execute after handlers from the listed states.
    // Example Schema with relations
    mach := am.New(ctx, am.Schema{
      "Foo": {
        Add:    am.S{"Bar"},
        Remove: am.S{"Baz"},
      },
      "Bar": {
        Require: am.S{"Foo"},
      },
      "Baz": {
        After: am.S{"Foo"},
      },
    }, nil)