Skip to content

Architecture Overview

Reconcile loop

The agent is built around a single reconcile loop that runs on a fixed interval (default: 30s).

┌───────────────────────────────────────────────────────────┐
│  Reconcile Engine                                         │
│                                                           │
│  1. Load local state from bbolt                          │
│  2. FetchDesiredState from backend                       │
│  3. For each registered handler:                         │
│     handler.Reconcile(localState, desiredState)          │
│  4. Save updated state to bbolt                          │
│  5. Report health via Heartbeat                          │
├───────────────────────────────────────────────────────────┤
│  Handler Registry                                         │
│  ┌─────────────────┐ ┌──────────────┐ ┌──────────────┐   │
│  │ PrivateNetwork  │ │ Kubernetes   │ │ Firewall     │   │
│  │ Handler         │ │ Handler      │ │ Handler      │   │
│  └─────────────────┘ └──────────────┘ └──────────────┘   │
├───────────────────────────────────────────────────────────┤
│  API Client                                               │
│  FetchDesiredState()     — pull desired state             │
│  ReportResourceState()   — push resource-specific data    │
│  Heartbeat()             — push health status             │
└───────────────────────────────────────────────────────────┘

The agent never initiates actions — it only reacts to desired state changes from the platform.

Key design principles

  • Declarative — the backend tells the agent what should exist, not how to do it
  • Idempotent — every operation can be safely retried
  • Extensible — new resource types are added by implementing a handler interface
  • Crash-safe — state is persisted to bbolt; the agent can restart at any point

Resource handlers

Each resource type (private network, Kubernetes, firewall, etc.) has its own handler that implements:

type ResourceHandler interface {
    Name() string
    Reconcile(ctx context.Context, state *AgentState, desired *DesiredState) error
}

Handlers are responsible for:

  • Detecting diffs between desired and current state
  • Applying changes (create, update, delete)
  • Reporting status back to the platform via ReportResourceState
  • Storing handler-specific local state (e.g., WireGuard private keys)

State management

State Storage Purpose
Desired state Fetched from backend each cycle What resources should exist
Local state bbolt on disk (/var/lib/segla) Agent ID, resource keys, last applied state
Resource state Reported to backend Status of each resource (active, failed, etc.)

Error handling

All operations are idempotent. Failures are logged and retried on the next cycle:

Scenario Behavior
FetchDesiredState fails Skip cycle, retry next tick
Handler fails Log error, continue other handlers
ReportResourceState fails Retry next cycle
State save fails Keys may regenerate (idempotent)