ergo-services/ergo

Goergo.services

An actor-based Framework with network transparency for creating event-driven architecture in Golang. Inspired by Erlang. Zero dependencies.

erlanggolangelixirsupervisorotpotp-applicationsdistributed-systemsframeworkmicroservicemicroservices-architecturegodistributed
Sterne-Wachstum
Sterne
4.7k
Forks
189
Wochenwachstum
+17
Issues
0
2k4k
Sept. 2017Sept. 2020Sept. 2023Sept. 2026
ArtefakteGo Modules
README

Ergo Framework

Gitbook Documentation MIT license Telegram Community Reddit

Actor model for Go. Build distributed systems without the distributed systems headache.

Goroutines and channels work great until your system grows. Then come the mutexes, the race conditions, the service discovery configs, the retry logic, the connection pool management. Ergo replaces all of that with one model: isolated processes that communicate through messages, supervised automatically, addressable across any cluster.

Inspired by Erlang/OTP. Zero external dependencies. Pure Go.

The core idea in 30 seconds

type Counter struct {
    act.Actor
    count int
}

type MessageInc struct{}

func (c *Counter) HandleMessage(from gen.PID, msg any) error {
    switch msg.(type) {
    case MessageInc:
        // safe without locks even with thousands of concurrent senders:
        // messages are processed one at a time
        c.count++
        c.Log().Info("count: %d", c.count)
    }
    return nil
}

func factory_Counter() gen.ProcessBehavior { return &Counter{} }

// Start a node and spawn the actor
node, _ := ergo.StartNode("mynode@localhost", gen.NodeOptions{})
pid, _ := node.Spawn(factory_Counter, gen.ProcessOptions{})

// Same API whether local or on another continent
node.Send(pid, MessageInc{})
node.Send(pid, MessageInc{})

No locks. No race conditions. Sequential message handling is the guarantee.

Why not just goroutines + channels?

Goroutines + channels Ergo
Shared state You manage with mutexes No shared state by design
Failure recovery Manual Supervision trees restart automatically
Cross-node messaging Build it yourself Same API, transparent
Service discovery External tool needed Built in
Race conditions Possible Impossible within a process

What you can build

Real-time backends. Each WebSocket connection becomes an addressable actor. Any node in your cluster can push to any specific client. No pub/sub intermediaries.

IoT platforms. One actor per device. Thousands of devices per node. Supervisors restart failed device actors automatically.

Multi-agent AI systems. Each agent is an isolated actor with a mailbox. Crash isolation, supervision, distributed addressability, and an MCP endpoint served by Observer that opens the running cluster to any AI assistant (Claude Code, Cursor, and other MCP-compatible clients). See AI Agents for patterns and diagnostics.

Financial and event-driven systems. Four priority queues per mailbox, guaranteed delivery, no dropped messages.

Distributed Pub/Sub across the cluster. Producer registers an event once; any process on any node subscribes. The framework delivers one network message per node, not per subscriber. 1M subscribers across 10 nodes cost 10 network messages, not 1M.

// Producer on any node
token, _ := producer.RegisterEvent("prices", gen.EventOptions{})
producer.SendEvent("prices", token, PriceUpdate{Asset: "BTC", Price: 95000})

// Subscriber on any other node, identical API
process.MonitorEvent(gen.Event{Name: "prices", Node: "producer@host"})

func (s *Sub) HandleEvent(event gen.MessageEvent) error {
    fmt.Println(event.Message.(PriceUpdate))
    return nil
}

Performance

  • 25M+ messages/second locally
  • ~5.8M messages/second over the network
  • Distributed Pub/Sub: 2.9M msg/sec delivery to 1,000,000 subscribers across 10 nodes

Lock-free queues. Processes sleep when idle. No CPU wasted.

The numbers come from make bench, which measures four scenarios: one process sending to one process, and one pair per CPU, each on a single node and across a connection between two nodes. msg/sec is the rate messages are carried end to end - the send loops included, not the rate Send is called at.

On an AMD Ryzen Threadripper 3970X (32 cores, 64 threads):

