Architecture
echo is a small core with a set of module crates around it. Three layers, each a clean boundary: the protocol that talks to the ircd, the engine that holds state and routes messages, and the services that do the work.
The protocol layer
An ircd link is one crate implementing the Protocol trait. It parses raw server-to-server lines into a normalized NetEvent vocabulary, and renders the engine's NetActions back into raw lines. The engine never sees a raw line, which is what makes another ircd a single new crate. InspIRCd is the reference, in modules/protocol/inspircd; it learns the server's channel modes, extbans, and capabilities from CAPAB at link time instead of hardcoding them.
The engine
The engine (src/engine) holds the live network view and the account and channel store, and routes a message addressed to a service into that service. It owns the things a module must never touch: the append-only log, the replication layer, and every credential.
It runs on one thread. An event is handled to completion before the next begins, so there are no locks on the shared state and no data races to reason about. The I/O around it (the uplink, the gRPC API, email, key derivation) runs on Tokio, and the one expensive thing a command can trigger, deriving a SCRAM verifier at registration, is handed off so it never stalls the event loop.
State as an event log
There is no database. Every change is one Event appended to a per-node JSON-lines log, and the running state is a fold over that log. A single fold serves three paths: replay on restart, ingest from a peer, and compaction. The code that applies an event on replay is the code that applies it live, so what is on disk and what is in memory cannot drift.
Every committed change is fsync'd before its command reports success, so a crash loses nothing. Compaction rewrites the log to a temp file and renames it atomically, so a crash mid-compaction leaves the old log intact.
Passwords are never stored in the clear. SASL uses stored SCRAM verifiers (PBKDF2), and none of that material is reachable through the traits a service sees.
Services and the two traits
A service is a pseudo-client in its own crate. It reaches the engine through exactly two traits and nothing else:
NetViewis a read-only view of the live network: who is online, their nick and host, channel membership, who holds ops, last-seen times.Storeis the account and channel store. Reads return credential-free views; writes are ordinary methods. A write committed here replicates like any other event, with no replication code in the service.
That boundary is the point. A service can read and change accounts and channels, but it cannot reach the log, the replication layer, or a password hash. A module can be wrong without being dangerous.
The workspace
The pluggable parts are separate crates in a Cargo workspace, each depending only on the echo-api SDK. Services live under modules/, one crate per service; ircd links live under modules/protocol/. If a crate compiles against echo-api, the core can run it. A service keeps lib.rs for the struct and command dispatch, with each command in its own file. See Writing a Module.