Ensemble is a multi-service platform, not a single app. The daemon is the substrate; services are the inhabitants. A chat bot, a game matchmaker, a group-chat host, and the package registry itself are all just services.
This doc is the map. To build one, follow creating-a-service.md. The trust model underneath is security.md (and ADR-0002).
A service is any program that registers with a daemon via the RegisterService
API. In return it gets:
- Its own cryptographic identity — an
E…address derived deterministically from the daemon's master seed + the service name. Same name → same address, stable across restarts and upgrades, for free. - Its own connection-authorization policy — a manifest ACL, default-deny,
with opt-in escalations (dial-as-self,
decides_connections). - Its own message stream — chat envelopes or raw RPC bytes, plus connection requests, file offers, command invocations, and peer introductions.
A service can run three ways, all over the same SDK surface:
| Shape | How it connects | Auth |
|---|---|---|
| Standalone / sidecar | dials a socket or TCP addr | admin seed signs every RPC |
| Supervised install | daemon execs it with a spawn contract | per-spawn token |
| Launched app | daemon launches a separate GUI process and brokers its link | scoped launch grant |
Client.from_env() (both SDKs) picks the right one from the environment, so the
same build runs supervised or standalone unchanged.
The daemon ships two services itself:
node— chat, file transfer, and contacts: the core peer-to-peer messaging surface.ui— the embedded Solid.js web client, served over its own onion (and optionally local HTTPS / LAN HTTP). See running.md → Web UI.
Write services in Python or .NET; the daemon doesn't care what language a service is in — it speaks gRPC over a socket.
- Python —
pip install ensemble-client. Async API, dataclass events,Client.from_env()for supervised installs.../clients/python/README.md. - .NET 8 —
dotnet add package Ensemble.Client. Typed records,IAsyncDisposablelifetimes, typed exceptions.../clients/dotnet/README.md.
Both expose the same three transports — chat (envelopes), RPC (raw protobuf bytes), and the introduction primitive for wiring two strangers together with a daemon-attested provenance stamp.
A registry is itself a service. It serves a signed, hash-pinning index;
subscribing pins its E… address, and that pin is the whole trust decision.
Install resolves a name through your subscribed registries, hash-verifies the
artifact, then the daemon's supervisor runs it. The full packaging → publish →
install → supervise story is ADR-0009 and the
service playbook.
These are separate projects that live on the platform — the proof that the "platform, not app" framing is real rather than aspirational. Each one leaned on a different platform primitive, which is the interesting part.
PUG — matchmaking for multiplayer games
Pick-Up Group: a .NET matchmaking library — queues, matchers, private
lobbies, and sessions — that runs on the Ensemble P2P network. The
matchmaker is a public, RPC-transport service: it pairs players and
introduces them to each other, but hands out no grants. Each player runs a
decides_connections service that accepts a dial iff it came from a peer the
verified matchmaker just introduced — then the two players connect directly
(player-service to player-service) over QUIC and the matchmaker drops out of the
data path entirely.
This is the canonical worked example of the ADR-0002 model: introductions are information, the game owns its own policy, and the core stays generic and default-deny.
Primitives exercised: RPC transport,
IntroducePeers,decides_connections, direct peer-to-peer QUIC after introduction.
Jeff — an AI companion
A single-user AI companion (an Ollama-backed LLM with pgvector long-term memory) that lives as a daemon-supervised package inside an Ensemble pod. Jeff is a chat-transport service with an operator-only allowlist — the contact list is the operator, never the public — and an "inner life" / proactive loop that lets it initiate contact rather than only replying.
Jeff is the showcase for the supervised-install lifecycle: it's installed
and run by the daemon's supervisor (readiness = a clean RegisterService,
liveness = the register stream, restart = exponential backoff), its identity and
data dir survive upgrades for free, and its secrets — the system prompt and
model config — arrive through the per-service env overlay rather than being
baked into the (public) artifact. Built in Python on the Python SDK.
Primitives exercised: supervised install + spawn contract, env overlay for secrets, stable derived identity across upgrades, operator-only ACL.
gomp — decentralized group rooms
G.O.M.P. ("Get Outta My Pub") — a small, self-hostable, decentralized take on a Discord server. It's a launched app (ADR-0010 / ADR-0011): a host service (the room) plus a separate desktop client process that the daemon launches and whose link it brokers, relayed through the daemon. Rooms are host-led (ADR-0007) — a room is just a service some node hosts, and the pub metaphor runs through the UI (getting kicked means "you're barred").
gomp is the showcase for services bringing their own frontend: rather than a declarative markdown page, it launches its own GUI process (any stack) and the daemon's launcher fires it up and brokers the client↔host relay datapath. It lives in its own repo and installs through the same registry + supervisor path as everything else.
Primitives exercised: launched-app lifecycle (ADR-0011), host-led group rooms (ADR-0007), services-bring-their-frontend (ADR-0010), client↔host relay through the daemon.
The end-to-end guide — SDK contract, manifest/capabilities, packaging, the spawn contract, the env overlay, registries, and install/management — is the service creation playbook. The relevant ADRs: