MIT·zero dependencies·Go 1.21·since 2017
Your Go service is one panic() away from a full restart.Ergo makes it one process.
Ergo is a Go framework for distributed systems. Every process gets a supervisor, an address and a mailbox — so the one that fails dies and comes back, and the rest never notice. Clustering included, compiled into your binary.
No broker. No sidecar. No codegen. No dependencies.
MyNode@localhost up 00:04:12 ├── myapp running │ └── mysup one_for_one │ ├── worker#1 4,102 msg │ └── worker#2 4,097 msg └── radar :9090
worker#1 terminated: panic mysup restarting worker#1 …
│ ├── worker#1 6 msg ← back │ └── worker#2 4,131 msg ← never noticed node uptime unchanged. no redeploy. no page.
In plain Go that panic ends the process — and you cannot recover it if the goroutine came from a library you do not own.
Trusted in production
“Suddenly I want a wrapper around each goroutine that bubbles up the panic to the main thread without crashing the app — that sounds a lot like an erlang supervision tree.”— Hacker News, July 2024
He was not talking about Ergo. He did not know it existed. He had hit the wall every Go service hits eventually and reasoned his way to the shape of the answer.
That shape has been shipping for years.
The whole thing, web UI included, has a dependency graph of zero.
Not “few”. Not “carefully audited”. Ergo’s go.sum is an empty file — and so is the observer’s, a cluster inspector with an embedded browser UI and an agent interface, built entirely on the standard library. No cgo either.
// ergo.services/ergo — go.mod, complete file module ergo.services/ergo go 1.21
- Static binary, one full node11.6 MB
- Embedded observer UI280 KB
- Bytes in go.sum0
Counted with go list -m all. The Prometheus, OTLP and etcd integrations do carry their vendors’ graphs — they are separate modules you opt into, not something you inherit.
Nothing to CVE-scan. Nothing to upgrade. Nothing to take to your security team.
You have state that has to live somewhere.
So it goes in Redis, and now every read is a network hop and a cache-invalidation bug. Or it stays in memory and you spend a quarter on sticky sessions, consistent hashing and a StatefulSet — and it still evaporates on the next rolling update.
// any node in the cluster, same call process.Send( gen.ProcessID{ Name: "session", Node: "edge-7@eu", }, MessageJoin{Room: "lobby"}, )
One Send. Whether that process sits in this binary or three datacenters away is decided inside the function, not by you.
The state stays in the process, and the process has a name. There is no cache to invalidate and no affinity to configure, because there is nothing to route to except the thing itself.
When it dies, its supervisor decides what happens next — restart just it, restart its siblings with it, or give up and let the failure travel one level out. Four supervision types, four restart strategies, declared in a struct and changeable while the node runs.
Every observability tool shows an agent the platform your code runs on.
This one shows it your code. Ergo speaks MCP: 38 tools and 13 resource lenses over a live cluster. The interesting one asks a running process what it knows — and the process answers, because answering is a callback, not an export.
→ process_state order-worker · prod-7 config.workers 8 config.timeout 30s stats.processed 3944 stats.errors 46 stats.rate 17/s runtime.uptime 3m51s
No metric was exported. No dashboard was configured six months ago. The process was asked.
Prometheus can only show what you decided to record before you knew what would go wrong. Here the agent reads the live variable — and it discovers what it may ask, because the process describes its own surface first.
Mailboxes report depth and the age in nanoseconds of the oldest unprocessed message, which is the difference between “this service is slow” and “this service is merely behind”.
And the objection you are already forming, answered structurally: 26 of the 38 tools cannot change anything, a permission ceiling can only ever narrow, and on a node built with DisableManage the rest are not denied — the process they address is never started.
Tests that start fifty real nodes inside one go test.
The harness calls the same node.Start production calls, binds real listeners and speaks the real wire protocol; only discovery is swapped for something in-memory. There is no Docker anywhere in the tree and no container runtime in your CI.
Throughput on a 64-core processor, memory on an Apple M4 Max. Full captures in the benchmarks repo, reproducible with go test -bench=.
The last one is the figure that matters: reaching a million subscribers costs ten network messages — one per node, not one per subscriber. That is the broker you did not deploy.
The boring half of a cluster is already written.
Twenty modules on one import path, all of them MIT. There is no licence boundary, no feature matrix, and nothing here to buy.
ergo — processes, supervisors, applications, the wire format, clustering, tracing, cron, the test harness
MITobserver cluster inspector, web UI and agent interface · health K8s probes · metrics Prometheus · pulse OTLP · radar both on one port
MITgrid distributed registry and process groups · leader Raft leader election · etcd registrar for small and medium clusters
MITwebsocket · sse · erlang EPMD, handshake, DIST and ETF, so a Go node can join a BEAM cluster
MITSaturn — the central registrar. Every node re-registers once a second, which stops paying off at scale; Saturn is built for the other side of that line.
MITNo key, no phone-home, no node cap, no tier that unlocks the useful part. Saturn, the registrar for large clusters, was the one component under a commercial licence and moved to MIT in 3.3.
Being built on top of it
The cluster inspector as a hosted service — the same surface you already run locally, without running it yourself.
October 2026Design the system into a machine-readable architecture contract, hand it to your coding agent over MCP, then investigate incidents against what the system was meant to do rather than against its telemetry.
December 2026One transparent network for your nodes, across data centres, clouds and continents. No VPN, no proxy, no tunnels.
waitlistWhat people build with it.
The same primitives, pointed at different problems. Each line is the part of the framework that does the work.
Actor-per-request, supervision trees for fault isolation, distributed service discovery
21M+ msg/sec throughput, four priority queues, sub-millisecond mailbox latency tracking
TCP/UDP meta processes, Port for device protocols, distributed pub/sub for sensor data fanout
Guaranteed message delivery, distributed consensus with built-in leader election, no external dependencies
Lightweight actors, worker pools, WebSocket support, cluster-wide event broadcasting
Small binary footprint, NAT traversal, the observer's MCP endpoint reachable through the cluster proxy without opening a port
Where it does not fit.
- —No durable execution. Ergo does not persist state. A restarted process starts clean; it does not resume mid-computation. If you need workflows that survive a crash you want Temporal — and it runs perfectly well on Ergo nodes.
- —Not a log. Cluster pub/sub replaces a broker used for fanout. It does not replace retention, replay, or a consumer group rewinding a week.
- —Back-pressure, not magic. A full mailbox returns an error you have to handle, and a remote send is fire-and-forget unless you mark it important.
- —Go, not polyglot. A Python service talks to it over HTTP like anything else — or you write that one in Erlang and it joins the cluster directly.
- —No hot code reload. New code means restarting the node.
Three commands, then open the observer.
$ go install ergo.tools/ergo@latest $ ergo init MyNode github.com/you/mynode $ go run ./cmd → http://localhost:9911