How it works

One server binary. One node binary. A single mTLS stream. That's the whole topology.

              ┌─────────────────────────────────────────────┐
              │              Central server (Go)             │
              │  gRPC + grpc-gateway (REST) · React UI       │
              │  ring buffer → flush → time-series store     │
              │  control-plane stream registry + scheduler   │
              │  alert engine · correlator · metrics taps    │
              └──────────────────▲──────────────────────────┘
                                 │  one outbound mTLS gRPC stream each
            ┌────────────────────┼────────────────────┐
            │                    │                    │
        ┌───┴────┐           ┌───┴────┐           ┌───┴────┐
        │  node  │           │  node  │           │  node  │
        │ PoP-A  │           │ branch │           │ cloud  │
        └────────┘           └────────┘           └────────┘
   in-process scheduler    behind NAT           outbound-only

The data path

  1. 1. Connect. Each probe node opens one bidirectional mTLS gRPC stream to the server and stays connected.
  2. 2. Assign. The server pushes test assignments down the stream; nodes resync on reconnect.
  3. 3. Probe. Nodes execute scheduled tests through a closed executor registry and stream results up.
  4. 4. Ingest. Results land in a ring buffer, flush in batches to the time-series store, and fan out to taps.
  5. 5. Act. The alert engine, correlator, live SSE stream, Prometheus, and OTel taps each consume the same flush.

One source of truth

Every wire shape — the node protocol, the REST API, the TypeScript client, and the OpenAPI document — is generated from a single set of protobuf definitions. There is no hand-maintained parallel schema to drift.

The default build is pure Go and CGO-free, so the server and node are single, statically-linked binaries that run anywhere — including a Raspberry Pi. SQLite is the default time-series backend, with no external database or container required.

Non-negotiables

Four design invariants shape every decision in FourEyes. They're why the architecture looks the way it does.

Outbound-only nodes

Probes never open an inbound port. They dial the server over mTLS, so they sit happily behind NAT and strict firewalls.

No remote-code channel

The node protocol carries only fixed, declared probe types — there is no slot for the server to push commands, scripts, or payloads. A compromised server cannot RCE your fleet.

Receive-only BGP

BGP integration observes sessions — it never originates, withdraws, or influences a route. FourEyes is a monitor, never a routing participant.

Signed self-update

Node updates are ed25519-verified against a build-pinned trust root and swapped atomically. An unverifiable binary is never installed.

More in the security model and the architecture docs.