Announcing SwiftXState - Actor-based State Management & Orchestration Library

Announcing SwiftXState

Actor-based State Management & Orchestration Library

Compatible with Stately.ai's awesome XState JS library, which in turn is inspired by State Chart XML.

Key features of SwiftXState:

  • Core runs on any Swift platform
  • SwiftUI and SwiftData adapters
  • included 2D/3D GPU-accelerated live state chart inspector
  • streaming HTTP JSON event and state snapshot output
  • included JSON-to-state-machine sample app
  • included SwiftXChess demonstrator app
  • check out the Github readme for more


2 Likes

Why reinvent actors in the language that have actors?

Different things entirely. Just has the same name to match the XState web API. I considered renaming them agent for the Swift API since I figured people would ask about this, and hey it's software so we could still refactor it maybe if it's a problem for anyone.

But the README on the repo explains it (pasted the relevant section below).

The below partial diagram of the SwiftXChess sample app shows the role of a Swift actor as a network handler in our system, in which InspectBridgeState (magenta dashed box) is a Swift concurrency actor specifically because it's the serial interface to the network when we stream JSON events and state snapshots over the websocket for live visualization/debugging/logging etc.

Concurrency: SwiftXState Actor vs Swift actor

This distinction matters everywhere in the docs and samples — including SwiftXChess.

What is a SwiftXState Actor?

SwiftXState.Actor is a reference-type interpreter (final class) that runs a StateMachine configuration. It:

  • Owns a mailbox of events processed on an internal serial DispatchQueue
  • Maintains a MachineSnapshot (value, context, tags, children, history)
  • Spawns and supervises child actors via invoke / spawnChild
  • Schedules delayed transitions and raises via a pluggable Clock
  • Emits inspection events compatible with Stately Inspector
let actor = createActor(machine).start()
actor.send(Event("SUBMIT"))
let snap = actor.snapshot

Naming matches XState v5's createActor on purpose. A SwiftXState Actor is the unit of behavior — the running state machine process.

What is a Swift actor?

A Swift actor is a language-level concurrency primitive: the compiler enforces isolated mutable state, and callers must await cross-actor access. It is unrelated to the XState actor model except in name.

actor SessionStore {
    var sessions: [String: ReplaySession] = [:]
    func save(_ session: ReplaySession) { sessions[session.id] = session }
}

How SwiftXState uses Swift concurrency today

SwiftXState does not implement the interpreter as a Swift actor. Instead, it uses Swift concurrency inside child actor logic:

XState concept SwiftXState API Concurrency
fromPromise fromTask async throws child runs in a Task
fromCallback fromCallback receive, sendBack, emit; sync setup + dispose cleanup
fromTaskGroup withThrowingTaskGroup for parallel child work
Observable fromObservable Subscribable + async delivery

The parent Actor stays on its serial queue. Async children report back via sendToParent, DoneActorEvent, ErrorActorEvent, and optional onSnapshot sync. That keeps transition logic deterministic while still embracing async/await for I/O.

Cancellation, cleanup, and restore policy

Task and task-group children are wrapped in withTaskCancellationHandler. Pass an onCancel closure to fromTask / fromTaskGroup to flush SwiftData writes, delete partial batches, or checkpoint before the child tears down:

fromTask(onCancel: { scope in
    await store.deletePendingBatch(scope.input?.get(String.self))
}) { scope in
    for job in jobs {
        try scope.checkCancellation()  // or: if scope.isCancelled { return }
        await store.write(job)
    }
    return jobs.count
}

TaskActorScope and TaskGroupScope also expose isCancelled, checkCancellation(), and withCancellationHandler for nested cleanup inside long operations.

When hydrating from a persisted snapshot, opaque invokes default to restart (fresh child). Set opaqueRestorePolicy on InvokeConfig or SpawnRef to defer auto-spawn until your entry logic reconciles external stores:

Policy Behavior on start(from:)
.restart (default) Spawn a new task/callback/taskGroup child
.skipIfActive Skip spawn if persisted opaque child was .active
.skipIfPresent Skip spawn whenever any opaque child snapshot exists

Pair .skipIfActive with entry actions that read SwiftData, clear or resume partial work, then transition or manually re-invoke.

Anticipated role of Swift actor in SwiftXState apps

Swift actor is a complementary tool, not a replacement for SwiftXState.Actor:

  1. App shell and bridges — A Swift actor can own UI-adjacent or cross-boundary state (network clients, BLE, Core Data facades, C++ game engines bridged through Swift) and send Eventable values into a SwiftXState Actor from fromCallback or fromTask children.

  2. Persistence and replay servicesReplayPersistenceStore-style services may be modeled as Swift actors to serialize disk access while the machine interpreter handles domain transitions.

  3. Future interpreter isolation — We may offer an optional Swift-actor-backed mailbox implementation behind the same Actor API for apps that want compiler-checked isolation instead of DispatchQueue. The public XState-shaped API would remain stable.

  4. Not planned: renaming Actor — Consistency with XState and Stately Inspector outweighs avoiding the keyword collision. Documentation and type context (SwiftXState.Actor) make the distinction clear.

Rule of thumb: use SwiftXState.Actor for orchestration and statecharts; use Swift actor for resource ownership and async isolation at the edges; connect them with invoke / fromTask / fromCallback.


1 Like

