v4.4.0

Released 2026-06-06 · GitHub release · CHANGELOG

Ships RFC-0043 — async systems gain an explicit @@[async] header and a layered casing / machine codegen architecture with a single-driver gate — plus RFC-0045 (relocating the state-name accessor) and an RFC-0044 kernel context-stack fix. This release has breaking hard-cuts; both are mechanical to fix and Frame is pre-public-beta, so no published program is affected. See the migration guide.

Highlights

  • @@[async] system-header attribute (RFC-0043 — Async systems: layered codegen with a single-driver gate). A system declaring any async member now opts in explicitly with @@[async] on the line above @@system. It may also be used on a sync system that just wants the gate.
  • Layered casing / machine emission across 11 async backends. An async system is emitted as a public casing (your declared name) wrapping a private _<Name>Machine that holds the existing dispatch core. Operations and persist save/load bypass the gate; internal self-calls go straight to the machine. Sync operations on an async system are no longer needlessly coroutinized.
  • E703 single-driver gate. The casing rejects a second external dispatch while one is in flight, surfaced as each language’s idiomatic — and recoverable — error (Rust Err(FrameE703Error), Swift throws, Java failedFuture, GDScript push_error + typed-zero, etc.). Catches the classic “two tasks driving one machine” bug loudly instead of corrupting state.
  • @@:system.state.name is the state-name accessor (RFC-0045 — reserve @@:system.state). Bare @@:system.state is reserved for a future meaning (a direct compartment reference); the current-state name now reads @@:system.state.name. Output is byte-identical to what @@:system.state produced before.
  • Kernel context-stack now unwinds on a handler exception (RFC-0044). A handler that raised mid-dispatch used to leak a stale context-stack entry per failed call; the dispatch wrapper now cleans up via each language’s try/finally idiom on 12 backends.
  • Graphviz: push$ -> $X now draws a forward edge — pushed-to states are no longer rendered unreachable.

Breaking changes

See the migration guide for worked before/after for each.

  • E720 — async members require @@[async] (RFC-0043, hard cut, no grace period). Fix: add the attribute, or run framec project add-async-attr <path>.
  • E721 — a sync system can’t compose an async system as a domain field (same-file). Fix: add @@[async] to the holder, or hold the async child from an async parent.
  • E608 — bare @@:system.state is reserved (RFC-0045). Fix: mechanical @@:system.state@@:system.state.name.
  • Rust casing methods return Result<T, FrameE703Error> (D5). Fix: ?-chain or match the result.
  • Swift casing methods are async throws (D2). Fix: try await sys.method() inside a do { … } catch { … }.

Action required

Most projects need nothing — sources that already carry @@[async] (or declare no async members) and don’t use @@:system.state generate byte-identical output to 4.3.x. If you do use either, the migration is a one-shot codemod plus a find-and-replace:

framec project add-async-attr path/to/source-tree   # inserts @@[async]
# and, across your tree:
#   @@:system.state  →  @@:system.state.name

Table of contents