Architecture Overview
How the ControlBird Store, kernel, and services work together.
ControlBird is a distributed in-memory entity database for real-time automation. At its heart is a single, schema-driven, hierarchical Store that holds every device reading, configuration value, and service state in memory, with sub-millisecond reads and writes. Around the Store sits a small set of cooperating processes: the kernel that hosts the Store, the backend services that read and write it, and the Web UI that renders it reactively in a browser.
The defining idea is that services never talk to each other directly. They communicate by writing fields and subscribing to change notifications. This makes the system decoupled, observable, and durable: every interaction is a Store change that is persisted and can be watched by anyone with permission.
The Store
The Store is a tree of typed entities, each owning a set of fields defined by its schema. It lives in memory inside the kernel and holds the schemas, the entities, the field values, and the notification subscriptions that drive reactivity. Because everything is in memory, reads and writes complete in sub-millisecond time.
Each entity belongs to a type whose schema defines the fields it can hold, along with their defaults and inheritance. Entities are arranged in a parent-child tree, so related data stays grouped together and is easy to navigate.
Value types
Every field holds a typed value. The set of value types covers primitives, references, and collections:
| Type | Purpose |
|---|---|
| Bool | True or false |
| Int | 64-bit signed integer |
| Float | Floating-point number |
| Decimal | Exact base-10 fixed-point value |
| String | UTF-8 text |
| Blob | Raw bytes |
| Timestamp | Point in time |
| Choice | Selector into a fixed enumeration |
| EntityReference | Link to another entity (enables field indirection) |
| EntityList | Ordered, multi-valued relation |
See the Data Model page for how entities, schemas, and field indirection compose into the entity tree.
Processes
A running node is a handful of processes arranged around the kernel. Each connects to the Store and plays a single role.
| Process | Role |
|---|---|
| Kernel | Hosts the in-memory Store, accepts reads and writes, and handles persistence |
| Backend services | Protocol drivers, historian, automation, and other workers that read and write fields |
| Web gateway | Bridges the browser to the kernel over a WebSocket connection |
| Web UI | Browser application that renders and edits Store data reactively |
The kernel
The kernel hosts the Store and exposes it over TCP, with commands to read, write, create, delete, query, inspect schemas, and subscribe to notifications. A single kernel serves many clients at once, including the Web UI through the web gateway. Clients connect to the kernel over TCP, typically on port 9100.
Services
Backend services connect to the kernel with a Store client. The client reads and writes fields, tracks the notification subscriptions a service has registered, and can batch multiple operations into a single round trip for efficiency. The same surface is available to both synchronous and asynchronous services.
The Web UI bridge
Browsers connect through the web gateway. It serves the application, authenticates each WebSocket session, and connects to the kernel on the session's behalf. The address of the kernel is supplied through the KERNEL_ADDRESS environment variable. In the browser, a data store wraps the WebSocket connection and handles automatic reconnection and session recovery.
WebSocket keepalive
The gateway sends periodic heartbeats and treats a client as gone after a short window of silence. This keeps idle connections alive through reverse proxies that would otherwise close them.
Notifications and decoupled communication
Reactivity is the reason the Store works as a communication bus. A service subscribes by describing what it wants to watch: a specific entity or an entire entity type, which field to monitor, and a set of context fields to fetch atomically whenever the notification fires. When a matching write lands, the kernel delivers the new value together with the requested context in a single message.
| Subscription option | Meaning |
|---|---|
| Target | Watch one entity, or all entities of a type |
| Field | Which field's changes to monitor |
| Trigger mode | Fire only when the value changes (config), or on every write (request/response) |
| Context | Related fields fetched atomically alongside the change |
Because all messaging flows through fields and notifications, two services exchange data by writing to and watching the same entity. One service writes a request field, the watcher reacts and writes a response field, and the requester is notified of the response. Neither needs to know the other exists. The Notifications & Reactivity page covers these patterns in detail.
Persistence and recovery
Although the Store is in memory, it is durable. ControlBird uses durable write-ahead logging and periodic snapshots under the data directory, so state survives a restart. The write-ahead log records every change, and snapshots are point-in-time images that keep recovery fast.
Persisted state lives under the data directory:
data/{node}/wal/ write-ahead log
data/{node}/snapshots/ point-in-time snapshots
data/{node}/service-logs/ per-service logsLifecycle of a write
The pieces above combine into a single reactive loop. A typical end-to-end write proceeds as follows:
- The kernel starts, loads the configured schemas, and restores its persisted state.
- Services connect to the kernel over TCP; the web gateway connects when a browser opens a session.
- A service writes a field; the kernel applies it to the in-memory Store and records it durably.
- The kernel fires every notification whose entity and field match, attaching the requested context.
- Watching services react and may write response fields, completing a decoupled request/response exchange.
- The Web UI receives the change over its WebSocket connection and re-renders the affected components.
- Changes are batched to the write-ahead log and a fresh snapshot is captured periodically.
Boundaries
What the Store does not do
A Store is a single-node, in-memory database. Notification delivery is best-effort: a subscriber that disconnects must catch up on reconnect rather than assume it saw every event. Schema updates briefly pause writes to keep the model consistent, and very deep field-indirection chains cost a little more per read.
With the architecture in mind, continue to the Data Model to see how the entity tree and schemas are structured, or jump straight to configuring your first integration.