$ make bench
go test -run XXX -bench . -benchmem -benchtime 5s ./testing/benchmarks/...
goos: linux
goarch: amd64
pkg: ergo.services/ergo/testing/benchmarks/ping
cpu: QEMU Virtual CPU version 2.5+
BenchmarkLocal11-64        14633359     459.0 ns/op    2178526 msg/sec     58 B/op    2 allocs/op
BenchmarkLocalNN-64       143445265      39.08 ns/op  25591080 msg/sec     69 B/op    2 allocs/op
BenchmarkNetwork11-64       6348997     908.9 ns/op    1100193 msg/sec    776 B/op    6 allocs/op
BenchmarkNetworkNN-64      34500532     172.2 ns/op    5807413 msg/sec    150 B/op    6 allocs/op
PASS

On an Apple M4 Max (14 cores: 10 performance, 4 efficiency):

$ make bench
go test -run XXX -bench . -benchmem -benchtime 5s ./testing/benchmarks/...
goos: darwin
goarch: arm64
pkg: ergo.services/ergo/testing/benchmarks/ping
cpu: Apple M4 Max
BenchmarkLocal11-14        31014296     191.4 ns/op    5224960 msg/sec     58 B/op    2 allocs/op
BenchmarkLocalNN-14       100000000      64.97 ns/op  15390843 msg/sec     72 B/op    2 allocs/op
BenchmarkNetwork11-14      13759047     431.2 ns/op    2319322 msg/sec    262 B/op    6 allocs/op
BenchmarkNetworkNN-14      27716260     193.6 ns/op    5166431 msg/sec    137 B/op    6 allocs/op
PASS

The two machines answer two different questions. A single pair costs 191ns per message on the M4 Max against 459ns on the Threadripper - that is per-core speed. Aggregate throughput goes the other way: 25.6M msg/sec against 15.4M, because there are 64 threads to fill instead of 14. What does not move is the allocation count: 2 allocations per local message and 6 per message that crosses the network, on both machines.

The cpu: line of the Linux run reports the hypervisor's string; the hardware underneath is the Threadripper.

Full benchmarks: benchmarks repository.

Observer

Observer is a real-time web UI for monitoring and inspecting Ergo nodes. It provides live visibility into every layer of the system:

  • Processes - full process list with state, mailbox depth, latency, running time, wakeups, and uptime. Click any process to inspect its supervision tree, links, monitors, aliases, environment, and internal actor state
  • Applications - running applications with their process trees, modes, and uptime
  • Network - cluster topology, per-node connection details, traffic counters, and protocol info
  • Events - registered events with producer, subscriber counts, and publication statistics
  • Logs - live log stream with level filtering across the cluster
  • Profiler - goroutine dump with grouping and stack traces, heap profile with allocation breakdown, and GC pressure charts

Add Observer to your node as an application:

import "ergo.services/application/observer"

options.Applications = []gen.ApplicationBehavior{
    observer.CreateApp(observer.Options{}),
}

To see it in action with a fully loaded cluster, see the observability example. For more information, visit the Observer documentation.

Features

  1. Actor Model: isolated processes communicate through message passing, handling messages sequentially with four priority queues. Supports asynchronous messaging and synchronous request-response, with per-process mailbox latency measurement (-tags=latency) for production diagnostics.

  2. Network Transparency: actors interact the same way whether local or remote. Uses EDF (Ergo Data Format), a custom binary serialization with type caching, pointer support, and message versioning for seamless upgrades. Includes connection pooling, compression, message fragmentation, and application-level keepalive for silent failure detection.

  3. Supervision Trees: hierarchical fault recovery where supervisors monitor child processes and apply configurable restart strategies. Supports One For One, All For One, Rest For One, and Simple One For One supervision types with Transient, Temporary, and Permanent restart policies.

  4. Meta Processes: bridge blocking I/O with the actor model through dedicated meta processes handling TCP, UDP, Port, Web, WebSocket, and SSE protocols without affecting regular actor message processing.

  5. Distributed Systems: service discovery via embedded or external registrars (etcd, Saturn), distributed publish/subscribe events with token-based authorization and buffering, remote process spawning with factory-based permissions, remote application orchestration across nodes, and Raft-style leader election - terms, votes and heartbeats, with no replicated log - without external dependencies for coordinating exclusive work across cluster replicas.

  6. Observability: real-time cluster inspection via the Observer web UI, native distributed tracing that follows message chains across nodes with automatic propagation (exportable to OTLP backends like Grafana Tempo or Jaeger via Pulse), and production metrics via Radar with a ready-to-use Grafana dashboard covering process lifecycle, mailbox pressure, network traffic, and event fanout. The extensible Metrics actor adds custom Prometheus collectors alongside built-in node telemetry.

  7. AI-Native: Observer serves an MCP endpoint beside its web UI, opening the full cluster to AI agents (Claude, Cursor, and any MCP-compatible client). Inspect processes, query events, capture goroutine dumps, stream logs, and run real-time samplers through natural language, turning any AI assistant into an interactive SRE for your Ergo cluster.

  8. Cloud Native: built-in Kubernetes health probes (liveness, readiness, startup) via the Health actor, Prometheus metrics endpoint, and mTLS support for zero-trust deployments.

  9. Ready-to-use Components: core framework includes Actor, Supervisor, Pool, Router, and WebWorker actors plus TCP, UDP, Port, and Web meta processes. Extra library provides Leader, Metrics, and Health actors, Observer, Radar, Pulse, and Grid applications, WebSocket and SSE meta processes, and Colored, Rotate, and Sentry loggers.

  10. Erlang Interoperability: native support for the Erlang distribution protocol enables heterogeneous clusters where Ergo (Go) and Erlang/Elixir nodes participate as equal peers. Send messages, spawn processes, and set up links and monitors across language boundaries without any proxies or bridges.

  11. Flexibility: customize network stack, certificate management (mTLS, NAT traversal), compression and message priorities, Cron-based scheduling, important delivery for guaranteed messaging, and logging. The ergo CLI tool generates project scaffolding, actors, supervisors, and message types from the command line, and argus is a vet tool that checks the actor model invariants the compiler cannot.