This is some llm generated response? I would propose to push back—you can build same stuff with Swift actors. Purpose of XState actors is that there is no actors in JS.
Also I see lots of ‘@unchecked Sendable’ in situations where it could be just ‘@Sendable’, which usually LLMs love to do—I’m not against them (and could be wrong in assumption), but this could be improved by looking at code at a first glance.

Certainly, but the question is, what would the benefit be of using a Swift actor over a class for that specific role?

Try not to get hung up on the word "actor" and look at what the library does. In the meantime I'm going to consider renaming it to avoid confusion.

Actor in Swift is a concurrency concept, whereas in XState lingo it's a role within the ecosystem of orchestrating state machines. It's an orthogonal concept to Swift's "actor" and is unrelated to JavaScript's lack of an equivalent.

We did consider making XState Actor a Swift actor but the typical job XState actors did not seem like it would benefit from isolation.

As to the issue you raise about sendable, it's beta version 0.9 for a reason. There is a lot of cleanup to do before it's ready for production. I hope you try the iPad/Mac chess app included there and see how this all works. Any other feedback welcomed.

But that is important—actor is well-defined term and library actor is doing basically the same stuff as all actors do: mailbox and state isolation. Same as in Swift, Erlang, Akka, Orleans and etc., difference being implementation details, and that Swift and Erlang have it as a language primitive.

Maybe I'm still missing something, but what I'm trying to say is I think:

This all could be encapsulated into what the language provides—just create a supervisor actor who handles and stores state and child actors.

Yeah, for sure! Another small comment—Swift have Mutex which are better to use instead of NSLock.

I think it's totally fine if some library has well established terms "Actor" -- scenes and game development libraries often use the word actor and there's no need to be too protective about it.

I will say though it is very hard to understand what this library is even about if the replies are just a generated LLM wall of text; it would be nice if you could use your own words to explain what the library is about and helps to achieve.

2 Likes

We explored that, but actor makes everything async, and the Actor in XState very specifically plays a synchronous role in the machinery. The statechart runtime has to be synchronous and run-to-completion. This determinism is kinda the whole point of it, it allows precise millisecond timing delays on transitions (not leaving it up to the schedule to interleave reentry).

The cost of having to stay synced up like gears in a big machine is also having to declare @unchecked in the 3.7% of places where we had to manually handle guarantees about what happens with state at runtime. For example that's why you see a serial queue on Actor. Other than those few places (35 out of 953 or something) I felt like we stuck to compiler sendable guarantees as close as possible.

That being said, it's open source so if you want to pull it and see if you can make it work with Swift actor then I'd be curious to see how.

One thing I think we can do better is provide better auditability on the unchecked types to better prove they are indeed holding mutable state inside a lock.

Ah, that's another thing and interesting to check!

btw would still insist it's the same concept

It's formatted that way as a part of the documentation page on the github that explains the distinction he was confused on.

As to what it helps you achieve, it really depends what kind of software you're writing.

But the main idea is about managing the state of your application in a deterministic way, with hooks into the logic of transitions between states that you can inspect and visualise in realtime. You can rewind your app back in time to a prior state. You can see JSON output at any point with the entire state of everything. It is like having XCode debugger and being able to step through the app but you can rewind and then fastforward step by step however many times you want while looking at an interactive diagram of the state of the app.

You can define what state transitions are legal between different states in a typesafe manner, so you get guarantees about what the user can do and cannot do in given circumstances. You can kick off background tasks in groups or solo as a trigger off some state activating.

There is a SwiftData store for a backing store and hooks into SwiftUI.

I made this due to frustrations I had with the limitations of Redux architecture. XState lets you break up your sources of truth in a more structured way and distribute your reducer rules as visualizable state transition hierarchies. As a visual/right brained person it's more up my ally.

Also, it's roadmapped for compatibility with SCXML, which is a niche thing used in certain industries. But there is a lack of great modern software for dealing with SCXML systems, so I hope this can help them once we finish our XML parsing engine.

I don't know if this kind of system is for everyone. And it is somewhat of an experiment. William Gibson said "The street will find its own use for technology."

But I think it is a really nice thing to be able to define the state logic in a structured and human readable way, and have a provable way to check if your app follows it. It's also kind if cool to be able to instantiate that logic from a serialized text file at runtime.

There are multiple sample apps in the project to play with BTW.

1 Like

He's just saying that the original idea of the Actor model influenced lots of languages, which is 100% true.

Swift actor applies the Actor concept to structured concurrency.

XState applies Actor concept to state machine orchestration.

It's the same concept applied on two different planes of existence that intersect, but don't overlap (mostly).

Really interesting philosophical discussion about these concepts so thanks. They definitely share those roots.

I am interested to see how people could expand
upon what's in 0.9.0 of SwiftXState—perhaps by adding asynchronous Swift actor Actors. Why not?

Main goal for 0.9 was compatibility/parity with the web version but there is no reason we can't start adding on more stuff that is unique to Swift.

1 Like

Well I'm happy to tell you that, as of version 1.0.0—we have migrated the SwiftXState Actor type to be a custom Swift actor.

It did seem infeasible during initial development, due to the issues I described, but we were able to make it happen. Check it out. GitHub - gistya/SwiftXState: Swift Adaptation of Stately's XState Actor-based State Management & Orchestration Library · GitHub

3 Likes