/docs/release-notes.md
v0.18.9 (2026-05-11)
feat: release v0.18.9
PR: #424 (@pancsta)
- refac: add state refs
- fix(repl): fix completion arg duplication
- fix(machine): fix gc duplicate false-positive
- fix(machine): fix ctxs closed after final handlers
- feat(machine): add
Machine.WhenNextActive - feat(helpers): add
EvAdd1Async,Add1Async - feat(helpers): add
*Remove*Sync - feat(helpers): migrate from errgroup to pond
- feat(helpers): add
BlockChan - feat(helpers): add
LogToTestLog - feat(machine): add
Machine.GoAfter - feat(machine): add
StatesByTag
The remainder of v0.18.9 is in this PR.
feat(am-dbg): release am-dbg v0.18.9
PR: #423 (@pancsta)
- refac: add state refs
- fix: fix steps and mermaid diagrams
- fix: fix missing log entries
- fix: fix ctrl+c exit
- fix: fix touch counter
- fix: fix listening on 0.0.0.0
- fix: fix machine list rendering
- fix: fix
--output-log - feat: add network graph time in the status bar
- feat: add next/prev machine toolbar buttons
- feat: apply auto-canceled tx filter to queued txs
- feat: add explicit and foldable tree links
- feat: add
[ ]out-logtoolbar button - feat: add
[ ]rpcfilter and CLI
feat(am-vis): improve transition steps diagrams, mach viewer
PR: #422 (@pancsta)
Changes: maximize shortcut, sequence repeated headers, richer info with state trace, optimized rendering, unified colors.
Transition sequence diagrams are out of experimental and the main way to see steps in am-dbg (steps timeline becomes disabled by default).
feat(history): add BadgerDB backend
PR: #421 (@pancsta)
Another K/V backend for pkg/history, useful for WASM, as bbolt doesn’t compile at all. Still needs manual binding to IndexedDB or File System API for persistence.
feat(docs): add editor templates and AI skills
PR: #420 (@pancsta)
“Skills” are generated from docs, divided by token usage and purpose. Live templates are often faster for everyday coding tho. See /docs/editors/README.md.
feat(integrations): add mcp-go server creator
PR: #419 (@pancsta)
Every state machine with typed args can have an MCP server. See am-dbg for how to extend it with custom getters (as asyncmachine does not return data).
feat(am-dbg): add –output-call-log for statically typed handler log
PR: #418 (@pancsta)
An explicit list of code executed by the machine with foldable negotiation. Navigable inside editors and IDEs, references for handler names and active states, ctrl+f, LSP/PSI queries. Still very experimental, manual bootstrap required (later via semlogger).
init.go
package main
import (
am "github.com/pancsta/asyncmachine-go/pkg/machine"
arpc "github.com/pancsta/asyncmachine-go/pkg/rpc"
"github.com/pancsta/asyncmachine-go/pkg/rpc/states"
)
var (
e *am.Event
active am.S
// TODO import schema
ss states.ClientStatesDef
// TODO import handlers
h0 *arpc.Client
// h1 *MachHandlers2
)
0.go
// Code generated by am-dbg, not for execution. Edit init.go for type defs.
package main
// Omitted: Any*(), Heartbeat*(), Healthcheck*()
func callLog0() {
// t2
{
h0.ConnectingEnter(e)
}
h0.StartState(e)
h0.ConnectingState(e)
// t5
h0.ConnectedState(e)
h0.HandshakingState(e)
// t7
{
h0.HandshakeDoneEnter(e)
}
h0.HandshakeDoneState(e)
// t8
h1.ReadyState(e)
}
feat(am-dbg): standardize machine URLs, add selection and query strings
PR: #417 (@pancsta)
Examples:
mach://cook/5b0e0574309ab28f36a2cd91249545d2-5/26?group=AgentBase&state=BaseDBSaving&t=76mach://cook/?t=76
Better support for mach time (?t), queue ticks (?qt), human time (?ht). Support for steps has been removed, docs are coming.
feat(graph): add XML graph format
PR: #416 (@pancsta)
- fix(graph): fix parsing pipes
The network graph can now be parsed as XML, available in am-dbg --output-graph, am-vis inspect-dump, and MCP/NetworkGraph. This is useful for a general overview in a single req.
<machine id="tool-searxng-cook" time="6">
<state tick="1" active="1">
Disposed
<remove>Disposing</remove>
<remove>Start</remove>
</state>
<state tick="2">
Disposing
<remove>Start</remove>
</state>
<state tick="1" active="1">
DockerAvailable
<require>Start</require>
<remove>DockerChecking</remove>
</state>
<state tick="1" active="1">
DockerChecking
<require>Start</require>
<remove>DockerAvailable</remove>
</state>
<state tick="0" auto="1">
DockerStarting
<require>DockerAvailable</require>
</state>
<state tick="0" multi="1">
ErrHandlerTimeout
<require>Exception</require>
<add>Exception</add>
</state>
<state tick="0" multi="1">
ErrNetwork
<require>Exception</require>
<add>Exception</add>
</state>
<state tick="0">
ErrOnClient
<require>Exception</require>
</state>
<state tick="1" active="1" multi="1">Exception</state>
<state tick="0" multi="1">Healthcheck</state>
<state tick="0">Heartbeat</state>
<state tick="0" auto="1">
Idle
<require>Ready</require>
<remove>Working</remove>
</state>
<state tick="0">
Ready
<require>Start</require>
<remove>DockerStarting</remove>
</state>
<state tick="0" multi="1">RegisterDisposal</state>
<state tick="0">Start</state>
<state tick="0">
Working
<require>Ready</require>
<remove>Idle</remove>
</state>
<pipe to="cook" add="1" as="Ready">Ready</pipe>
<pipe to="cook" add="0" as="Ready">Ready</pipe>
<tag>tool</tag>
</machine>
feat(am-dbg): add full transition history navigation
PR: #415 (@pancsta)
New buttons, full history traversal and by-machine jumps.
feat(am-dbg): add MCP server and REPL
PR: #414 (@pancsta)
These changes allow for automating repetitive tasks within the debugger.
The MCP server, unlike the REPL, returns data and has predefined mutations via /pkg/machine.CallSignature.
Accessible via --ui-mcp and --dbg-repl.
feat(machine): include relations in TimeAfter for negotiation
PR: #413 (@pancsta)
Usually used via TimeIndexAfter, previously only (shallow) called states were processed. Very useful for proper negotiation.
func (d *Debugger) NarrowLayoutExit(e *am.Event) bool {
// always allow to exit
after := e.Transition().TimeIndexAfter()
if after.Not1(ss.Start) {
return true
}
return after.Not1(ss.UserNarrowLayout)
}
v0.18.8 (2026-04-23)
The highlight of this release is a full state-trace.
feat: release v0.18.8
PR: #411 (@pancsta)
The remainder of v0.18.8 is in this PR.
feat(am-dbg): release am-dbg v0.18.8
PR: #410 (@pancsta)
- fix(am-dbg): remove pipes from disconnected clients
- feat(am-dbg): add narrow layout toolbar button
- feat(am-dbg): use filtered transitions for the timeline info bars, steps
- feat(am-dbg): add log wrapping
- fix(am-dbg): fill visible log with filter-matching transitions
- refac(am-dbg): migrate to schema-v2
- refac(am-dbg): trace mutations where possible
- refac(am-dbg): migrate to typed args
A lot of quality-related refacs and a pretty important navigation feature for the timeline. Also, duplicating pipes are now fixed.
feat(am-dbg): add diagram filtering (group, called, changed, touched, relations, selected)
PR: #409 (@pancsta)
The new diagram filter options are --output-diag-tx and --output-diag-group (also on the toolbar). The former one highlights the diagram based on the current transition, while the latter one skips or completely re-renders the graph for states from the selected group. State selection will also highlight the diagram (distinctively). The diagram viewer now also has better UX.
feat(am-dbg): add light theme via --view-theme light
PR: #408 (@pancsta)
The light theme can be useful for debugging outdoors. Some fade-out colors in the dark theme have been fixed.
feat(am-dbg): add explicit focus management
PR: #407 (@pancsta)
- feat(am-dbg): improve toolbar keyboard nav
While still not perfect, the focus management is now handled manually which fixes many issues, including a deadlock caused by cview. The navigation in toolbars now wraps and remembers the position index.
feat(am-dbg): add stack traces to queued mutations
PR: #406 (@pancsta)
Every queued mutation (toolbar button) now has the stack trace attached and accessible from the log reader, with the machine-related entries filtered out.
feat(am-dbg): add state-trace to the reader
PR: #405 (@pancsta)
The log reader now lists the full “state trace”, making it look skin to a stack trace. Machine URLs can be used to navigate / copy.
feat(machine): add Next*Active* tick helpers
PR: #404 (@pancsta)
WhenQueue combined with tick couting effectively allows using a sync state as an async one, and the new tick helpers make it easier. Example:
// click more and wait for unclick
err = amhelp.WaitForErrAll(ctx, time.Second, mach,
mach.WhenQueue(mach.EvAdd1(e, ss.ClickingMore, nil)),
mach.WhenTicks(ss.ClickingMore, am.NextInactiveIn(mach.Tick(ss.ClickingMore)), ctx))
feat(machine): add CtxToEv and EvToCtx
PR: #403 (@pancsta)
A small yet helpful sugar for passing Event via context.
fix(machine): remove counter-mut duplicate detection
PR: #402 (@pancsta)
This magic caused random bugs.
test: add tests for examples
PR: #401 (@pancsta)
- fix(machine): pool init
Basic integration tests to make sure examples arent breaking.
v0.18.6 (2026-04-07)
The highlight of this release is the updated cookbook.
feat: release v0.18.6
PR: #400 (@pancsta)
- fix(rpc): fix ID prefixing for proper pipe logs, drop rand IDs
- fix(graph): ignore duplicated pipes
The remainder of v0.18.6 is in this PR.
feat(am-vis): add inspect-dump cmd producing a markdown version of th…
PR: #399 (@pancsta)
- fix: CLI overloads
We can now use am-vis to inspect the whole network as text.
am-vis inspect-dump am-dbg-dump.gob.br
-----
##### rnm-srv-browser2
Parent: rc-srv-browser2
###### States
- Browser3Completed
- Browser3Disposed
- Browser3Disposing
- Browser3ErrHandlerTimeout
- Browser3ErrNetwork
- Browser3ErrOnClient
- Browser3Exception
- Browser3Failed
- Browser3Healthcheck
- Browser3Heartbeat
- Browser3Ready
- Browser3RegisterDisposal
- Browser3Retry
- Browser3Retrying
- Browser3RpcReady
- Browser3Start
- Browser3Work
- Browser3Working
- Browser4Completed
- Browser4Disposed
- Browser4Disposing
- Browser4ErrHandlerTimeout
- Browser4ErrNetwork
- Browser4ErrOnClient
- Browser4Exception
- Browser4Failed
- Browser4Healthcheck
- Browser4Heartbeat
- Browser4Ready
- Browser4RegisterDisposal
- Browser4Retry
- Browser4Retrying
- Browser4RpcReady
- Browser4Start
- Browser4Work
- Browser4Working
- Completed
- Disposed
- Disposing
- ErrHandlerTimeout
- ErrNetwork
- ErrOnClient
- Exception
- Failed
- Healthcheck
- Heartbeat
- Ready
- RegisterDisposal
- Retrying
- RpcReady
- ServerPayload
- Start
- Working
###### Pipes
####### orchestrator
- [add] Browser3Completed -> Browser3Completed
- [add] Browser3Exception -> ErrBrowser3
- [add] Browser3Failed -> Browser3Failed
- [add] Browser3Ready -> Browser3Ready
- [add] Browser3Working -> Browser3Working
- [add] Browser4Completed -> Browser4Completed
- [add] Browser4Exception -> ErrBrowser4
- [add] Browser4Failed -> Browser4Failed
- [add] Browser4Ready -> Browser4Ready
- [add] Browser4Working -> Browser4Working
- [add] Completed -> Browser2Completed
- [add] Exception -> ErrBrowser2
- [add] Failed -> Browser2Failed
- [add] Ready -> Browser2Ready
- [add] Working -> Browser2Working
- [remove] Browser3Completed -> Browser3Completed
- [remove] Browser3Exception -> ErrBrowser3
- [remove] Browser3Failed -> Browser3Failed
- [remove] Browser3Ready -> Browser3Ready
- [remove] Browser3Working -> Browser3Working
- [remove] Browser4Completed -> Browser4Completed
- [remove] Browser4Exception -> ErrBrowser4
- [remove] Browser4Failed -> Browser4Failed
- [remove] Browser4Ready -> Browser4Ready
- [remove] Browser4Working -> Browser4Working
- [remove] Completed -> Browser2Completed
- [remove] Exception -> ErrBrowser2
- [remove] Failed -> Browser2Failed
- [remove] Ready -> Browser2Ready
- [remove] Working -> Browser2Working
-----
##### rs-browser1
Parent: browser1
###### States
- ClientConnected
- ErrDelivery
- ErrHandlerTimeout
- ErrNetwork
- ErrNetworkTimeout
- ErrOnClient
- ErrRpc
- Exception
- HandshakeDone
- Handshaking
- Healthcheck
- Heartbeat
- MetricSync
- Ready
- RpcAccepting
- RpcReady
- RpcStarting
- Start
- WebSocketTunnel
###### Pipes
####### browser1
- [add] Ready -> RpcReady
- [remove] Ready -> RpcReady
-----
feat(machine): add SemLogger.Pipes() returning currently bound pipes
PR: #398 (@pancsta)
- feat(telemetry): push out pipes on first schema
Binding pipes before am-dbg is not a problem any more.
feat(machine): add PoolFork, PoolSetLimit, PoolSetLimitGlobal for non-blocking pools
PR: #397 (@pancsta)
The previous amhelp.Pool used a blocking errgroup. PubSub adjusted. It will handle a heavy load better.
v0.18.4 (2026-04-01)
The highlight of this release is support for WebWorkers.
feat: release v0.18.4
PR: #392 (@pancsta)
The remainder of v0.18.4 is in this PR.
feat(machine): add Tracer.TransitionFinals for more detailed tracing
PR: #390 (@pancsta)
Tracer now splits negotiation and final handlers, for better timing results.
feat(states): add ampipe.Sync for re-connects
PR: #389 (@pancsta)
When piping states, the target should be synchronized first to catch up if the source have been previously used.
err = ampipe.Sync(client.NetMach, o.mach, pipeSrc, pipeDest)
err = ampipe.BindMany(client.NetMach, o.mach, pipeSrc, pipeDest)
docs(examples): add WebWorker workflow example
PR: #388 (@pancsta)
This is the highlight of v0.18.4 - tightly synchronized threads, enabling stateful multi-threading inside any web browser.
feat(telemetry): auto exchange the root trace ID via env
PR: #387 (@pancsta)
- fix(telemetry): fix Otel states filtering
- feat(telemetry): keep a singleton Otel provider
Tracing multiple state machines under the same trace is now automatic within the same process. Exchanging IDs via env makes it work for N number of sources (like browsers).
feat(telemetry): add WASM support for Otel
PR: #386 (@pancsta)
Traces are even more useful when there’s no step-through debugger. Some build flags and a fork, and just works.
See /docs/wasm.md.
refac(am-dbg): migrate to go-arg, add missing CLI filters
PR: #385 (@pancsta)
Long overdue CLI refac has landed. Adding new args is now simple and all the filters are finally covered:
--filter-auto Filter automatic transitions
--filter-auto-canceled
Filter automatic canceled transitions
--filter-canceled Filter canceled transitions
--filter-checks Filter check (read-only) transitions
--filter-disconn Filter disconnected clients
--filter-empty Filter empty transitions
--filter-group Filter transitions by a selected group [default: true]
--filter-health Filter health-check transitions
--filter-log-level FILTER-LOG-LEVEL
Filter transitions up to this log level, 0-5 (silent-everything) [default: 2]
--filter-queued Filter queued transitions
fix(rpc): remove SendPayload state
PR: #384 (@pancsta)
- use rpc.Server.SendPayload() directly
Sending a payload from server to client via a state has been removed. This simplifies the ambiguous flow and reduces reflection.
fix(machine): fix disposal with detached ctx
PR: #383 (@pancsta)
Yet another disposal round, this time works for fleets of machines across the network.
v0.18.3 (2026-03-17)
The highlight of this release is selective Otel tracing.
feat: release v0.18.3
PR: #381 (@pancsta)
The remainder of v0.18.3 is in this PR.
feat(telemetry): add selective Otel traces
PR: #380 (@pancsta)
New env vars for limiting states which are traced. Useful for profiling large state machines.
AM_OTEL_TRACE_ALLOW_STATESAM_OTEL_TRACE_SKIP_STATESAM_OTEL_TRACE_ALLOW_STATES_REAM_OTEL_TRACE_SKIP_STATES_RE.
os.Setenv(amtele.EnvService, "dbg")
os.Setenv(amtele.EnvOtelTrace, "1")
os.Setenv(amtele.EnvOtelTraceTxs, "1")
os.Setenv(amtele.EnvOtelTraceArgs, "1")
os.Setenv(amtele.EnvOtelTraceAllowStates,
"ClientSelected,SelectingClient,RemoveClient,BuildingLog,LogBuilt")
os.Setenv(amtele.EnvOtelTraceAllowStatesRe, "^Diagrams")
feat(relay): add WebSocket-to-TCP dial
PR: #379 (@pancsta)
WASM aRPC clients can now dial TCP over /tools/relay and access pkg/rpc.(*Mux), meaning a single server can accept multiple browser on the same port. #377 still to go.
// client
foo, err := arpc.NewClient(ctx, example.EnvRelayHttpAddr, fooHandlerMach.Id(), states.FooSchema, &arpc.ClientOpts{
Parent: fooHandlerMach,
WebSocket: arpc.WsDialPath(fooHandlerMach.Id(), example.EnvFooTcpAddr),
})
// server as lib (optional, for in-process matching)
relay, err := amrelay.New(ctx, amrelayt.Args{
Name: "wasm-demo",
Debug: true,
Parent: fooMach,
Wasm: &amrelayt.ArgsWasm{
ListenAddr: example.EnvRelayHttpAddr,
StaticDir: "./client",
ReplAddrDir: "tmp",
TunnelMatchers: []amrelayt.TunnelMatcher{{
Id: regexp.MustCompile("^browser-bar-"),
NewClient: newClient,
}},
DialMatchers: []amrelayt.DialMatcher{{
Id: regexp.MustCompile("^browser-foo-"),
NewServer: func(ctx context.Context, id string, conn net.Conn) (*arpc.Server, error) {
return mux.NewServer(nil, id, conn)
},
}},
},
})
refac(rpc): improve Mux APIs
PR: #378 (@pancsta)
Changes for relay injections and a unified signature of NewMux and NewServer.
v0.18.2 (2026-03-09)
fix(states): fix piping log directions, RPC deadlocks
PR: #375 (@pancsta)
No PR description provided.
fix(rpc): dont dispose state source on server dispose
PR: #374 (@pancsta)
No PR description provided.
fix(history): fix SQL diffs, mach IDs, tracking matches
PR: #373 (@pancsta)
No PR description provided.
fix(am-dbg): preserve log reader selection
PR: #372 (@pancsta)
No PR description provided.
v0.18.1 (2026-02-24)
feat: release v0.18.1
PR: #371 (@pancsta)
The remainder of v0.18.1 is in this PR.
fix(rpc): fix WASM reconns hang the browser
PR: #370 (@pancsta)
No PR description provided.
fix: fix disposal in mach, rpc, history, debugger
PR: #369 (@pancsta)
No PR description provided.
fix(am-vis): fix parent and RPC graph linking
PR: #368 (@pancsta)
No PR description provided.
v0.18.0 (2026-02-21)
The highlight of this release is WASM support. See you at 2026.wasm.io.
feat: release v0.18.0
PR: #365 (@pancsta)
The remainder of the v0.18 is in this PR
feat(am-dbg): release v0.18
PR: #364 (@pancsta)
- feat: add
--ui-sshfor SSH access and embedding - feat: add
--ui-webfor web access to files in--dir - feat: add support for WebSocket clients
listen-onis now--listen-addr- fix: fixed single-frame export
- feat: add
--output-log
A lot of dev accessibility improvments in v0.18:
- SSH allows for embedding the debugger in an app, and connecting the UI on demand (1 simultaneous client only)
- Web UI makes browsing
--dirassets much easier, also links to pages (for now only/diagrams/mach) - some params changed, some were added
chore(am-dbg-ssh): am-dbg-ssh is now EOL
PR: #363 (@pancsta)
Because SSH is now built into am-dbg, there’s no need to have a separate tool for basic SSH demos.
feat(am-vis): render all machs when no URL given
PR: #362 (@pancsta)
It’s now easier to render a whole am-dbg snapshot without specifing a mach:// URL. This works around a bug with graph linking. Example:
am-vis --bird --render-detailed-pipes --render-pipes render-dump tmp/am-dbg-dump.gob.br
feat(telemetry): dbg protocol moved to /pkg/telemetry/dbg
PR: #360 (@pancsta)
Turns out the WASM linker really can’t handle dead code removal well, and the dbg protocol had to be extracted to avoid linking things like Otel and gRPC. The dbg protocol is net/rpc based.
docs(examples): add WASM example with REPL
PR: #359 (@pancsta)
A documented example of using the new WASM features. See /examples/wasm/README.md.
feat(machine): add Machine.Fork and Machine.Go sugar
PR: #358 (@pancsta)
Fork helps in handler bodies:
// ----- BEFORE
if !e.IsValid() {
return
}
go func() {
if ctx.Err() != nil {
return // expired
}
// code
}()
// ----- AFTER
mach.Fork(ctx, e, func(){
// code
})
Go is for nested forks:
// ----- BEFORE
go func() {
if ctx.Err() != nil {
return // expired
}
// code
}()
// ----- AFTER
mach.Go(ctx, func(){
// code
})
feat(am-vis): generate tx sequence diagrams (D2, mermaid, ASCII, SVG)
PR: #357 (@pancsta)
The visualizer API now pops out transitions with handlers in either D2 or Mermaid (basic support) format. Mermaid also has an ASCII mode thanks to AlexanderGrooff/mermaid-ascii. Integrated into am-dbg.
┌────────────────┐ ┌───────┐ ┌───────┐
│ ReplaceStories │ │ Start │ │ Ready │
└────────┬───────┘ └───┬───┘ └───┬───┘
│ │ │
│ called │ │
├──┐ │ │
│ │ │ │
│◄─┘ │ │
│ │ │
│ activate │ │
├──┐ │ │
│ │ │ │
│◄─┘ │ │
│ │ │
│ │ require │
│ │◄────────────┤
│ │ │
│ │ require │
│ │◄────────────┤
│ │ │
│ ReplaceStoriesState │
├──┐ │ │
│ │ │ │
│◄─┘ │ │
│ │ │
feat(am-dbg): add --output-tx to output current tx in MD and diagrams
PR: #356 (@pancsta)
Detailed transition info is created in --dir in Markdown format, as well as various visualizations. This is useful for very large transitions (eg auto transitions).
mach://cook/5064f50f92a8b17cc0a0043eac213284
2026-02-21T12:01:27.251627709Z
[add] CharacterReady
- called **CharacterReady**
- activate **CharacterReady**
- **DBReady** require **Start**
- **SSHReady** require **UIMode**
- **WebRPCReady** require **UIMode**
- **CheckStories** require **Start**
- **WebHTTPReady** require **UIMode**
- **BrowserRPCReady** require **WebHTTPReady**
- **HistoryDBStarting** require **Start**
- **Msg** require **Start**
- **RestoreCharacter** require **DBReady**
- **WebConnected** require **WebHTTPReady**
- **WebSSHReady** require **SSHReady**
- **BaseDBReady** require **Start**
- **CharacterReady** remove **RestoreCharacter**
- deactivate **RestoreCharacter**
- **RestoreCharacter** remove **CharacterReady**
- **DBReady** require **Start**
- **SSHReady** require **UIMode**
- **WebRPCReady** require **UIMode**
- **CheckStories** require **Start**
- **WebHTTPReady** require **UIMode**
- **BrowserRPCReady** require **WebHTTPReady**
- **HistoryDBStarting** require **Start**
- **Msg** require **Start**
- **WebConnected** require **WebHTTPReady**
- **WebSSHReady** require **SSHReady**
- **BaseDBReady** require **Start**
- handler **CharacterReady**Enter
- handler **CharacterReady**State
- handler **Any**State
feat(repl): support connecting to WebSocket servers via addr:port/path
PR: #355 (@pancsta)
arpc will understand WebSocket addresses followed by a path (eg host:port/) and the type of connected machines can be mixed (TCP and WS).
feat(am-relay): add listening on TCP via WebSockets
PR: #354 (@pancsta)
- feat: add in-process WebSockets relays
- feat: add optional REPL
- feat: add dedicated machine WS TCP Tun
am-relay is picking up can now be used also as a library. To create a WebAssembly relay, one can simply:
am-relay wasm --static-dir .
feat(rpc): add support for WebSocket clients and servers
PR: #353 (@pancsta)
The highlight of v0.18.0 - WebAssembly support. It’s now possible to control async state machines in-and-from the browser. For that, the aRPC server is now also a WebSocket-tunnel client with auto-reconnect logic. Muxing still come…
More info in the dedicated /examples/wasm/README.md.
feat(machine): add more Transition.TimeIndex* methods
PR: #352 (@pancsta)
TimeIndex allows for similar state checking calls like machine.Api (eg Is1) and now provides the following on Transition:
TimeIndexBefore()TimeIndexCalled()TimeIndexDiff()TimeIndexTouched()
Especially useful to state-navigate on transition states in handlers.
feat(machine): add graceful shutdown
PR: #351 (@pancsta)
Machine has now a separate context which closes later than the parent context, to allow disposal handlers to listed on Done().
fix(rpc): fix args value parsing
PR: #350 (@pancsta)
No PR description provided.
fix(repl): fix cobra flag value slices not reset
PR: #349 (@pancsta)
No PR description provided.
fix(machine): fix self handlers only triggered for called states
PR: #348 (@pancsta)
Till now FooFoo was triggered only for called states. Now it’s for all active before and after, with the previous behavior being achievable via checking Transition.CalledStates().
v0.17.2 (2026-02-20)
fix(machine): dont call handlers of partially rejected auto states
PR: #347 (@pancsta)
Rejecting in FooEnter for Foo being Auto would still call FooState. Fortunately using an expiration ctx mitigated most of side effects, but…
v0.17.1 (2025-01-18)
The highlight of this release is a better REPL.
chore: add dedicated go.mod for /scripts
PR: #344 (@pancsta)
Remove unrelated dependencies, eg chrome.
feat(repl): add typed args support with completion
PR: #343 (@pancsta)
- feat(repl): only suggest non-empty transition states
- feat(repl): use pseudo-unique IDs
- fix(repl): fix duplicated reconnects
- fix(rpc): fix REPL schemas
- fix(rpc): panic less
v0.17.1 is all about the REPL, which should be faster to work with, thanks to smarter completion:
- only inactive or
Multistates can be added --argwill complete names from typed args struct
Fixes #338.
feat(am-dbg): add disconnected filter
PR: #342 (@pancsta)
- fix(am-dbg): fix export panic
The new disconn filter will show/hide disconnected clients on the client list.
feat(machine): add OnChange handler
PR: #341 (@pancsta)
OnChange is the most basic state change handler and almost always should be replaced with BindHandlers. The only exception is when we need a handler-less machine (eg for clean aRPC), but still need to react to mutations (eg from REPL).
fix(machine): fix IsQueued for PositionLast
PR: #340 (@pancsta)
No PR description provided.
fix(machine): fix SetSchema err handling
PR: #339 (@pancsta)
No PR description provided.
v0.17.0 (2025-12-22)
The highlight of this release is partial distribution with handlers via aRPC.
feat(node): add defaults to constructors, align with pkg/rpc
PR: #334 (@pancsta)
Constructors have been aligned with new pkg/rpc APIs and are now shorter.
feat(rpc): add partial, shallow, and mutation-clock syncing, also handler piping
PR: #333 (@pancsta)
- feat(rpc): add queue flushing on lowered queue ticks
- feat(rpc): make tracers synchronous
- feat(rpc): add piping support for NetworkMachines
- fix(rpc): fix error methods and EvRemove
- refac(rpc): redo sync logic, clarify naming
- hash-based schema sync
- support expanding schemas
- state names not needed any more
- feat(rpc): request full sync on bad checksum
/pkg/rpc has undegone a heavy refac, add gotten a handful of new features.
- Partial sync, and schema-less sync allow for granular distribution and minimizing traffic in large scale deployments.
- Client-side handlers via piping to a local machine - this approach allows for negotiation, while still minimizing blocking, as the negotiation happens in a non-networked machine. As always, if in doubt, make it a state.
Client-side mutation filtering is still TODO, although all the required info is in place now (the resolver needs integration work though).
Last but not least - naming is now more descriptive with network-source exporting state to network-machines. Nothing is called a “worker” anymore.
feat(helpers): add SchemaStates, SchemaImplements, Dispose, DisposeBind
PR: #332 (@pancsta)
Just a couple of convenience helpers.
feat(helpers): add AM_LOG_PRINT
PR: #331 (@pancsta)
AM_LOG_PRINT will start printing the output to stdout, for all machines using amhelp.MachDebugEnv(). This replaces the previous behavior of AM_DEBUG=2, so printing doesn’t depend on bumped timeouts anymore.
docs(examples): add CLI, CLI Daemon, and TUI examples
PR: #330 (@pancsta)
Small but practical examples / templates to bootstrap TTY tools quickly:
examples/cli_daemonis the most interesting one, as it usesaRPCto mutate a singleton state-machine (the daemon)examples/cliis a minimal CLI app mapping params to statesexamples/tuiuses a race-free fork of cview (tview), used byam-dbg
test: add scripts/test_loop with artifacts
PR: #329 (@pancsta)
Per-package race-tests executing in a loop. In case of a failure, the following artifacts are generated:
$ ll tmp/test_loop/2025-12-05T15-33-21/
.rw-r--r--@ 69k tob 5 Dec 15:33 am-dbg-dump-2025-12-05T15-33-23.gob.br
.rw-r--r--@ 10k tob 5 Dec 15:33 am-dbg-dump-2025-12-05T15-33-24.gob.br
.rw-r--r--@ 621 tob 5 Dec 15:33 stdout.log
.rw-r--r--@ 604k tob 5 Dec 15:33 trace.out
fix(telemetry): fix race for queued mutations
PR: #328 (@pancsta)
No PR description provided.
fix(machine): fix subscription clock on schema changes
PR: #327 (@pancsta)
No PR description provided.
v0.16.1 (2025-11-23)
The highlights of this release are am-vis and am-relay. Last but not least - the website has launched at https://asyncmachine.dev, enjoy.
feat: release v0.16.1
PR: #317 (@pancsta)
- feat: add website generator
The remainder of v0.16.1 is here.
feat(am-dbg): release v0.16.1
PR: #316 (@pancsta)
- fix: fix group re-selection
- fix: fix starting on a filtered-out tx
- feat: add exporting of single-frame dumps
- feat: extract RPC server
Bulk PR for am-dbg v0.16.1.
feat(am-relay): add dbg protocol rotation server
PR: #315 (@pancsta)
am-relay converts formats and relays connections. It’s an early version that for now can only rotate dbg telemetry
into chunked file dumps.
.rw-r--r--@ 713k foo 17 Nov 12:19 am-dbg-dump-2025-11-17_12-19-35.gob.br
.rw-r--r--@ 737k foo 17 Nov 12:20 am-dbg-dump-2025-11-17_12-20-02.gob.br
.rw-r--r--@ 749k foo 17 Nov 12:20 am-dbg-dump-2025-11-17_12-20-28.gob.br
fix(machine): fix Groups deadlock
PR: #314 (@pancsta)
No PR description provided.
fix(machine): fix double disposal
PR: #313 (@pancsta)
No PR description provided.
feat(am-vis): add diagram generator
PR: #216 (@pancsta)
am-vis is a dbg protocol based distributed diagram renderer. It creates a graph of interconnected machines, based on fragmented data they provide via:
- state schema
- states
- relations
- tags
- logs L2
- pipes
- RPC connections
- transitions
- active states
It does not persist transitions and only knows the latest active states. The rendering uses ELK and is done by:
- D2 (shipped, mid-sized diagrams)
- Mermaid (basic support, not shipped, small-sized diagrams)
This prototype reads am-dbg-dump.gob.br export file and renderes some predefined configurations. machIds need to be adjusted to the dumped machine IDs.
Config
Graphs can be filtered using the rendering config:
type Visualizer struct {
// Render only these machines as starting points.
RenderMachs []string
// Render only machines matching the regular expressions as starting points.
RenderMachsRe []*regexp.Regexp
// Skip rendering of these machines.
RenderSkipMachs []string
// Distance to render from starting machines.
RenderDistance int
// How deep to render from starting machines. Same as RenderDistance, but only
// for submachines.
RenderDepth int
// Render states bubbles.
RenderStates bool
// With RenderStates, false will hide Start, and without RenderStates true
// will render Start.
RenderStart bool
// With RenderStates, false will hide Exception, and without RenderStates true
// will render Exception.
RenderException bool
// With RenderStates, false will hide Ready, and without RenderStates true
// will render Ready.
RenderReady bool
// Render states which have pipes being rendered, even if the state should
// not be rendered.
RenderPipeStates bool
// Render group of pipes as mach->mach
RenderPipes bool
// Render pipes to non-rendered machines / states.
RenderHalfPipes bool
// Render detailed pipes as state -> state
RenderDetailedPipes bool
// Render relation between states.
RenderRelations bool
// Style currently active states.
RenderActive bool
// Render the parent relation. Ignored when RenderNestSubmachines.
RenderParentRel bool
// Render submachines nested inside their parents. See also RenderDepth.
RenderNestSubmachines bool
// Render a tags box for machines having some.
RenderTags bool
// Render RPC connections
RenderConns bool
// Render RPC connections to non-rendered machines.
RenderHalfConns bool
// Render a parent relation to and from non-rendered machines.
RenderHalfHierarchy bool
// Render inherited states.
RenderInherited bool
// Mark inherited states.
RenderMarkInherited bool
}
TODO
- refactor to support
am-vis import-render mydiag --import-file dump.gob.br --output-svg --render-opt1=true ...am-vis live-render mydiag --output-svg --render-opt1=true ...
- extract rendering
- parallel rendering within a single am-vis instance
- config file, watch for changes
- extract
pkg/graph - cleanups
Later / out of scope
- add a TUI version of the rendering config
- use D2 API instead of gluing strings
- support GC, store machine time for enter / exit
- add G6 renderer for large graphs
Legend
- yellow border = machine requested by ID
- white border = machine close or deep enough from a requested one
- no border = not fully rendered machine for grouped inbound and outbound edges
- yellow state = active
- double border state = own / non-inherited state
- blue state = Ready active
- green state = Start active
Previews
It’s recommended to view the SVGs using SVG Navigator.
%%{init: {'flowchart': {'defaultRenderer': 'elk'}} }%%
flowchart LR
classDef _active color:black,fill:yellow;
subgraph a[nc-TCWP-cli-140116<br>#node-client]
direction TB
b([WorkerRequested])
b --o c
d([WorkerDisconnected])
e([Exception])
f([SuperDisconnecting])
g([WorkerReady])
g --x b
h([ErrWorker])
h --o e
i([Ready])
i --o g
j([SuperConnecting])
c([SuperReady])
k([WorkerDisconnecting])
l([WorkerPayload])
m([Start])
n([WorkerConnecting])
o([ErrSupervisor])
o --o e
p([WorkerConnected])
q([ErrNetwork])
q --o e
r([Heartbeat])
s([ErrHandlerTimeout])
s --o e
t([Healthcheck])
u([SuperConnected])
v([SuperDisconnected])
class p,g,i,u,c,m _active;
end
v0.16.0 (2025-11-11)
The highlight of this release is a proper history layer.
feat: release v0.16
PR: #312 (@pancsta)
The remainder of v16.0 is in this PR.
feat(machine): add filtering to ActiveStates
PR: #311 (@pancsta)
The output of ActiveStates() can now be filtered, similarly to many other methods.
refac(machine): limit queue len to uint16
PR: #310 (@pancsta)
Lowering the limit to save space.
feat(machine): add new Time methods
PR: #309 (@pancsta)
Time.After,Time.Before,Time.EqualTime.NonZeroStates,Time.ToIndexTime.ActiveStates,Time.Filter
Time APIs (Time and TimeIndex) have been streamlined and are now more composable.
mach.Time(nil).Sum(S{"Foo", "Bar"})
mach.Time(nil).ToIndex(mach.Index(nil).ActiveStates(S{"Foo", "Bar"})
feat: add WhenQuery for time queries
PR: #308 (@pancsta)
Time queries assert the current machine time matches a simple function, useful for eg state proportions. The RPC compat is also implemented.
mach.WhenQuery(func(c am.Clock) bool {
return c["A"] / 2 > c["B"]
}, nil)
feat(machine): add machine ticks
PR: #307 (@pancsta)
Machine lifespans are now being counted and can be used for import/export. RPC implementation still to come.
feat(history): add columnar backend experiment
PR: #306 (@pancsta)
Part of #303.
There’s an experimental Columnar backend based on FrostDB and Parquet in /pkg/x/history/frostdb.
feat(history): add KV backend
PR: #305 (@pancsta)
Part of #303.
The Key-Value store backend uses etcd-io/bbolt with vmihailenco/msgpack and writes to a single file. For debugging there’s also JSON encoding, with 2x the size.
feat(history): add SQL backend
PR: #304 (@pancsta)
Part of #303.
The SQL backend uses GORM, and ships with a WASM-based SQLite (WAL enabled), although it can be used with any SQL database.
feat(history): add memory backend
PR: #303 (@pancsta)
The new history layer replaces the old prototype and provides a fairly complete data schema and APIs, implemented across several backends (in-process, SQL, KV, columnar). Details in /pkg/history.
type TimeRecord struct {
// TransitionId is an optional ID of the related [TransitionRecord].
TransitionId string
// MutType is a mutation type.
MutType am.MutationType
// MTimeSum is a machine time sum after this transition.
MTimeSum uint64
// MTimeSum is a machine time sum after this transition for tracked states
// only.
MTimeTrackedSum uint64
// MTimeDiff is a machine time difference for this transition.
MTimeDiff uint64
// MTimeDiff is a machine time difference for this transition for tracked
// states only.
MTimeTrackedDiff uint64
// MTimeRecordDiff is a machine time difference since the previous
// [TimeRecord].
MTimeRecordDiff uint64
// HTime is a human time in UTC.
HTime time.Time
// MTime is a machine time after this mutation.
MTimeTracked am.Time
// MachTick is the machine tick at the time of this transition.
MachTick uint32
}
v0.15.2 (2025-10-19)
fix(machine): fix empty args map
PR: #302 (@pancsta)
No PR description provided.
fix(rpc): fix queue ticks and tracers
PR: #301 (@pancsta)
No PR description provided.
feat(am-dbg): add --enable-clipboard=false
PR: #300 (@pancsta)
The clipboard integration can be disabled, so it doesn’t error the UI and log.
v0.15.1 (2025-10-02)
The highlight of this release is goroutines-less GC of subscriptions.
feat(rpc): inherit Subscriptions from pkg/machine
PR: #299 (@pancsta)
RPC worker now directly inherits Subscriptions from pkg/machine which reduces redundant code. More methods should follow this path, if possible.
feat(machine): optimize ctx-based GC
PR: #298 (@pancsta)
Closes https://github.com/pancsta/asyncmachine-go/issues/160.
- extract
Subscriptions - no goroutine per listener
- close channels on
Machine.Dispose()
The only places where the machine was creating waiting goroutines were ctx-bound subscriptions. These are now are GCed during the processing of the queue, which may cause some delays but won’t flood the VM with goroutines.
feat(rpc): support queue ticks and checksums
PR: #297 (@pancsta)
Closes https://github.com/pancsta/asyncmachine-go/issues/292.
aRPC is now synchronizing queue ticks, as well as simple checksums. This enables pkg/helpers.Add1Sync to work with RPC workers via worker.WhenQueue().
v0.15.0 (2025-09-23)
The highlights of this release are queue ticks and progressive log rendering.
- docs: release v0.15 #296
- feat: release v0.15 #295
- feat(am-dbg): release am-dbg v0.15 #294
- feat(helpers): add blocking Add1Sync #293
- feat(am-dbg): add queue section to log reader #291
- feat(am-dbg): add queued and canceled txs to log view #290
- feat(am-dbg): progressive log rendering #289
- feat(machine): simulate TimeAfter for called states #288
- feat(machine): add QueueTick, WhenQueue #287
- feat(machine): add strict breakpoints #286
- feat(machine): add CheckDone to Can methods #285
- fix(machine): fix duplicate detection for Can* #284

docs: release v0.15
PR: #296 (@pancsta)
No PR description provided.
feat: release v0.15
PR: #295 (@pancsta)
All the remaining changes for v0.15.
feat(am-dbg): release am-dbg v0.15
PR: #294 (@pancsta)
- fix: fix IsCheck transitions
- fix: fix touched states counter
- fix: fix: web diagrams on port +1
- fix: fix help dialog
- feat: add Arguments to log reader
- feat: add –select-group and –filter-group
All the remaining changes for am-dbg v0.15.
feat(helpers): add blocking Add1Sync
PR: #293 (@pancsta)
Add1Sync replaces Add1Block and is based on the new queue ticks, which makes in compatible with the queue, unlike Add1Block. RPC compat still to be added.
// test
res := amhelp.Add1Sync(ctx, mach, ss.UserBack, nil)
// assert
assert.NotEqual(t, res, am.Canceled)
feat(am-dbg): add queue section to log reader
PR: #291 (@pancsta)
- am-dbg: reconstruct the queue in log reader
The queue is now fully visible in the log reader, based on the transition history and queue ticks. Most of the lines are hyperlinked.
feat(am-dbg): add queued and canceled txs to log view
PR: #290q (@pancsta)
- add queued filter
- add log gutter
Queued and canceled transactions are now explicitly visible in the log view, with dedicated UI colors. The log entries are synthetic and are the first step to completely disable text log above LogExternal.
feat(am-dbg): progressive log rendering
PR: #289 (@pancsta)
The log is now rendered in small buffers, instead everything. This allows for viewing very large data sets. Edge cases still to be covered.
Fixes https://github.com/pancsta/asyncmachine-go/issues/98.
feat(machine): simulate TimeAfter for called states
PR: #288 (@pancsta)
- add
TimeIndexAfter()
Every Transition now has a simulated TimeAfter for called states, which makes negotiation much easier, especially with the new helper.
func (d *Debugger) NarrowLayoutExit(e *am.Event) bool {
// always allow to exit
if e.Transition().TimeIndexAfter().Not1(ss.Start) {
return true
}
return !d.Opts.ViewNarrow
}
feat(machine): add QueueTick, WhenQueue
PR: #287 (@pancsta)
The highlight of v0.15 - counted queue ticks for most of the transitions, which allow for waiting until the execution happens, independently of the negotiation. Waiting is efficient as queue ticks are uint64 and do not create a channel nor a goroutine.
Queue ticks “extend” the Result type and are returned by all the mutation methods, except for PrependMut.
res := mach.Add1(state, args)
<-mach.WhenQueue(res):
if mach.Is1(state) {
// executed
} else {
// canceled
}
feat(machine): add strict breakpoints
PR: #286 (@pancsta)
Breakpoints now support the queue and empty transitions with the strict param.
feat(machine): add CheckDone to Can methods
PR: #285 (@pancsta)
Can* methods now return over a channel, which makes them compatible with the queue.
done := &am.CheckDone{
Ch: make(chan struct{}),
}
mach.CanAdd(states, &am.AT{
CheckDone: done,
})
<-done.Ch
fix(machine): fix duplicate detection for Can*
PR: #284 (@pancsta)
No PR description provided.
v0.14.0-pre1 (2025-08-17)
The highlight of this release is a refreshed debugger. Not all the tests pass, so marked as a prerelease.
- feat: release v0.14.0 #281
- feat(am-dbg): release am-dbg v0.14 #280
- feat(am-dbg): add log pane hiding #279
- feat(am-dbg): add web diagram viewer #278
- feat(am-dbg): add support for schema groups and Can methods #277
- fix(telemetry): fix otel and jaeger #276
- feat(helpers): add CantAdd, AskAdd, CantRemove, AskRemove #275
- feat(machine): add CanAdd, CanRemove, and EnableCan #274
- feat(machine): add PrependMut #273
- feat(machine): add Semantic Logger #272
- feat(machine): add schema groups via Machine.SetGroups #271
- feat(machine): recover state from timeouts in final handlers #270
feat: release v0.14.0
PR: #281 (@pancsta)
- add
amhelp.EvalGetter
All the remainder of v0.14 is in this changelist.
feat(am-dbg): release am-dbg v0.14
PR: #280 (@pancsta)
A lot of bugfixes, especially the race conditions being finally tackled (via the sub-handler methods h*() convention). It’s even possible that the internal GC will now work as expected…
- fix: keyboard navigation
- fix: races with rendering
- postpone txs during repaints
- feat: style tx times
- fix: minor mouse issues
- fix: rebuild log race
- feat: add –id
- feat: expand schema on 2nd click
feat(am-dbg): add log pane hiding
PR: #279 (@pancsta)
The log view is now fully optional, which makes the debugger fit into a sidebar. The reader pane can be opened without the log (although it still relies on LogOps to be send over telemetry).
All the views below show the same transition:
feat(am-dbg): add web diagram viewer
PR: #278 (@pancsta)
After caching made diagrams fast / usable, the next step was to add a web viewer. A single-file SVG viewer is served on the same port the debugger is listening on, and features auto-refresh and zoom. More optimizations are possible in the future.
Video: https://github.com/user-attachments/assets/0225cd4b-e8a9-41b9-b6da-94f444a1a439
Hidden behind --ui-diagrams, as it causes some conn races.
feat(am-dbg): add support for schema groups and Can methods
PR: #277 (@pancsta)
The new “Can methods” have their dedicated filter in the debugger.
State groups are now first-class citizens in the debugger, making working with large schemas WAY easier. Support has been added across the whole app:
- schema tree
- timeline
- log
- diagrams
- rain
- matrix
fix(telemetry): fix otel and jaeger
PR: #276 (@pancsta)
- tele: fix otel index names
- tele: fix jaeger version
feat(helpers): add CantAdd, AskAdd, CantRemove, AskRemove
PR: #275 (@pancsta)
New helpers making “Can methods” easier to use in oneliners.
amhelp.AskAdd1(mach, ss.MissUpdatesByGossip, Pass(&A{
PeersGossip: msg.PeerGossips,
PeerId: fromId,
}))
feat(machine): add CanAdd, CanRemove, and EnableCan
PR: #274 (@pancsta)
The long-delayed “Can methods” have finally landed. These can be used to probe which mutations are possible, or rather impossible. Benefits:
- just for checking things out (graph traversal)
- a cleaner debug timeline for negotiation-heavy machines (less canceled transitions)
func (d *Debugger) ExceptionEnter(e *am.Event) bool {
// ignore eval timeouts, but log them
a := am.ParseArgs(e.Args)
if errors.Is(a.Err, am.ErrEvalTimeout) {
if !e.IsCheck {
d.Mach.Log(a.Err.Error())
}
return false
}
return true
}
feat(machine): add PrependMut
PR: #273 (@pancsta)
This new mutations method is for advanced use cases, like race avoidance by delaying / waiting within a handler timeout. For usage see /tools/debugger.(AnyEnter).
feat(machine): add Semantic Logger
PR: #272 (@pancsta)
- replacing many log related methods
InternalLogremoved
Logging changes from v0.13 proved not to scale well, and a new semantic logger has been introduced. LogChanges is now 2 (was 1 and then 3), which is final thanks to sem logger methods:
EnableGraph(for pipes and custom connections)EnableStepsEnableCan
New logging env vars were added:
AM_LOG_FULLAM_LOG_STEPSAM_LOG_GRAPHAM_LOG_CHECKS
feat(machine): add schema groups via Machine.SetGroups
PR: #271 (@pancsta)
Schema groups can now be directly injected into the machine and processed automatically, along with schema inheritance. Manual / fallback version is also provided.
// inject groups and infer parents tree
mach.SetGroups(states.MachTemplateGroups, states.MachTemplateStates)
feat(machine): recover state from timeouts in final handlers
PR: #270 (@pancsta)
Timed-out transitions now recover the final state the same way panics do. Manual data recovery (if any) is still necessary via catching the correct Exception.
v0.13.0 (2025-07-09)
The highlights of this release are optimization (both space and time) and fault tolerance (via handler timeouts). Additionally, diagrams are now being cached and reused.
feat: release v0.13.0
PR: #269 (@pancsta)
All the remainder of v0.13 is in this changelist.
feat(am-dbg): cache SVG diagrams based on schema hashes
PR: #268 (@pancsta)
Diagrams are now usable for observing active states, as they dont re-render on each transition change. Re-rendering completely changes the layout, so schemas are hashed and SVGs reused. Another SVG DOM cache is kept for the current machine, making results almost instantaneous.
feat(machine): various space and time optimizations
PR: #267 (@pancsta)
- fix(machine): dont process handlers for machines without handlers
- fix(machine): reuse the handler goroutine
- feat: prevent Heartbeat and Healthcheck from triggering auto transitions #256
Optimizations were very much needed at this point, and there’s still room for improvement (especially for context GC). Load tests can now handle more load, and the debugger feels snappier.
feat(machine): add LogExternal, LogSteps log levels
PR: #266 (@pancsta)
A simple yet effective optimization in v0.13 is disabling the log, while still keeping external msgs. This will also prevent the machine from creating and sending out debugging steps, per each transition. This unfortunately breaks old data dumps a bit, which need to be bumped +2 for the log level.
feat(helpers): support copying schemas and mirror machines
PR: #265 (@pancsta)
Schemas can now be easily injected into existing machines with err = amhelp.CopySchema(schema, mach, statesWhitelist).
One can also create “metrics-machines” by mirroring interesting states in eather flat or deep manner (without or with state clocks).
feat(pubsub): optimize pubsub, add UDS for tests
PR: #264 (@pancsta)
- update libp2p (not pubsub)
- add more rate limiting via a multiplayer
- queue-aware mutations
- evals removed
- use libp2p without prom_client
- saves 15k goroutines for 100 peers
- load test using Unix Domain Sockets
Load test for 200 peers now passes. It’s possible to debug 100 peers with metrics machines, and logging disabled.
feat(machine): add OnError(), IsQueueAbove(), QueueLen()
PR: #263 (@pancsta)
New handy machine methods - OnError is for machines without handlers, while the other 2 are optimization-related. Still #262 remaining.
feat(machine): introduce backoff for handler timeouts
PR: #261 (@pancsta)
- fixes #220
- add Backoff(), HandlerDeadline, HandlerBackoff
Fault tolerancy now works well under a heavy load with handlers timing out:
- handler is started
- timeout is reached (100ms)
- handle has to return within
HandlerDeadline(10s) - as early as
Event.IsValid()returns true - transition is canceled anyway
- in case of no return, the machine goes into a backoff state
- all the queue is flushed down the drain
- machine cancels any mutations for the period of
HandlerBackoff(3s) - new mutations run on a new event loop
This achieves goroutine cancellation based on probabilities (similar to hash collisions), and can be adjusted on per-machine and per-usecase basis.
fix(am-dbg): fix log timestamps
PR: #260 (@pancsta)
No PR description provided.
fix(machine): fix states implied by rejected auto states should also be rejected
PR: #259 (@pancsta)
Fixes #255
v0.12.0 (2025-06-26)
Highlights of v0.12:
- dynamic schemas
- naming cleanups
- lots of debugger polish
- new machine methods and TimeIndex
feat: release v0.12
PR: #253 (@pancsta)
The umbrella PR for all the code which didnt fit in a dedicated v0.12 changelist.
feat(pubsub): optimize resources usage
PR: #252 (@pancsta)
- feat(pubsub): add errgroup
- feat(pubsub): add worker states
Thanks to the improved concurrency management, with less multi states and a goroutine pool, the 100 peers load test went down from >1m to <3s. Still waiting on #220 to run stress tests.
feat(am-dbg): add am-dbg v0.12
PR: #251 (@pancsta)
- feat(dbg): add
--output-txto dir/am-dbg-tx.txt - fix(dbg): show client list when started in narrow view
- fix(dbg): fix help button
- feat(dbg): click on log line selects the 1st state
- feat(dbg): add hiding stack traces
- feat(dbg): add forked txs in the reader
- feat(dbg): add support for schema changes
- feat(dbg): remember tree expansions in reader view
- feat(dbg): add detailed step descriptions in statusbar
- feat(dbg): add keybindings for steps
- feat(dbg): add sibling mutations to reader view
- fix(dbg): fix received txs not filtered for canceled and auto
- feat(dbg): add multi state indicators to schema tree
- feat(dbg): add tags support to schema tree
- fix(dbg): empty filter wont remove canceled txs
- fix(dbg): fix tree steps color issues #167
- feat(dbg): de-noise stack traces
- fix(dbg): send disconnects with –fwd-data
- fix(dbg): fix visualizer panic when parent mach missing
This release is full a tiny features and fixes aiming at daily usage, as well as adding support for dynamic schemas.
feat(dbg): add ttyd docker image
PR: #250 (@pancsta)
am-dbg now has a docker image with a web interface and REPL (via a zellij dashboard). This is useful for docker compose deployments.
feat(dbg): support machine URLs in data imports
PR: #249 (@pancsta)
When importing a data dump, it’s possible to go to a specific transition step by passing a Machine URL:
am-dbg mach://cook/f6578eb6cdf79e59f0ecccbe375c26c0/27/t152 --import-data dump.gob
feat(repl): support watching for address changes
PR: #248 (@pancsta)
An experimental implementation of --watch is here for both dirs and files. It’s brute-forced but seems to work well.
feat(rpc): support client schema changes
PR: #247 (@pancsta)
Schemas can now be safely enlarged, which will cause the server to push the new schema to the client. Schema’s length is effectively its version. This feature is also supported by am-dbg.
fix(states): piping ErrFoo will also add Exception
PR: #246 (@pancsta)
All error states (eg ErrFoo) should require Exception, so piping to custom error states wasn’t possible, unless ErrFoo:Add:Exception was in relations. This is now automated by piping methods.
feat(helpers): add slog and loop guards
PR: #245 (@pancsta)
- feat(helpers): add NewStateLoop(…) for state loop guards
- feat(helpers): add slog to mach log converter
- feat(helpers): log args for slices
- feat(helpers): add Cond for complex state relations
NewStateLoop helper creates a state loop guard bound to a specific state (eg Heartbeat), preventing infinite loops. It monitors context, off states, ticks of related “context states”, and an optional check function.
type A {
buttons []string `log:"buttons"`
}
It’s now possible to use a machine as an slog sink.
fix(telemetry): add misc fixes v0.12
PR: #244 (@pancsta)
- feat(telemetry): shorter auto exporter functions
- fix(telemetry): loki dispose, add err msg
- fix(telemetry): fix prometheus tracer races
- fix(telemetry): better logging for dbg tracers
- fix(telemetry): fix races in otel
feat(machine): add misc v0.12 changes
PR: #243 (@pancsta)
v0.12 release remainder for the machine package.
feat(machine): AddErr methods will no-op for nil errors
PR: #242 (@pancsta)
Non-actionable errors can be handled by oneliners. The two examples below are equal. All *AddErr* mutation methods work this way now.
Before:
err := Op()
if err != nil {
mach.AddErr(err, nil)
// no return
}
After:
err := Op()
mach.AddErr(err, nil) // does nothing for err == nil
feat(machine): add dummy Event.IsCheck
PR: #241 (@pancsta)
Right now, Event.IsCheck is always false, but allows for safe errors and logs in negotiation handlers. It will be useful for negotiation checking.
fix(machine): re-add Exception with 1st handlers
PR: #240 (@pancsta)
When a machine is in Exception and the first handlers are bound, the Exception state is Add-ed again, to cause the ExceptionState handler. This prevents timing issues and disappearing errors.
feat(machine): add WasTime, WasClock
PR: #239 (@pancsta)
To complement IsTime and IsClock (which need an exact match), the new methods consider the progressive nature of clocks.
feat(machine): add TimeIndex and replace Mutation.CalledStates
PR: #238 (@pancsta)
- feat(machine): add TimeIndex
- fix(machine): fix TimeAfter set too late
- feat(machine): add nice Transition.String()
- feat(machine): refac Mutation.Called to indexes
am.TimeIndex is a struct wrapping []uint64 and a state index. It exposes basic state checking methods, eg ti.Is1("Foo"). It can also be used to represent binary lists of states, eg Mutation.CalledIndex(S).
v0.11.0 (2025-05-01)
The highlight of this release is the more advanced OpenTelemetry integration #231.

feat(am-gen): generate basic schema constructors
PR: #235 (@pancsta)
Constructors directly in schemas - seems like nothing, but it really lowers the barrier of entry and makes testing / experimenting faster. Of course handlers should NOT be kept in schema packages.
import "github.com/USER/REPO/states"
// ...
mach := states.NewFoo(ctx)
feat(am-dbg): improve toolbar and statusbar
PR: #234 (@pancsta)
Changes:
- feat: improve toolbar UX
- feat: show steps in the status bar
- refac: add missing focus states
- feat: bump Go to 1.24
- refac: –graph is now –diagrams
- fix: fix dashboards, add task am-dbg-dashboard-exit
The bottom of the debugger is now easier to use with ordered navigation arrows, as well as step’s details when the 2nd timeline is scrolled. The dashboard also have gotten good enough to be a daily driver with the new zellij option to expand a pane using alt+plus, so it can look like a single instance when needed.
refac(machine): rename Struct to Schema, WhenTicksEq to WhenTime1
PR: #233 (@pancsta)
It’s never too late to get rid of bad naming, as long it’s semver-safe…
Breaking changes:
am.Structis nowam.SchemaMachine.GetStructis nowMachine.Schemaam.StructMergeis nowam.SchemaMergeTracer.StructChangeis nowTracer.SchemaChangeMachine.WhenTicksEqis nowMachine.WhenTime1
feat(integrations): add NATS and JSON types with schemas
PR: #232 (@pancsta)
The first integration has landed and it’s NATS because of how versatile and scalable it its (pubsub, queue, kv). The whole comms is through JSON (with tight schemas) and the plan is to use them for other integrations. Below an example of a subscription:
{
"kind" : "am_req_waiting",
"states" : [ "A" ]
}
Types:
- MutationReq
- MutationResp
- WaitingReq
- WaitingResp
- GetterReq
- GetterResp
Usage is as simple as res, err := amnats.Add(ctx, nc, topic, mach.Id(), am.S{"A"}, nil).
feat(telemetry): improve Otel integration
PR: #231 (@pancsta)
Changes:
- extend tracing config (add health, handlers, auto)
- link states and transitions
- group handlers as events
- group multi-states as event
- show mutations as events of states
- fix canceled txs
- link transitions to their source transitions (eg from EvAdd)
- env based loki logger
- remove dep on samber/lo
- move Grafana error panel to 2nd position
Some people seems to really like Otel traces, so this iteration covers all the postponed features like linking and noise reduction. This should be enough to handle tracing of larger datasets. Skipping canceled and empty is still pending, as it requires writing a custom processor.
v0.10.3 (2025-04-13)
feat(am-dbg): add graphs, dashboards, and others
PR: #229 (@pancsta)
- feat(am-dbg): add graph rendering
- feat(am-dbg): add zellij dashboards
- feat(am-dbg): add auto narrow layout and –view-narrow
- feat(am-dbg): add –view-rain, –view-timelines
- fix(am-dbg): ignore ID-less clients
- fix(am-dbg): fix expand/collapse of schema pane
- feat(am-dbg): improve the toolbar
Dashboard can be started with task am-dbg-dashboard. The responsive narrow layout is useful also outside of dashboards. Requires zellij.
Graph rendering has been extracted from the am-vis prototype - automatic generation with 3-level of details of the currently selected machine is now built right into the toolbar.
Generated structure inside --dir:
#### currently visible machine
MACHID.d2
MACHID.mermaid
MACHID.svg
#### symlink for autorefresh
am-vis -> MACHID.svg
#### overall map
am-vis-map.d2
am-vis-map.mermaid
am-vis-map.svg
feat: add MTimeDiff, Time.DiffSince
PR: #228 (@pancsta)
MTimeDiff in pkg/history computes a diff between of the machine time for the given transition.
feat(telemetry): add env-based activation for Otel, Prom, Grafana, pkg/rpc
PR: #227 (@pancsta)
- feat(telemetry): add env-based activation for Otel, Prom, Grafana, pkg/rpc
- fix(rpc): improve error support for REPL
- feat: make Inspect() output more readable
This is the highlight of v0.10.3 - all data exporters are now automatic and env-based, including Grafana dashboards. Setup code available in mach_template, env vars in /config/env/.
#### telemetry source (service / job)
#### defaults to ""
AM_SERVICE=
#### prometheus address
#### defaults to ""
AM_PROM_PUSH_URL=http://localhost:9091
#### grafana address, required for automatic dashboards
#### defaults to ""
AM_GRAFANA_URL=http://localhost:3000
#### grafana API token, required for automatic dashboards
#### defaults to ""
AM_GRAFANA_TOKEN=
#### export Otel traces for states and submachines
#### defaults to ""
AM_OTEL_TRACE=1
#### create additional Otel traces for transitions
#### defaults to ""
AM_OTEL_TRACE_TXS=1
// telemetry
mach.SetLogLevel(am.LogChanges)
mach.SetLogArgs(LogArgs)
amhelp.MachDebugEnv(mach)
// start a dedicated aRPC server for the REPL, create an addr file
arpc.MachReplEnv(mach)
// root machines only
if mach.ParentId() == "" {
// export metrics to prometheus
amprom.MachMetricsEnv(mach)
// grafana dashboard
err := amgen.MachDashboardEnv(mach)
if err != nil {
mach.AddErr(err, nil)
}
// open telemetry traces
err = amtele.MachBindOtelEnv(mach, false)
if err != nil {
mach.AddErr(err, nil)
}
}
feat(pkg/states): add global import, BindStart, ConnPool
PR: #226 (@pancsta)
We can skip the states_utils.go file now by importing the namespace into the global scope.
import _ "github.com/pancsta/asyncmachine-go/pkg/states/global"
ConnPool extends Connected schema and adda a new ConnectedFully state.
// ConnPoolSchema represents all relations and properties of ConnPoolStates.
var ConnPoolSchema = am.Schema{
ssPc.ErrConnecting: {Require: S{Exception}},
ssPc.Disconnected: {
Remove: S{ssPc.Connecting, ssPc.ConnectedFully, ssPc.Disconnecting},
},
ssPc.Connecting: {
Require: S{ssB.Start},
Remove: S{ssPc.Disconnecting},
},
ssPc.Connected: {
Require: S{ssB.Start},
Remove: S{ssPc.Disconnected},
},
ssPc.ConnectedFully: {
Require: S{ssPc.Connected},
Remove: S{ssPc.Disconnected},
},
ssPc.Disconnecting: {
Remove: S{ssPc.ConnectedFully, ssPc.Connected, ssPc.Connecting},
},
}
v0.10.2 (2025-02-25)
The REPL has landed.

feat(arpc): add aRPC REPL
PR: #225 (@pancsta)
arpc is a network-native REPL and CLI to manage one or many asyncmachines.
Combined with am-dbg and a dedicated machine, it can act as a debugging agent, showing results in the debugger, while accepting commands in the REPL. It’s also useful to modify live systems without restarting them.

v0.10.1 (2025-02-15)
The highlight of this release is a decentralized PubSub based on libp2p.
Other changes are:
- handful of bugfixes for
pkg/machine - new features for
am-dbg
The upcoming diagram generator am-vis didnt make it, but the draft PR prototype is more than usable, as seen in the readmes which now feature state schemas.
Last but not least, asyncmachine-go was featured in the Golang Weekly #539 newsletter, thank you.
feat(am-dbg): add --client-list file.txt for a live detailed dump
PR: #224 (@pancsta)
With a large list of clients, especially tagged ones, it’s way easier to browser it in a plain text file. It refreshes automatically to match the current one in the TUI. Useful for searching for peer IDs and addresses within tags.
Using --client-list am-dbg.txt will create am-dbg.txt in CWD.
ps-TestExposingMany-p0-root R|85+
#pubsub:TestExposingMany
#peer:12D3KooWKBTjhWff2Vd1xbW41duMp5hJmZsBnfGrcNz4vL7XNpuy
- ps-rand-p1-0-065a |0
#pubsub-worker
#src-id:rand-p1-0
- ps-rand-p1-1-41b0 |0
#pubsub-worker
#src-id:rand-p1-1
- ps-rand-p1-2-ab80 |0
#pubsub-worker
#src-id:rand-p1-2
- ps-rand-p3-0-7408 |0
#pubsub-worker
#src-id:rand-p3-0
- ps-rand-p3-1-8476 |0
#pubsub-worker
#src-id:rand-p3-1
- ps-rand-p3-2-2787 |0
#pubsub-worker
#src-id:rand-p3-2
- ps-rand-p4-0-1f7e |0
#pubsub-worker
#src-id:rand-p4-0
- ps-rand-p4-1-5590 |0
#pubsub-worker
#src-id:rand-p4-1
- ps-rand-p4-2-74d8 |0
#pubsub-worker
#src-id:rand-p4-2
- ps-rand-p5-0-3728 |1
#pubsub-worker
#src-id:rand-p5-0
- ps-rand-p5-1-e61a |1
#pubsub-worker
#src-id:rand-p5-1
- ps-rand-p5-2-fc37 |1
#pubsub-worker
#src-id:rand-p5-2
ps-TestExposingMany-p1 S|102+
#pubsub:TestExposingMany
#peer:12D3KooWJ6a95r7XZrHXJ8YonNH3xnwWgfdEpcPDTTmEv2Ypdh9Z
- ps-rand-p3-0-831a |0
#pubsub-worker
#src-id:rand-p3-0
- ps-rand-p3-1-c081 |0
#pubsub-worker
#src-id:rand-p3-1
- ps-rand-p3-2-1248 |0
#pubsub-worker
#src-id:rand-p3-2
- ps-rand-p4-0-370b |0
#pubsub-worker
#src-id:rand-p4-0
- ps-rand-p4-1-4909 |0
#pubsub-worker
#src-id:rand-p4-1
- ps-rand-p4-2-069f |0
#pubsub-worker
#src-id:rand-p4-2
- ps-rand-p5-0-783d |1
#pubsub-worker
#src-id:rand-p5-0
- ps-rand-p5-1-a73f |1
#pubsub-worker
#src-id:rand-p5-1
- ps-rand-p5-2-9d04 |1
#pubsub-worker
#src-id:rand-p5-2
docs(pubsub): add pubsub and schemas
PR: #223 (@pancsta)
No PR description provided.
feat(pubsub): add initial pubsub implementation
PR: #222 (@pancsta)
/pkg/pubsub is a trustful decentralized synchronization network for asyncmachine-go. Each peer exposes several state machines, then starts gossiping about them and other ones known to him. Remote state machines are then visible to other peers as /pkg/rpc.LocalWorker. PubSub can be used to match Clients with Workers from /pkg/node.
This is an early version and needs optimizations, as its hard to run load tests ATM. Time to settle discovery for 100 peers is around ~50s.
Features
- gossip-based discovery
- gossip-based clock updates
- gossip checksums via machine time
- rate limitting
- no leaders, no elections
fix(machine): improve timeouts (eg add EvalTimeout)
PR: #219 (@pancsta)
Eval now has a separate timeout, as the way eval funcs execute is completely different than handlers. Additionally
event now exposes Event.IsValid(), but changes in the handler loop are needed to properly implement timeouts.
Further work forked to https://github.com/pancsta/asyncmachine-go/issues/220.
feat(machine): improve exception handling
PR: #218 (@pancsta)
Error mutations will now pass through the queue limit (when needed).
ExceptionState now understands richer context from arguments:
- TargetStates
- CalledStates
- TimeBefore
- TimeAfter
- Event
Still needs to be implemented in EvAddErrState #221.
fix(am-dbg): add fixes for large amount of clients
PR: #217 (@pancsta)
- fix(am-dbg): fix log scrolling H
- fix(am-dbg): optimized client list >1k
- fix(am-dbg): fix races during GC
fix(am-gen): fix inheriting of Disposed
PR: #215 (@pancsta)
No PR description provided.
feat(am-dbg): add 2nd toolbar, style log states
PR: #214 (@pancsta)
UX:
- toolbar is now mouse accessible
- state names in the log are highlighted according to importance
fix(machine): add missing args param to Toggle1, Toggle
PR: #213 (@pancsta)
No PR description provided.
fix(rpc): fix RPC retry ctx
PR: #212 (@pancsta)
No PR description provided.
v0.10.0 (2025-01-14)
feat(states): add Disposed state def
PR: #210 (@pancsta)
Async disposal is common and Machine.HandleDispose() is problematic. The new predefined Disposed mixin with handlers allows for standardized atomic disposals.
docs(examples): refac NFA example
PR: #209 (@pancsta)
Refactor to explicit input states (Input0, Input1).
feat(machine): allow for partial auto states via handlers
PR: #208 (@pancsta)
Until now, partial auto states were accepted only from resolving relation (via Remove). Now, handlers for auto states can also remove that state from target states of a transition.
These will remove Foo in auto transitions:
FooEnterBarFoo
fix(node): migrate to lock-less workers map
PR: #207 (@pancsta)
Instead of using a concurrent map and per-record RW locks, everything now goes via ListWorkers and SetWorker states and the Supervisor.Workers() getter, while in-handler access is via a regular map[string]*workerInfo.
v0.9.1 (2025-01-14)
fix(machine): dont panic on topo sort with cycles
PR: #206 (@pancsta)
No PR description provided.
feat(machine): improve state-state handlers (eg FooBar)
PR: #205 (@pancsta)
FooBar (aka state-state) handlers were previously only fired between states exit-enter. This left holes in negotiation, as all currently active states should be asked about new ones (not just the ones being de-activated).
This is a very small breaking change (order changed and the handler is called more often).
fix: fix test -race for handler.Dispose
PR: #204 (@pancsta)
No PR description provided.
fix: fix test -race for handleDispose
PR: #203 (@pancsta)
No PR description provided.
ci: split go pipelines
PR: #202 (@pancsta)
Each package now has a separate pipeline with more detailed test execution:
- parallel
- race
fix(dbg): fix mouse scroll
PR: #201 (@pancsta)
- tree scroll fixed
- client list scroll fixed
- log view clicks added
feat(dbg): group piped states
PR: #200 (@pancsta)
Pipes are now grouped by states instead of states & machines, which is way cleaner and fits more lines.
fix(dbg): fix rain with filters
PR: #199 (@pancsta)
No PR description provided.
fix(dbg): improve reader links and history
PR: #198 (@pancsta)
Besides fixes for navigation theres also a toolbar button to turn on the reader (in addition to a hotkey).
feat(dbg): show tags from parent machines
PR: #197 (@pancsta)
The tag list inherits from all parents (separated by ...), so nothing needs to be duplicated.
v0.9.0 (2024-12-15)
Highlights:
- address bar #188 #187
- mouse support #189
feat: add v0.9 release
PR: #194 (@pancsta)
All the things which didnt make it to other PRs, mostly small fixes, readmes and miss-merges.
feat(node): release v0.9
PR: #193 (@pancsta)
Still experimental, but moving forward. Some serious stability issues have been addressed. Fail recovery needs more work.
Also implemented:
- tags support
- event source
fix(am-dbg): add various fixes v0.9
PR: #192 (@pancsta)
- feat(am-dbg): add Healthcheck filter
- fix(am-dbg): distinguish msgs from same ID on different conns
- fix(am-dbg): optimize rendering
- fix(am-dbg): fix state un-selection
- fix(am-dbg): fix removing clients
- fix(am-dbg): fix steps tree sorting
- fix(am-dbg): fix rain borders
- feat(am-dbg): sticky steps expansion
feat(am-dbg): add –fwd-data flag
PR: #191 (@pancsta)
This little feature is actually very important, as it distributes indentical data across N number of instances. Some can be used in a dashbaord, and some to persist / process the data.
View the same (live) data in 2 instances of am-dbg:
- TTY1 -
am-dbg -l 1234 - TTY2 -
am-dbg --fwd-data 1234 - TTY3 -
env AM_DBG_ADDR=1 go run ./mach.go
feat(am-dbg): hide GCed pipes
PR: #190 (@pancsta)
Now pipes from GCed machines will be hidden in the Reader, accordingly.
feat(am-dbg): add mouse support
PR: #189 (@pancsta)
Everything can be clicked now, and most of the things can be scrolled. Only the bottom toolbar is keyboard-only. Mouse is now enabled by default.
feat(am-dbg): add address bar & tags
PR: #188 (@pancsta)
This PR adds an address bar built using a table, and re-builds the filter bar in the similar fashion. Keyboard support comes from the table.
Each machine has 2 history entries (enter & exit) and switching is done via the client list and “Source” in the reader pane. Clipboard support imported from micro-editor.
feat(am-dbg): add Source in reader pane
PR: #187 (@pancsta)
Clickable “Source” entry now works:
- within the same machine
- between 2 machines within the same process
- between 2 machines over RPC (bidi)
feat(am-dbg): add highlighting for log args and stack traces
PR: #186 (@pancsta)
Greatly improves readability and makes debugging faster.
feat(am-dbg): improve GC and add –max-mem flag
PR: #185 (@pancsta)
Debugger will now dispose log level 2 msgs before disposing transitions. Max mem usage can be set with --max-mem and max number of clients was raised to 1k. State names are indexed on the client side.
refac(machine): add indexes to Step
PR: #184 (@pancsta)
Initial optimization for using indexes everywhere, instead of string state names.
feat(machine): add AMerge for log args
PR: #183 (@pancsta)
Syntax sugar for inheriting log args from a dependency.
a1 := amnode.ParseArgs(args)
a2 := ParseArgs(args)
// ...
return am.AMerge(amhelp.ArgsToLogMap(a1), amhelp.ArgsToLogMap(a2))
feat(machine): allow events as mutation sources eg EvAdd()
PR: #182 (@pancsta)
Handlers can now use *Event as a source with prefixed methods. This allows for distributed tracing / state trace.
EvAddEvAdd1EvRemoveEvRemove1
v0.8.1 (2024-12-14)
- feat(telemetry): add metrics for state activations (#158)
- feat(helpers): add fan out-in (#159)
- fix(machine): fix remove implied by multi (#180)
- feat(am-dbg): show tx handlers in log reader (#157)
fix(machine): fix remove implied by multi
PR: #180 (@pancsta)
No PR description provided.
feat(helpers): add fan out-in
PR: #159 (@pancsta)
- Fan Out Fan In algo via state generation with relations and a concurrency limit
- new global final handler
AnyState - /examples/fan_out_in
feat(telemetry): add metrics for state activations
PR: #158 (@pancsta)
It’s now possible to get metrics for important state activations right out of the box via --added-states, eg
am-gen grafana
--name root
--folder tree_state_source
--ids root,rm-root,rs-root-0,rs-root-1,rs-root-2
--source tree_state_source_root
--added-states root:Flight1Departed,root:Flight2Departed,root:Flight3Departed,root:Flight4Departed,root:Flight5Departed
States need to be monitored first via amprom.BindMach(mach, states). Screen from /examples/tree_state_source.
feat(am-dbg): show tx handlers in log reader
PR: #157 (@pancsta)
- executed handlers of the current transition are now pinned to the top of the reader
- handler names aren’t send over with steps any more
test: add source param to /pkg/helpers
PR: #156 (@pancsta)
Fixes ambiguous timeout msgs in test logs.
ci: add tests and LoC badges
PR: #155 (@pancsta)
- badges
- cache
- use
task
v0.8.0 (2024-11-13)
feat(am-dbg): release am-dbg v0.8.0
PR: #154 (@pancsta)
Highlights:
- error support
- log reader
- GC
This is a big release for the debugger, as requirements have risen with the introduction of pkg/node and an efficient error detection become neccessary. Log reader will show whats happening under the hood (subscription, pipes, ctxs), allowing to focus on LogChanges logs only, while garbadge collection of old messages allows the app to be open indefinitely.


Full changelog:
- fix: fix log highlights
- fix: fix filters
- feat: add state jump support filters
- feat: add state jump support called states
- feat: add rain view timestamps
- feat: state tree resize for steps
- feat: add hide summaries filter
- fix: fix [] escaping
- fix: skip collapsed nodes when tree type-search
- fix: make type-search start from current position
- feat: GC old messages
- feat: optimize space
- feat: mark errors on the rain
- feat: mark recent errors in the client list
- feat: mark Start and Ready in client list
- feat: preserve timeline position across clients
- feat: skip binding rpc with
-l -1 - feat: show tx numbers till current H time in client list
- feat: align numbers in tree and sidebar
- feat: import data from URLs
- feat: add log reader
- fix: export to file not saving
- feat: support parents in client list
- feat: add rain support filters
feat: add rest of v0.8.0
PR: #153 (@pancsta)
Remainder of v0.8.0 changes.
feat(examples): add benchmark state source
PR: #152 (@pancsta)
Another RPC benchmark, this time showing how to distribute state-read load across a tree of nodes. Spoiler: it gets faster with more nodes.
See /examples/benchmark_state_source.
feat(examples): add tree state source
PR: #151 (@pancsta)
The biggest example so far, has it’s own state generator and can be used to stress test telemetry, but most of all it’s an interesting distributed concept.
flowchart BT
Root
Replicant-1 -- aRPC --> Root
Replicant-2 -- aRPC --> Root
Replicant-1-1 -- aRPC --> Replicant-1
Replicant-1-2 -- aRPC --> Replicant-1
Replicant-1-3 -- aRPC --> Replicant-1
Replicant-2-1 -- aRPC --> Replicant-2
Replicant-2-2 -- aRPC --> Replicant-2
Replicant-2-3 -- aRPC --> Replicant-2
See /examples/tree_state_source.
feat(helpers): add dedicated test helpers
PR: #150 (@pancsta)
Test versions of /pkg/helpers which accept testing.T instead of returning an error, as well as assertion functions for stretchr/testify.
feat(helpers): add WaitFor* methods
PR: #149 (@pancsta)
To tackle select&ctx-pasta, there’s new Waiting helpers
- WaitForAll
- WaitForAny
- WaitForErrAll
- WaitForErrAny
Example - wait for bootstrap RPC to become ready.
//
err := amhelp.WaitForAll(ctx, s.ConnTimeout,
boot.server.Mach.When1(ssrpc.ServerStates.RpcReady, nil))
if ctx.Err() != nil {
return // expired
}
if err != nil {
AddErrWorker(s.Mach, err, Pass(argsOut))
return
}
Example - wait for Foo, Bar, or Exception.
ctx := client.Mach.NewStateCtx("Start")
err := amhelp.WaitForErrAll(ctx, 1*time.Second, mach,
mach.When1("Foo", nil),
mach.When1("Bar", nil))
if ctx.Err() != nil {
// no err
return nil // expired
}
if err != nil {
// either timeout happened or Exception has been activated
return err
}
Theres is more new related stuff in /pkg/helpers.
feat(helpers): add failsafe Request object
PR: #148 (@pancsta)
Because of optionally-accepting nature of asyncmachine, as well as network-bound issues, not every mutation goes through. New Request object allows to define various retry policies using failsafe-go, which will re-try until done, policy dries out, or context expires.
import amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
// ...
// failsafe worker request
_, err := amhelp.NewReqAdd1(c.Mach, ssC.WorkerRequested, nil).Run(ctx)
if err != nil {
return err
}
feat(rpc): add logging supports to RPC Worker
PR: #147 (@pancsta)
With Tracers support for RPC workers, it makes sense to expose local logs directly, eg to track local subscriptions (today), or local handlers (in the future).
feat(rpc): add am.Tracer supports to RPC Worker
PR: #146 (@pancsta)
Network transparency is one thing, and API polimorphism another one. Now RPC workers can be traced by telemetry just like any other asyncmachine. This means debugging a remote worker, getting metrics, or anything else from /pkg/telemetry.
feat(rpc): add client failsafety
PR: #145 (@pancsta)
RPC client now has connection and call retry policies with backoff. Everything is configurable. Migrated to states files v2.
feat(rpc): add port multiplexer
PR: #144 (@pancsta)
cmux-based multiplexer will create a new RPC server for each new connection. Used by node supervisors.
feat(telemetry): auto-detach dbg tracer on errs
PR: #143 (@pancsta)
Small dev-UX, when you can disconnect a debugger anytime and the tracers will gracefully quit.
feat(telemetry): Loki and Otel loggers
PR: #142 (@pancsta)
Logs can now be exported to more destinations. Loki is supported by the new Grafana dashboard, while OpenTelemetry seems good for stdout scraping.
feat(am-gen): gen grafana dashboards using grabana
PR: #141 (@pancsta)
Grafana finally becomes usabe with the new auto-syncing am-gen subcommand. Prometheus data source was also aligned.

Panels:
- Number of transitions
- Transition Mutations
- Queue size
- States added
- States removed
- States touched
- Transition Details
- Transition ticks
- Number of steps
- Number of handlers
- States & Relations
- Number of states
- Number of relations
- Referenced states
- Active states
- Inactive states
- Average Transition Time
- Errors
- Log view
feat(node): add pkg/node
PR: #140 (@pancsta)
This is the highlight of the v0.8.0 release - distributed worker pools with supervisors. Still in an early stage, but behaves very well.
flowchart LR
c1-RemoteWorker -- aRPC --> w1-rpcPub
c1-RemoteSupervisor -- aRPC --> s1-rpcPub
subgraph Client 1
c1-Client[Client]
c1-RemoteWorker[RemoteWorker]
c1-RemoteSupervisor[RemoteSupervisor]
c1-Client --> c1-RemoteWorker
c1-Client --> c1-RemoteSupervisor
end
subgraph Client 2
c2-Client[Client]
c2-RemoteWorker[RemoteWorker]
c2-RemoteSupervisor[RemoteSupervisor]
c2-Client --> c2-RemoteWorker
c2-Client --> c2-RemoteSupervisor
end
subgraph Node Host
subgraph Worker Pool
w1-rpcPub --> Worker1
w1-rpcPub([Public aRPC])
w1-rpcPriv --> Worker1
w1-rpcPriv([Private aRPC])
w2-rpcPub --> Worker2
w2-rpcPub([Public aRPC])
w2-rpcPriv --> Worker2
w2-rpcPriv([Private aRPC])
w3-rpcPub --> Worker3
w3-rpcPub([Public aRPC])
w3-rpcPriv --> Worker3
w3-rpcPriv([Private aRPC])
end
s1-rpcPub([Public aRPC])
s1-rpcPub --> Supervisor1
Supervisor1 --> RemoteWorker1
Supervisor1[Supervisor]
RemoteWorker1 -- aRPC --> w1-rpcPriv
Supervisor1 -- fork --> Worker1
Supervisor1 --> RemoteWorker2
RemoteWorker2 -- aRPC --> w2-rpcPriv
Supervisor1 -- fork --> Worker2
Supervisor1 --> RemoteWorker3
RemoteWorker3 -- aRPC --> w3-rpcPriv
Supervisor1 -- fork --> Worker3
end
c2-RemoteWorker -- aRPC --> w2-rpcPub
c2-RemoteSupervisor -- aRPC --> s1-rpcPub
More info in /pkg/node.
feat(states): add pkg/states/pipes
PR: #139 (@pancsta)
State piping made it in earlier then expected. Pipe state from one machine to another, possibly as 2 different states. It’s all transition handlers at the end, its easy to write you own and packages offer predefined one.
import (
am "github.com/pancsta/asyncmachine-go/pkg/machine"
ampipe "github.com/pancsta/asyncmachine-go/pkg/rpc/states/pipes"
)
// ...
ampipe.BindReady(rpcClient.Mach, myMach, "RpcReady", "")
// ...
var source *am.Machine
var target *am.Machine
h := &struct {
ReadyState am.HandlerFinal
ReadyEnd am.HandlerFinal
}{
ReadyState: Add1(source, target, "Ready", "RpcReady"),
ReadyEnd: Remove1(source, target, "Ready", "RpcReady"),
}
source.BindHandlers(h)
More info in /pkg/states and the dedicated example.
feat(machine): reuse state contexts
PR: #138 (@pancsta)
Although optimizations are out of scope for current releases this one had made it in, as the new log reader was getting polluted with duplicated contexts.
feat(machine): topological sort for Require
PR: #137 (@pancsta)
Async DAG have been possible since forever, but sync ones (within the same transition) not so much. Simple topo sort gets it done, which can be checked in /examples/dag_dependency_graph.
feat: add typesafe mutation args
PR: #136 (@pancsta)
String params with any dont scale at all, but are a great baseline. New convention of ParseArgs & Pass (adopted in every /pkg/*) makes it safe and sound locally and over encoders. There’s a dedicated chapter in the manual and a sample below:
func (s *Supervisor) ForkingWorkerState(e *am.Event) {
args := ParseArgs(e.Args)
b := args.Bootstrap
argsOut := &A{Bootstrap: b}
// ...
// err
if err != nil {
AddErrWorker(s.Mach, err, Pass(argsOut))
return
}
// ...
// next
s.Mach.Add1(ssS.AwaitingWorker, Pass(argsOut))
}
Closes #24.
feat(machine): add info to state ctxs
PR: #135 (@pancsta)
Contexts can be checked for why they’ve been created using pkg/machine#CtxKey:
type CtxValue struct {
Id string
State string
Tick uint64
}
feat(machine): add WillBe, WillBeRemoved
PR: #134 (@pancsta)
Dead simple queue traversal, eg if mach.WillBe1("Foo") {....
- WillBe
- WillBe1
- WillBeRemoved
- WillBeRemoved1
feat: add schema file v2 format
PR: #133 (@pancsta)
New states file format supports inheritance (embedding) and reduces redundancy, while still being fully compatible with the previous one, as the pkg/machine API hasnt changed. am-gen can now also generate more details on its own. More info in /tools/cmd/am-gen/ and an example file below:
package states
import (
am "github.com/pancsta/asyncmachine-go/pkg/machine"
ss "github.com/pancsta/asyncmachine-go/pkg/states"
)
// MyMachStatesDef contains all the states of the MyMach state machine.
type MyMachStatesDef struct {
*am.StatesBase
State1 string
State2 string
// inherit from BasicStatesDef
*ss.BasicStatesDef
// inherit from ConnectedStatesDef
*ss.ConnectedStatesDef
}
// MyMachGroupsDef contains all the state groups MyMach state machine.
type MyMachGroupsDef struct {
*ss.ConnectedGroupsDef
Group1 S
Group2 S
}
// MyMachStruct represents all relations and properties of MyMachStates.
var MyMachStruct = StructMerge(
// inherit from BasicStruct
ss.BasicStruct,
// inherit from ConnectedStruct
ss.ConnectedStruct,
am.Struct{
ssM.State1: {},
ssM.State2: {
Multi: true,
},
})
// EXPORTS AND GROUPS
var (
ssM = am.NewStates(MyMachStatesDef{})
sgM = am.NewStateGroups(MyMachGroupsDef{
Group1: S{},
Group2: S{},
}, ss.ConnectedGroups)
// MyMachStates contains all the states for the MyMach machine.
MyMachStates = ssM
// MyMachGroups contains all the state groups for the MyMach machine.
MyMachGroups = sgM
)
v0.7.1 (2024-11-13)
fix(machine): fix when-methods index deletions
PR: #132 (@pancsta)
No PR description provided.
fix(machine): dont copy parent tracers in newCommon
PR: #131 (@pancsta)
No PR description provided.
fix(machine): dont block on handler timeout
PR: #130 (@pancsta)
No PR description provided.
fix: add post-v0.7 fixes
PR: #129 (@pancsta)
- rpc closed conn
- debugger worker race
- docs fixes
v0.7.0 (2024-09-14)
Release highlights:
pkg/rpc#117- error handling #111 #119
- monorepo: readme files got split into pkgs
See breaking changes for migration details.
chore: update changelog to v0.7
PR: #128 (@pancsta)
No PR description provided.
docs: update readmes, manual, cookbook for v0.7
PR: #127 (@pancsta)
No PR description provided.
feat: release v0.7
PR: #126 (@pancsta)
All the remainder of v0.7 is here, mostly the stuff below:
- amd-dbg: remote integration tests
- amd-dbg: fix -race
- amd-dbg: add matrix rain
- implement refacs from v0.7
- am-gen: add new funcs
- config: add env vars
- cleanups
- ci: test -race and -p 1
refac(telemetry): switch telemetry to Tracer API
PR: #125 (@pancsta)
Channel based telemetry wasn’t reliable, especially with 2 consumers. Tracer API is synchronous and deterministic. Further optimizations and thread-safe attach/detach needed.
fix(machine): fix test -race
PR: #124 (@pancsta)
With the current amount of tests -race was catching magnitude of cases. Atomics and locks, especially in queue and dispose logic got the job done.
feat(machine): add Index() and Time.Is(index)
PR: #123 (@pancsta)
Operating on state indexes is very widespread in the codebase now and having checking methods directly on the Time struct makes asserting prev/next state in transition handlers short and concise.
Example from rpc#Client:
func (c *Client) StartEnd(e *am.Event) {
// gather state from before the transition
before := e.Transition().TimeBefore
mach := e.Machine
wasConn := before.Is1(mach.Index(ss.Connecting)) ||
before.Is1(mach.Index(ss.Connected))
// graceful disconnect
if wasConn {
c.Mach.Add1(ss.Disconnecting, nil)
}
}
Theres helpers for lists of indexes.
feat(machine): add Eval() detection tool
PR: #122 (@pancsta)
Seems like Eval() is here to stay, but it deadlocks when used synchronously in a handler. AM_DETECT_EVAL=1 works like test -race and will panic with a stack trace for such calls.
func (c *Client) StartEnd(e *am.Event) {
// panic
c.Mach.Eval("StartEnd", func(){
// ...
}, ctx)
}
feat(machine): add EnvLogLevel
PR: #121 (@pancsta)
For quicker logging, am.EnvLogLevel("") returns am.LogLevel from eg AM_LOG=3.
mach := am.New(nil, am.Struct{
"Foo": {Requires: "Bar"},
"Bar": {},
}, &am.Opts{
LogLevel: am.EnvLogLevel(""),
})
feat(rpc): add grpc benchmark
PR: #120 (@pancsta)
feat(machine): add PanicToErr, PanicToErrState
PR: #119 (@pancsta)
Catching panics in goroutines into the Exception state or a user defined one. Supports am.A args, to mark the source location.
func rpcAccept(l net.Listener, mach *am.Machine) {
defer mach.PanicToErr(nil)
// ...
}
feat(helpers): add pkg/helpers (Add1Block, Add1AsyncBlock, …)
PR: #118 (@pancsta)
Many people may look for synchronous wrappers of async state machine calls. These assume a single, blocking scenario which is controller with the passed context.
Example - add state StateNameSelected and wait until it becomes active
res := amh.Add1Block(ctx, mach, ss.StateNameSelected, am.A{"state": state})
print(mach.Is1(ss.StateNameSelected)) // true
print(res) // am.Executed or am.Canceled, never am.Queued
feat(rpc): add pkg/rpc
PR: #117 (@pancsta)
This is the highlight of the v0.7 release - transparent RCP, where most of the methods execute locally. WhenArgs is still missing, but rpc is already hard at work executing remote integration tests.
// init
c, err := NewClient(ctx, addr, "clientid", ss.States, ss.Names)
if err != nil {
panic(err)
}
// start
c.Start()
<-c.Mach.When1("Ready", nil)
// use the remote worker
c.Worker.Add1("Foo", nil)
<-c.Worker.When1("Bar", nil)
print("Server added Bar")
feat(states): add pkg/states
PR: #116 (@pancsta)
These are just first bits of what one day may develop to a repo/hub of async APIs.
feat(machine): add state def manipulations (eg StateAdd)
PR: #115 (@pancsta)
To allow state definitions to be re-used, new modification methods are available for both state lists and state structures.
// Add a dependency on Connected to HandshakeDone.
HandshakeDone: am.StateAdd(ss.States[ss.HandshakeDone], am.State{
Require: S{Connected},
}),
feat(machine): add new Tracer methods (eg VerifyStates)
PR: #114 (@pancsta)
All the previous “dynamic events” are now exposed as Tracer API methods.
feat(rpc): add rpc tests, including remote machine suite
PR: #113 (@pancsta)
Tests for RPC measure traffic as assertions and run the same test suite as pkg/machine, but over the network.
feat(machine): add SetLoggerSimple, SetLoggerEmpty
PR: #112 (@pancsta)
To support more logging scenarios, these tiny helpers come in handy.
// test log with the minimal log level
mach.SetLoggerSimple(t.Logf, am.LogChanges)
feat(machine): add AddErrState and unified stack traces
PR: #111 (@pancsta)
Error handing is finally a thing, as pkg/rpc uses sentinel errors to trigger error states. Stack traces are nicely trimmed and logged.
func (h *handlers) ExceptionState(e *am.Event) {
// call super
h.ExceptionHandler.ExceptionState(e)
mach := e.Machine
err := e.Args["err"].(error)
// handle sentinel errors to states
if errors.Is(err, ErrNetwork) || errors.Is(err, ErrNetworkTimeout) {
mach.Add1(ss.ErrNetwork, nil)
} else if errors.Is(err, ErrInvalidParams) {
mach.Add1(ss.ErrRpc, nil)
} else if errors.Is(err, ErrInvalidResp) {
mach.Add1(ss.ErrRpc, nil)
} else if errors.Is(err, ErrRpc) {
mach.Add1(ss.ErrRpc, nil)
}
}
v0.6.5 (2024-09-11)
fix(am-dbg): correct timeline tailing
PR: #110 (@pancsta)
No PR description provided.
fix(am-dbg): escape secondary logtxt brackets
PR: #109 (@pancsta)
No PR description provided.
fix(am-dbg): fix filtering in TailMode
PR: #108 (@pancsta)
No PR description provided.
fix(am-dbg): stop playing on timeline jumps
PR: #107 (@pancsta)
No PR description provided.
fix(am-dbg): fix changing log level removed trailing tx
PR: #106 (@pancsta)
No PR description provided.
fix(am-dbg): allow state jump after search as type #100
PR: #105 (@pancsta)
Closes #100.
fix(am-dbg): align tree rel lines
PR: #104 (@pancsta)
No PR description provided.
fix(am-dbg): fix tree highlights for ref links
PR: #103 (@pancsta)
No PR description provided.
v0.6.4 (2024-07-28)
test(am-dbg): add TUI integration tests
PR: #97 (@pancsta)
First TUI integration tests have landed and work well in parallel. Manipulating the machine directly makes reproducing scenarios fairly easy, especially with helpers.
feat(machine): add export / import
PR: #96 (@pancsta)
Machine can be restarted on another host and continue previous work, eg in a lambda or bacalhau worker.
Code creating both machines has to be identical on both hosts.
feat(am-dbg): add ssh server
PR: #95 (@pancsta)
am-dbg over ssh is useful for sharing existing data dumps in one line, usually from edge servers and local machines.
am-dbg-ssh --import-data dump.gob.brssh localhost:4444 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
Additional new binary archive dist contains both versions.
feat(am-dbg): render guidelines in tree relations
PR: #94 (@pancsta)
It’s now easier to eye-locate source/target states of the relation, especially in bigger trees. Green is start and red is end.
Code is terrible and needs to be re-written (surprising it even works).
refac(am-dbg): refac cli apis
PR: #93 (@pancsta)
More lines to make the debugger instances IoC manageable.
feat(am-dbg): switch compression to brotli
PR: #92 (@pancsta)
github.com/andybalholm/brotli seems to be better maintained compared to parallel bz2 pkg used till now. Additionally, fast read times will help when starting up the debugger.
feat(am-dbg): add Start and Dispose methods
PR: #91 (@pancsta)
Dispose is desperately needed for multi-instance scenarios.
feat(helpers): add 5 helper funcs, eg Add1Sync, EnvLogLevel
PR: #90 (@pancsta)
New helpers:
- RelationsMatrix
- TransitionMatrix
- EnvLogLevel
- Add1Sync
- Add1MultiSync
Most important is Add1*Sync, which is a syntax sugar for old school imperative calls.
v0.6.3 (2024-07-16)
fix(am-dbg): make LogUserScrolled pause the timeline
PR: #89 (@pancsta)
LogUserScrolled was missing some sane relations with Playing and TailMode. Additionally, scrolling logic needed unification.
Now, manually scrolling the log will keep it there and cause Paused.
feat(machine): retain log level for pre-logs
PR: #88 (@pancsta)
Because pre-logs (logs created in-between transitions) were all LogChanges level, it was impossible to filter out higher log level entries in the debugger.
Now:
[extern]isLogChanges- internal entries have their level attached
v0.6.2 (2024-07-15)
fix: fix dispose crash, misc am-dbg issues
PR: #87 (@pancsta)
Fix dispose crash with timer being nil.
Minor am-dbg fixes:
- filters without a client
- steps timeline blinking
- tail order
- max clients 500
v0.6.1 (2024-07-12)
fix(am-dbg): fix tail mode with filters
PR: #85 (@pancsta)
New msgs weren’t added to the filtered index, thus Tail Mode didnt follow.
v0.6.0 (2024-07-10)
fix: address misc issues
PR: #84 (@pancsta)
- typos
- conventions
- task am-dbg
- missing when dispose
- missing handler timeouts
- history using no-op tracer
docs: add pdf manual
PR: #83 (@pancsta)
task gen-manual-pdf will generate assets/manual.pdf, in case someone needs to mark all these errors with a pencil.
docs: minor manual updates
PR: #82 (@pancsta)
Mostly cosmetic changes with some chapters still missing.
refac(am-dbg): split and reorg files
PR: #81 (@pancsta)
This is a changelog-only placeholder PR for an unsplit commit.
am-dbg has 4 new files, as code gets growing:
- keyboard.go
- log.go
- rpc.go
- ui.go
feat(telemetry): include log level in msgs
PR: #80 (@pancsta)
This is a changelog-only placeholder PR for an unsplit commit.
Telemetry now exports log levels along with each tx msg, instead of initially with the machine’s struct (which was never getting updated).
feat(am-dbg): add tx and log filtering
PR: #79 (@pancsta)
- add filters bar
- include log level in msgs
- split and reorg files
There’s still performance issues when loading a larger log on level > 1 and the flow needs to be split in chunks via an async state. Transitions seem handing it very well, with an minor issue of duplicated timestamps only.
feat(machine): add global AnyAny negotiation handler
PR: #78 (@pancsta)
AnyAnyalways executes- as the last negotiation handler
- can cancel the transition
func (h *Handlers) AnyAny(e *am.Event) bool {
return true
}
refac(machine): extract When* ctx disposal
PR: #77 (@pancsta)
Most of the “when” methods share similar logic and data structures, and should be unified, with possibility of new ones coming in the future.
refac(machine): refac to directional channs
PR: #76 (@pancsta)
Just to make sure no one tries write to “when” channels…
refac(machine): reorder Eval params
PR: #75 (@pancsta)
Reorder of Eval params for readability.
Before:
s.Mach.Eval(func() {
t, ok := s.topics[topic]
if !ok {
s.Mach.Log("Error: topic not found", topic)
return
}
}, nil, "topicPeerChange")
After:
s.Mach.Eval("topicPeerChange", func() {
t, ok := s.topics[topic]
if !ok {
s.Mach.Log("Error: topic not found", topic)
return
}
}, nil)
feat(machine): add NoOpTracer for embedding
PR: #74 (@pancsta)
Avoids breaking tracers when new methods are added.
type MyTracer struct {
*am.NoOpTracer
}
feat(machine): add Tracer.QueueEnd
PR: #73 (@pancsta)
Minor feature for Tracer parity with OnEvent, so the latter doesnt have to be used.
test(machine): increase coverage to 85%
PR: #72 (@pancsta)
Although scenario coverage was very high, actual code had many gaps and this is the first iteration addressing it.
refac(machine): make WhenDisposed a method
PR: #71 (@pancsta)
To unify “when” methods, after disposal can be waited for with <-mach.WhenDisposed()
v0.5.1 (2024-07-10)
fix(machine): fix Dispose() deadlock
PR: #70 (@pancsta)
Steps:
task demo-run- hit
CTRL+C - deadlocks instead of exiting the process
This isnt a full fix and needs test coverage.
fix(machine): allow for nil ctx
PR: #69 (@pancsta)
nil ctx is useful for examples and snippets.
fix(am-dbg): fix tail mode delay
PR: #68 (@pancsta)
After alt+v, only a new network msg would move to the last tx.
fix(am-dbg): fix crash for machs with log level 0
PR: #67 (@pancsta)
Machines without any log msgs break debugger’s log view.
feat(machine): add WhenQueueEnds
PR: #65 (@pancsta)
Machine.WhenQueueEnds is useful to make sure a mutation method wont return Queued. Rarely used, usually inside handlers. Should be avoided.
docs: update manual to v0.5.0
PR: #64 (@pancsta)
Much needed refresh of the initial old draft, towards a more spec-like format. Some smaller chapters are still missing and marked as TODO.
v0.5.0 (2024-06-06)
feat: add tools/cmd/am-gen
PR: #63 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
am-gen generates typesafe states files from a list of stats names. It’s highly recommended to use it.
feat(am-dbg): add --select-connected and --clean-on-connect
PR: #62 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
New CLI params --select-connected and --clean-on-connect are here to help with long-running debugging sessions.
feat(am-dbg): add search as you type (clients, tree)
PR: #61 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
To keep up with the increasing number of clients and states, there’s a basic search-as-you-type with a 1.5s buffer.
feat(am-dbg): add matrix view
PR: #60 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
For no apparent reasons am-dbg now has a “matrix” view under alt+m. It’s a nice way to see what’s happening under the hood, that includes stepping through transition steps.
feat(am-dbg): optimize UI processing
PR: #59 (@pancsta)
This is a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
UI got some most basic optimizations (mostly throttling of inputs) to accommodate live data coming from multiple clients.
feat(am-dbg): add tree relations UI
PR: #58 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Representing cyclic graphs on top of a tree structure was the only way to show relations resolving process. Combined with moving touched states to the top of the tree, it’s now very easy to understand the details about each transition’s relations.
feat(am-dbg): add import/export
PR: #57 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
am-dbg can now export and import compresses encoding/gob files using a dedicated shortcut and --import-data cli param. There’s also --select-machine and --select-transition to CLI params, useful for going to a specific place in the past.
feat(am-dbg): add multi client support
PR: #56 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
am-dbg now uses cmux to handle multiple clients on the same port, along with a sorted sidebar and ability to remove chosen ones. For now the limit is 150, but till will definitely be increased in the future.
feat(machine): add empty roadmap methods
PR: #55 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Stubs for methods like CanAdd1 are already in-place to minimize API changes.
feat(machine): add Eval
PR: #54 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
To avoid overhead for heavily called states (like IncomingRPC), the Machine.Eval method does the required minimum to schedule execution on the event loop, without creating a full transition. It will return a bool to confirm execution and time out like other handlers.
refac(machine): rename many identifiers, shorten
PR: #53 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Many method names, enums and functions have been renamed to be more descriptive and shorter.
feat(machine): drop all dependencies (lo, uuid)
PR: #52 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
asyncmachine-go is now dependency free! Of course the standard library is still heavily used and also there’s only a single go.mod for the whole monorepo (which should change in the future).
feat(machine): alloc handler goroutine on demand
PR: #51 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Only after the handler are bound with Machine.BindHandlers the handler goroutine will be forked. This comes with an overhaul of handler calling mechanism, which previously have been allocating goroutines while calling handlers.
This change opens doors to composing multiple machines using the parent’s queue and handlers, meaning 1+N machines would allocate a single goroutine only.
feat(machine): add Transition.ClocksAfter
PR: #50 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Transitions now expose clock values form after the transition, which complement Transitino.ClocksBefore to give a machine time diff.
feat(machine): add HasStateChangedSince
PR: #49 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Machine.HasStateChangedSince is yet another function to work with the machine time, accepting a return from Machine.Clocks.
feat: add pkg/x/helpers
PR: #48 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Extra and experimental helpers are kept here, with NestedState being the most interesting, as it’s the first step to compose network of machines. There’s also some time and state-names helpers.
feat: add pkg/telemetry/prometheus
PR: #46 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Extension of the telemetry package, kept separately because of a forced goroutine allocation, exports vital machine’s metrics to Prometheus.
See pkg/telemetry/prometheus for more info.
feat: add pkg/history
PR: #45 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
pkg/history is a completely new package, in its early stage, offering basic history recording and querying. Useful for metrics, rate limiting and also negotiation (based on history).
fix(machine): add funcs SMerge, NormalizeID, IsActiveTick, CloneStates
PR: #44 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
New basic helpers, with SMerge (states merge) being the most commonly used.
Playing: {
Require: S{ClientSelected},
Remove: am.SMerge(GroupPlaying, S{LogUserScrolled}),
},
fix(machine): fix thread safety
PR: #43 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Various thread safety issues have been fixed and none is known at the moment.
feat(machine): add Tracer API and Opts.Tracers
PR: #42 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
A new Tracer API is available as a faster an alternative to OnEvent. Currently used by the Open Telemetry integration, will become the default in the future.
feat(machine): add SetStruct, EventStructChange
PR: #41 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Machine.SetStruct allows to change the registered states and their relations. It’s mostly useful right after creating a machine, when adding new dynamic states, right after the first mutation.
feat(machine): add getters (ActiveStates, Queue, Struct)
PR: #40 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
To thread-safe machine’s most important data, there’s 3 new getters now ActiveStates(), Queue() and GetStruct().
feat(machine): add single-state shorthands (Add1, Has1, etc)
PR: #39 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
All the mutation methods now have a single-state shorthand to save 6 chars per call. These are also the most common types of calls.
// old
mach.Add(am.S{"DownloadingFile"}, nil)
// new
mach.Add1("DownloadingFile", nil)
feat(machine): add Switch(states…)
PR: #38 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Combined with state groups, Machine.Switch comes in handy to replace multiple Is calls.
switch mach.Switch(ss.GroupPlaying...) {
case ss.Playing:
case ss.TailMode:
default:
}
feat(machine): add Opts.Parent
PR: #37 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
To ensure propagation of debug settings (like tracers), there’s a new option Opts.Parent.
feat(machine): add Opts.LogArgs, NewArgsMapper
PR: #36 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Arguments can now be logged in am-dbg and Otel traces, either using a custom mapper or the predefined helper.
mach.SetLogArgs(am.NewArgsMapper(
[]string{"Machine.id", "conn_id"},
20))
feat(machine): add Opts.QueueLimit
PR: #35 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
The first way to limit the queue has landed as Opts.QueueLimit.
feat(machine): add WhenDisposed, RegisterDisposalHandler
PR: #34 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Both Machine.WhenDisposed and Machine.RegisterDisposalHandler are meant to handle an async disposal of a machine. Alternatively, one can use Machine.DisposeForce.
// wait for everything to finish
s.Mach.Remove1(ss.Start, nil)
s.Mach.Dispose()
<-s.Mach.WhenDisposed
feat(machine): add WhenArgs
PR: #33 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Machine.WhenArgs waits until a state gets activated with specified arguments.
// define args
args := am.A{"id": 123}
// crate a wait channel
when := mach.WhenArgs("EventConnected", args, nil)
// wait with err and timeout
select {
case <-time.After(5 * time.Second):
return am.ErrTimeout
case <-mach.WhenErr(nil):
return mach.Err
case <-when:
// EventConnected activated with (id=1232)
}
feat(machine): add WhenTime, WhenTicks
PR: #32 (@pancsta)
This ia a changelog-only placeholder PR, due to the bulk of changes in v0.5.0.
Machine.WhenTime waits until the machine time has been reached. It can be combined with Machine.Time() as the reference source, indexed by Machine.StateNames.
// crate a wait channel
when := mach.WhenTime(am.S{"DownloadingFile"}, am.T{6}, nil)
// wait with err and timeout
select {
case <-time.After(5 * time.Second):
return am.ErrTimeout
case <-mach.WhenErr(nil):
return mach.Err
case <-when:
// DownloadingFile has tick >= 6
}
v0.3.1 (2024-03-04)
feat: add version param
PR: #23 (@pancsta)
No PR description provided.
feat: complete TUI debugger iteration 3
PR: #22 (@pancsta)
Changes:
- refactor
- cli params
- UX details
- help screen
- aligned readme
- aligned Taskfile
feat: TUI debugger iteration 2
PR: #21 (@pancsta)
Changes:
- jump by state
- ordered state tree
- log level
- queue counter
- multiline key bar
- many bug fixes
feat: add TUI debugger
PR: #20 (@pancsta)
Initial version of the TUI debugger, consuming telemetry data.

Config:
AM_DEBUG_URLmakes the debugger connect as client to another instanceAM_DEBUG_SERVER_URLlisten on a different address thanlocalhost:9823AM_DEBUG_LOGlog filename, defaults tolog.txt
Usage ATM:
go run tools/debugger/debugger.go tools/debugger/rpc.go
feat: add telemetry via net/rpc
PR: #19 (@pancsta)
Initial telemetry implementation, needed for the upcoming debugger.
Changes:
- telemetry via
net/rpc - 2 types of msg
- initial (full)
- updates (refs and logs)
- add transaction IDs
- remove step IDs
Usage:
import (
"github.com/pancsta/asyncmachine-go/pkg/telemetry"
)
// ...
err := telemetry.MonitorTransitions(mach, url)
feat: add support for state groups for the Remove relation
PR: #17 (@pancsta)
Ability to define a mutually exclusive state group and using it for the Remove relation for all the states from that group.
Reduces repetition, increases readability.
var GroupPlaying = am.S{
Playing, Paused, LiveView,
}
var States = am.States{
Playing: {
Remove: GroupPlaying,
},
Paused: {
Remove: GroupPlaying,
},
LiveView: {
Remove: GroupPlaying,
},
fix: add more locks
PR: #16 (@pancsta)
More thread safety bits.
feat: prevent empty remove mutations
PR: #15 (@pancsta)
Remove mutation will only be queued if the state to be removed is currently active. This prevents from flooding the queue with empty requests, which is common.
feat: add VerifyStates for early state names assert
PR: #14 (@pancsta)
Strongly typed states names can be achived as an enum in a dedicated package.
The list can be passed to the new VerifyStates method immediately after creating a machine, which will guarantee no later panic, when a name hasn’t been registered.
Definition:
package states
import am "github.com/pancsta/asyncmachine-go/pkg/machine"
// enum of all the state names
const (
TreeFocused string = "TreeFocused"
LogFocused string = "LogFocused"
)
var Names = am.S{TreeFocused, LogFocused}
var States = am.States{
TreeFocused: {},
LogFocused: {},
}
Usage:
err = mach.VerifyStates(ss.Names)
docs: add debugger readme img
PR: #13 (@pancsta)
No PR description provided.
docs: add ToC, cheatsheet
PR: #12 (@pancsta)
No PR description provided.
docs: align with the v0.2.0 release
PR: #11 (@pancsta)
No PR description provided.
v0.2.1 (2024-01-18)
fix: prevent double handlerDone notif
PR: #10 (@pancsta)
Fixes https://play.golang.com/p/JxcQ-in4cCk
chore: release v0.2.0
PR: #9 (@pancsta)
No PR description provided.
v0.2.0 (2024-01-18)
chore: fix readme playground links
PR: #8 (@pancsta)
- simplified deps
- shorter timeouts
- switched to play.golang.com
feat: synchronous emitter bindings
PR: #7 (@pancsta)
Fixes #2 .
Changes:
- synchronous
BindHandlers(breaking API change) - synchronous
On - atomic queue lock (fixes a race)
fix: synchronous GetStateCtx, When, WhenNot
PR: #6 (@pancsta)
Fixes #3.
Changes:
GetStateCtxwithout usingemitterWhen,WhenNotwithout usingemitter