Examples demonstrating the framework's capabilities are available in the examples repository.

Questions and answers: FAQ.

Quick start

The ergo CLI generates project scaffolding for you: applications, actors, supervisors, message types. The output is a complete, runnable project structure. Add components incrementally as your service grows.

To install use the following command:

$ go install ergo.tools/ergo@latest

Create a project and start adding components:

$ ergo init MyNode github.com/myorg/mynode
$ cd mynode
$ ergo add supervisor MyNodeApp:MySup
$ ergo add actor MySup:MyActor
$ go run ./cmd

The generated project is ready to run immediately. Add more components as your service grows:

$ ergo add actor --pool MySup:MyPool
$ ergo add app BackgroundApp
$ ergo add message MessageConnect --field ID:gen.Alias --field Addr:string

For the full command reference, see the ergo tool documentation.

Claude Code integration

Pre-built agents and skills for Claude Code turn any Claude session into an Ergo-aware collaborator. Two paired toolkits shipped in the ergo-services/claude repository:

  • framework - designing and implementing actor systems. An architect agent (DDD bounded contexts, supervision trees, cluster topology, load analysis) plus a skill with progressive-disclosure references covering actors, supervision, messages, applications, pool and routing, meta processes, node configuration, EDF, cluster, tracing, logging, cron, errors, testing, the Erlang protocol, and every extension library.

  • devops - diagnosing running clusters over the observer's MCP endpoint. An SRE agent that runs hypothesis-driven investigations (observe, hypothesize, test, confirm) plus a skill with the full catalog of 13 resource lenses and 38 tools, counters reference, 11 diagnostic playbooks, active/passive sampler recipes, and build-tag awareness.

The plugin is published in the Claude Code marketplace, so installing it takes one command:

/plugin

Search the list that opens for ergo and install it. Nothing else to set up.

To take it from this repository instead - to follow the source, or to install without the marketplace:

/plugin marketplace add ergo-services/claude
/plugin install ergo@ergo-services

After install, invoke the skills as /ergo:framework or /ergo:devops. Agents pick themselves up from trigger phrases ("design ergo application", "why is it slow", "check cluster health", etc.).

Requirements

  • Go 1.21.x and above

Development and debugging

