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.
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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 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 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.
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."
$ 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.
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 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.
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.
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.
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.
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.
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.