Skip to content

Architecture and Core Concepts

Pragmatic.Agent is the local coordination substrate for Pragmatic.Design hosts. It’s a small sidecar process that provides a shared KV store, cluster membership, and a stable IPC surface — so individual app processes don’t need to talk to a centralised service for routine operational concerns.

Status: implemented preview subsystem. Already used inside the repo; packaging and operational surface are still evolving.


A multi-host Pragmatic application needs a place to keep things like:

  • Configuration snapshots — what’s the current booking-service feature flag state?
  • Tenant → backend map — which hosts serve tenant acme-eu?
  • Cluster membership — which hosts are alive right now?
  • Control-plane commands — “please drain traffic from host-3”

You can centralise this on Redis / etcd / Consul / a custom SignalR hub, and Pragmatic has adapters for all of those. But you pay:

  • a network hop for every read
  • single-point-of-failure risk if the central store is down
  • latency variance for operational hot paths (feature-flag evaluation)
  • secrets + TLS management for every outbound call

The Agent solves the local half of this problem: every host has a local daemon that apps talk to over a Unix socket / Windows named pipe. The daemon gossips with peer Agents, persists state locally, and serves reads in microseconds.


PackageRole
Pragmatic.AgentThe daemon executable + CLI (pragmatic-agent start, pragmatic-agent status, …)
Pragmatic.Agent.ClientRuntime integration consumed by Pragmatic hosts (builder.UseAgent())
Pragmatic.Agent.ProtocolWire protocol — frames, payloads, JSON/MessagePack serialisation, transport security

Hosts reference Pragmatic.Agent.Client. Operators run Pragmatic.Agent as a daemon. Pragmatic.Agent.Protocol is an implementation detail pulled in transitively.


The default implementations of these stores are in-memory / file-based; UseAgent() swaps them to Agent-backed implementations:

InterfaceDefaultAgent-backed
IControlPlaneNo-opAgentControlPlane (heartbeat, commands)
IConfigurationStoreappsettings.jsonAgent KV (config/{key})
IFeatureFlagStoreIn-memory defaultsAgent KV (flags/{flag})
ITenantStoreStaticAgent KV (tenants/{tenant-id})

The Agent-backed versions fall back to graceful degradation: if the Agent is unavailable, reads return defaults (or the last known value from a local cache), writes are queued and retried. Your app keeps serving rather than crashing on a socket error.


The daemon persists state in a file-backed KV store under a data directory:

  • ~/.pragmatic/agent/ on desktop Linux / macOS
  • %LOCALAPPDATA%\Pragmatic\Agent\ on Windows
  • /var/lib/pragmatic/agent/ or $DATA_DIR in containers and services
  • Multiple instances live in sibling directories (--instance=stagingagent-staging/)

The KV layout is hierarchical:

config/
booking-service/
retry-max-attempts → "3"
timeout-ms → "5000"
flags/
new-checkout-flow → "enabled:true,rollout:50"
tenants/
acme-eu → "{ 'connectionString': '...', 'region': 'eu-west-1' }"
gateway/
routes/
booking → "{ 'backends': ['host-1', 'host-2'] }"

Apps read with AgentClient.GetAsync("config/booking-service/retry-max-attempts"); the daemon serves from RAM (state is fully loaded at startup) and persists writes to disk.


Multiple Agents form a cluster through SWIM-based gossip:

  • membership — each agent maintains a view of live/suspect/dead peers
  • failure detection — periodic indirect pings; an unreachable peer is marked suspect then dead after a grace period
  • dissemination — KV changes piggyback on gossip messages, propagating through the cluster in O(log N) rounds
  • piggybacking — the same gossip packets carry state updates plus health info, minimising traffic

This gives you cluster-wide propagation of config / flag / tenant changes without a central store. Each write lands in one Agent; within a few gossip rounds (~seconds on LAN), every Agent has the new value.

SWIM was chosen because it scales, tolerates partitions, and needs no leader. The tradeoff: state convergence is eventually consistent — a reader in a different host might see stale values for a few seconds.

For strong consistency (leader election, linearisable writes), use Pragmatic.ControlPlane on top of the Agent.


Apps talk to the Agent over a local-only transport:

  • Linux / macOS: Unix domain socket at $XDG_RUNTIME_DIR/pragmatic-agent.sock (or configurable)
  • Windows: named pipe at \\.\pipe\pragmatic-agent

Both transports are machine-local — no network exposure, no TLS needed, filesystem permissions gate who can talk to the daemon.

The wire protocol is framed binary with optional MessagePack bodies (default is JSON for debuggability). Clients reconnect automatically on daemon restart.


  1. Daemon starts (manually, systemd, launchd, Docker CMD, or auto-start from UseAgent() in dev)
  2. Daemon binds its local socket, loads persisted KV state, joins the gossip cluster
  3. Apps open connections on startup via UseAgent(); each app sends a heartbeat every few seconds
  4. Apps publish initial state (tenant mapping, flag defaults) if they own it
  5. Apps read runtime state on demand; reads are sub-millisecond via in-memory cache on the daemon
  6. Daemon persists KV mutations to disk as they happen
  7. On shutdown, daemon unregisters from gossip (if graceful), flushes state, closes sockets

The same socket is used by the CLI as by app clients:

Terminal window
pragmatic-agent start --data-dir=/var/lib/pragmatic/agent
pragmatic-agent status
pragmatic-agent config get booking-service.retry-max-attempts
pragmatic-agent config set booking-service.retry-max-attempts 5
pragmatic-agent flag set new-checkout-flow enabled=true rollout=100
pragmatic-agent cluster members

Operators don’t need to install a separate tool — the daemon ships the CLI in the same binary.


  • Application delivery — the Agent doesn’t deploy your app, it just coordinates it
  • Strong consistency — for leader election / distributed locks, use Pragmatic.ControlPlane
  • External gateway — routing decisions happen in Pragmatic.Gateway, which consumes from the Agent
  • Service discovery across WAN — the gossip cluster is LAN-oriented; for WAN-scale discovery use an external tool (Consul, etcd)
  • Secrets storage — values are file-backed but not encrypted at rest. Use a separate secrets store and put references in Agent KV if needed