To enable Golang profiler just add --tags pprof in your go run or go build (profiler runs at http://localhost:9009/debug/pprof). Use PPROF_HOST and PPROF_PORT environment variables to customize the address.

With --tags pprof, each actor goroutine is labeled with its PID and each meta process with its Alias for easy identification in pprof output:

curl -s "http://localhost:9009/debug/pprof/goroutine?debug=1" | grep -B5 'labels:.*pid'
curl -s "http://localhost:9009/debug/pprof/goroutine?debug=1" | grep -B5 'labels:.*meta'

Output:

1 @ 0x100c17fa0 ...
# labels: {"pid":"<ABC123.0.1005>"}
#   main.(*Worker).HandleMessage+0x27  /path/worker.go:45

This helps identify stuck processes during shutdown by matching PIDs/Aliases from the shutdown log with goroutine stack traces.

Since Go 1.27 the labels also appear in a plain goroutine dump - ?debug=2, runtime.Stack and the traceback of an unrecovered panic - in the header line of every goroutine:

goroutine 38669 [chan receive] {pid: "<ABC123.0.1041>"}:
goroutine 24812 [IO wait] {meta: "Alias#<ABC123.107118.6819740677833.0>", role: reader}:

The runtime keeps this off for a module that declares an older Go version, so a module on go 1.21 has to ask for it. Either put the directive in the main package:

//go:debug tracebacklabels=1

package main

or set GODEBUG=tracebacklabels=1 in the environment. Without it the ?debug=1 profile still carries the labels and a plain dump does not.

To disable panic recovery use --tags norecover.

To enable mailbox latency measurement use --tags latency. This adds a monotonic timestamp to every message pushed into the MPSC queue, allowing QueueMPSC.Latency() and ProcessMailbox.Latency() to report the age of the oldest unprocessed message. Overhead is approximately 10-25% on micro-benchmarks (LOCAL 1-1 scenario). Without the tag, Latency() returns -1 and there is zero overhead.

To enable per-type encode/decode statistics use --tags typestats. This tracks the count of root-level encode/decode operations and decompressed wire-byte volume per registered EDF type, exposed via Network().RegisteredTypes() and visible in the Observer Types panel. Helps identify which message types dominate network traffic and which processes would benefit from compression. Overhead is approximately 2-3% on encode/decode throughput. Without the tag, counters remain zero and there is zero overhead.

To enable trace logging level for the internals (node, network,...) use --tags verbose and set the log level gen.LogLevelTrace for your node.

For detailed debugging techniques, troubleshooting scenarios, and best practices, see the Debugging documentation.

To run tests with cleaned test cache:

go vet
go clean -testcache
go test -v ./testing/tests/...

Commercial support

please, contact support@ergo.services for more information

Ähnliche Repositories
anoma/anoma

Reference implementation of Anoma

ElixirMIT Licensecryptographyblockchain
anoma.net
33.6k4.1k
asdf-vm/asdf

Extendable version manager with support for Ruby, Node.js, Elixir, Erlang & more

GoGo ModulescliMIT Licenseversion-managerruby
asdf-vm.com
25.6k941
gleam-lang/gleam

⭐️ A friendly language for building type-safe, scalable systems!

Rustcrates.ioApache License 2.0gleamprogramming-language
gleam.run
21.9k1k
emqx/emqx

The most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles

ErlangOthermqttiot
emqx.com
16.7k2.5k
lk-geimfari/awesomo

Cool open source projects. Choose your project and get involved in Open Source development now.

GoGo ModulesawesomeCreative Commons Zero v1.0 Universalawesomeocaml
9.9k687
ninenines/cowboy

Small, fast, modern HTTP server for Erlang/OTP.

ErlanglibraryISC Licenseerlanghttp
ninenines.eu
7.5k1.2k
apache/couchdb

Seamless multi-primary syncing database with an intuitive HTTP/JSON API, designed for reliability

ErlangApache License 2.0contentnetwork-server
couchdb.apache.org
6.9k1.1k
oldratlee/translations

🐼 Chinese translations for classic software development resources

libraryOthertranslationchinese-translation
github.com/oldratlee/translations
6.9k1.5k
processone/ejabberd

Robust, Ubiquitous and Massively Scalable Messaging Platform (XMPP, MQTT, SIP Server)

ErlangOthererlangxmpp
process-one.net/ejabberd/
6.7k1.6k
lunatic-solutions/lunatic

Lunatic is an Erlang-inspired runtime for WebAssembly

Rustcrates.ioApache License 2.0vmwebassembly
lunatic.solutions
4.9k149
rusterlium/rustler

Safe Rust bridge for creating Erlang NIF functions

Rustcrates.iolibraryApache License 2.0erlangnif
docs.rs/crate/rustler
4.9k247
dgiot/dgiot

Open Source Industrial IoT Platform | 300+ protocols | 6-min deploy | Modbus OPC UA MQTT | 12K Stars

ErlangApache License 2.0iotiot-platform
dgiotcloud.cn
4.8k966