Experiments
fugue · independent voices, one score · rhymes with “few”

The BEAM's shape.
The CLR's engine.

Elixir proved the shape: millions of cheap stateful processes, let-it-crash, presence for free. Orleans — built for Halo, running Azure-scale for a decade — proved the CLR can do that shape with durability OTP never had. And almost nobody uses it, because it never got its Phoenix. Fugue is that Phoenix, written in G#: grains as processes, scope as supervision, LiveView over SignalR. One binary. One Postgres. Every process immortal — kill the node and the user never knows.

Cart, Room, Doc every stateful thing is a grain · durable · addressable
fugue view-grains render HTML · diff · stream
SignalR diffs over the wire · presence · pubsub
postgres:// grain state · truth · history
The tension nobody resolved

The best actor runtime never met the best actor framework

For a decade the two halves lived on different runtimes. Phoenix and LiveView are the best full-stack story alive — on a dynamically-typed runtime your model writes hesitantly and your CFO has never heard of. Orleans is the most battle-tested virtual-actor system alive — wearing an enterprise C# suit, with no web story, no generators, no taste. Each half spent ten years missing exactly what the other half had:

elixir has

The shape.

Cheap processes, supervision trees, presence and pubsub as birthrights — the actor model as a way of life, not a library. And a small ecosystem on a niche runtime: the shape is right, the gravity is missing.

orleans has

Virtual actors — durable and location-transparent by default.

An Orleans grain never really dies: it persists, reactivates anywhere, and is addressable without knowing where it lives — things raw OTP makes you build yourself. And it has zero full-stack story. It is an engine sold without a car.

phoenix proved

The actor model is a web framework waiting to happen.

LiveView, presence, channels: every hard web problem — realtime, collaboration, state per user — collapses when a process holds the state and the wire carries diffs. Phoenix is the proof that this is not a niche trick. It is the trick.

g# provides

The cultural reset.

scope structured concurrency, Go-ergonomics, Swift-clarity — a fresh surface over the whole CLR with no enterprise expectations attached. Orleans behind a G# face is not "enterprise C#." It is a new thing that happens to be ten years old and bulletproof.

The one-sentence mind-blow: virtual actors mean the process never dies — fugue is Phoenix where every GenServer is immortal, every restart is a reactivation from persisted state, and placement is nobody's job.
How it runs

Everything stateful is a grain. Every view is a grain too.

Orleans' shape, kept exactly; Phoenix's moves, translated faithfully. There is no cache tier, no message broker, no session store, no orchestration layer — the grain directory and the Postgres behind it are the entire topology.

grains — every entity is a process

A Cart, a Document, a GameRoom is a virtual actor: always addressable by identity, activated on demand, single-threaded so it never needs a lock, persisted to Postgres, placed by the runtime across the cluster. You write a class; the runtime decides where it lives and keeps it alive forever.

views — the LiveView move

Each connected user's view is itself a grain: it renders HTML on the server, keeps the socket's state, and streams DOM diffs over SignalR as the grains it watches change. Reconnect is just reactivation — the view-grain remembers exactly where the user was, because it never actually went away.

supervise — scope blocks as trees

G#'s structured concurrency is the supervision model: children cannot outlive their parent scope, so the failure domain is visible in the indentation. A crashed grain is reactivated from its persisted state — let-it-crash without hand-building the tree plumbing.

cluster — add a node, that's the runbook

A new node joins the silo ring; grains rebalance onto it; a lost node's grains reactivate elsewhere from Postgres. No orchestration layer, no broker, no Redis — Postgres is the only externality, and one binary per node is the only artifact.

The stack

What Orleans contributes, what Phoenix taught, what only fugue has

Fugue the framework's own
phoenix : otp :: fugue : orleans View-grains, generators, taste. fugue g live deal scaffolds a grain, its live view, and its route in one move; presence and pubsub are conventions, not packages; the whole framework exists to make the right grain the obvious one to write. Orleans supplied the engine. Fugue is the car.
Voices from orleans
grains · the independent voices Virtual actors: durable, single-threaded, location-transparent, activated on demand and never truly gone. A decade of Azure-scale production hardening arrives as a dependency, not a bet.
Score from g#
scope · unions · supervision as syntax Structured concurrency as the failure model: a scope block joins every async child before returning, so the failure domain is lexical. And payload-bearing enums are the message protocol — a grain's commands are one union, matched with an exhaustive switch, so adding a message the handler forgot is a compile error, not a dead letter. The score keeps the voices in time.
Wire from phoenix, via signalr
channels · presence · html diffs SignalR carries what Phoenix taught the world to carry: rendered-HTML diffs instead of JSON, channels instead of ad-hoc websockets, presence as a first-class primitive. The client is a thin patcher; the app lives on the server, in grains.
Data grain state · relational truth
postgres is the only externality Grain state persists to Postgres on write; EF Core handles the relational queries grains shouldn't do — reports, joins, search. Grains are behavior wrapped around state; Postgres is the state's home and its history.
Substrate clr
one binary per node · one postgres G# compiles straight to CLR assemblies — every BCL type, every NuGet package, every dotnet tool just works. One self-contained binary per node, roles chosen at boot; one Postgres; nothing else on the invoice.
The demo that sells it

Kill the node. The user never knows.

The canonical exchange: on the left, a room and its live view in plain G# — faithful to the shipped tour: a primary-constructor data class, Go-flavored slices, scope joining its children, await for over an async sequence. On the right, what happens when you murder the machine it runs on.

room.gs · a grain and its live viewg#
package App.Rooms

import Fugue.Live
import Gsharp.Extensions.Go   // slices, append

data class Message(user UserId, body string)

class Room : Grain {
    var members = []UserId{}

    func join(user UserId) {
        members = append(members, user)   // single-threaded — no lock
    }
    // fugue derives posts() async sequence[Message] from grain writes
}

async func roomLive(view LiveView, room Room) {
    scope {                                // supervision, as syntax
        await for msg in room.posts() {     // the tour's async streams
            await view.push(view.diff(render(msg)))
        }
    }
}
kill -9
the kill test · one transcriptproduction
$ kill -9 silo-2

silo-2             ✗ gone · heartbeat lost
grains on silo-2     1,204 active

reactivating        silo-1, silo-3 · 380ms
  state loaded       postgres · last persisted turn
liveview sockets    812 reconnected
diff resync         server re-renders · client patches

the user saw: nothing
no session store consulted. no cache warmed.
placement was nobody's job.

This is the demo Elixir people say the CLR can't do. And the part that OTP itself does not give you free — the durability, the placement, the reactivation from persisted state — is exactly the part Orleans has been running at Halo scale for a decade. Fugue doesn't have to prove the hard part. It has to make the hard part pleasant.

The second demo

Immortal in space. Now immortal in time.

The kill test proves a grain survives losing its machine. The flight recorder proves it survives losing the moment: journal a grain's turns instead of only its latest state, and any grain can be reactivated as it was at any instant. And because views are grains too, that includes the screen — you can re-render what a specific user actually saw when the bug happened.

room.gs · one attributeg#
package App.Rooms

@Journaled(Retain: "90d")   // turns, not just state
class Room : Grain {
    // every message this grain handles is a
    // journal entry before it is an effect.
    // state is now a derivation — the fold
    // of the journal — not the only truth.
}

// debugging becomes a query:
// "room 42, at 14:03, through
//  user 71's eyes."
rewind
the flight recorder · one transcriptproduction
$ fugue replay room:42 --at 14:03:07

journal           18,204 turns · postgres
reactivate        room:42 · turn 9,311 · sandboxed
view-grain        user 71's screen, re-rendered
step             ⏵ 9,312 · 9,313 · 9,314 …

the bug: on screen, as it happened
no logs grepped. no repro steps written.
history was already running.

Everything downstream is a byproduct. Audit history is free — the journal is the audit. Incident review is a replay, not a reconstruction. And regression testing gets its sharpest possible form: replay yesterday's production turns against tomorrow's build and diff what the grains did. The kill test and the recorder are the same claim on two axes — kill the node, the user never knows; rewind the clock, the system never forgot.

What we won't pretend

What the fusion costs

Virtual actors are not OTP.

No selective receive, different mailbox semantics, activation latency on the first call to a cold grain. Erlang veterans will find the corners where the metaphor bends — fugue names the differences instead of papering over them.

Orleans and Native AOT are still negotiating.

Orleans leans on codegen and reflection; single-file AOT is not a settled story. Fugue ships self-contained CoreCLR builds first — still one binary, still one artifact — and treats AOT as a destination, not a day-one claim.

One grain, one thread.

Single-threaded grains delete locks, and they cap per-entity throughput. The million-follower Room and the flash-sale Cart need sharding patterns — worker grains, fan-out — that fugue must make conventional and generated, not clever and folkloric.

LiveView took Phoenix years.

Reconnect semantics, diff replay, form recovery, latency masking — these were earned across releases, not designed in an afternoon. Fugue inherits the design homework, not the answers, and the view-grain layer is its deepest original bet.

Zero training corpus + a v0.3 language.

Models write C# and Elixir fluently; they write G# barely. The language is real — a compiler, a full spec, a tour small enough to hold in a context window — but it is v0.3 with one primary maintainer, and no model has trained on it. The guts are C# assemblies and the G# surface is deliberately thin, but day-one agent fluency is lost. Stated plainly.

Time travel has a disk bill.

Journals grow without mercy, and “rewind anything” is a promise about retention, compaction, and privacy law all at once. Erasure requests mean rewriting history — so fugue ships forget as a first-class verb (per-user redaction across journals), prices the recorder per grain type, and turns it on by default only where it earns its storage.

“Just put it in a grain” is a seduction.

Cross-grain consistency is not transactional by default: two grains updated in one gesture can land in two states. Postgres remains truth, grains remain cache-with-behavior, and fugue says so on page one instead of in a postmortem.

The pitch, in one sentence: every stateful thing in your product is an immortal, addressable process; every user's screen is one too; the wire carries HTML diffs; the cluster heals itself from one Postgres — and all of it is written in a language that feels like Go and runs on the runtime your CFO already pays for.