Architecture
System Diagram
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā External Interfaces ā
ā (HTTP, Behavior Trees, CLI - future) ā
āāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāā
ā
āāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāā
ā Anolis Runtime (core/) ā
ā ā
ā āāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā ā
ā ā Device āāāāāāāŗā State Cache ā ā
ā ā Registry ā ā (polls) ā ā
ā āāāāāāāā¬āāāāāāā āāāāāāāā²āāāāāāāā ā
ā ā ā ā
ā ā¼ ā ā
ā āāāāāāāāāāāāāāā ā ā
ā ā Call āāāāāāāāāāāāāāā ā
ā ā Router ā ā
ā āāāāāāāā¬āāāāāāā ā
ā ā ā
ā āāāāāāāā¼āāāāāāā ā
ā ā Provider ā ā
ā ā Host ā ā
ā āāāāāāāā¬āāāāāāā ā
āāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ADPP (stdio)
āāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Provider Processes ā
ā (anolis-provider-sim, future: modbus, ā
ā arduino, canbus, crumbs, etc.) ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Component Responsibilities
Provider Host (core/provider/)
- Spawn/manage provider processes
- Frame stdio communication (uint32_le length prefix)
- Send ADPP requests, receive responses
- Supervise provider health (crash detection, automatic restart)
- Enforce exponential backoff and circuit breaker policies
- Coordinate device cleanup and rediscovery on restart
Device Registry (core/registry/)
- Run discovery at startup (Hello ā ListDevices ā DescribeDevice)
- Store immutable device capabilities
- Provide lookup for validation
State Cache (core/state/)
- Poll default signals from all devices (500ms interval)
- Track timestamps and staleness
- Expose read-only snapshot API
- Thread-safe via shared_ptr copies
Call Router (core/control/)
- Validate control requests (device exists, function exists, args valid)
- Serialize calls per-provider (mutex locks)
- Forward to provider via ProviderHandle
- Trigger immediate post-call state poll
Runtime (core/runtime/)
- Load config (yaml)
- Launch providers
- Initialize state cache polling config (
StateCache::initialize) - Prime one synchronous snapshot (
StateCache::poll_once) - Start background polling only after initialization (
StateCache::start_polling) - Enter main loop
Data Flow
Discovery (startup):
Runtime ā Provider Host ā Provider Process
ā Hello
ā ListDevices
ā DescribeDevice
ā Device Registry (immutable)
State Polling (continuous):
State Cache ā Provider Host ā Provider Process
ā ReadSignals
ā Update cache with timestamps
Control Commands (Manual or Automated):
External API (HTTP, CLI, Behavior Tree) ā Call Router
ā Validate against Registry
ā Provider Host ā Provider Process
ā CallResponse
ā State Cache (immediate poll)
ā Result
Behavior Trees (Above Kernel):
Behavior Tree (reads) ā State Cache
Behavior Tree (writes) ā Call Router ā Provider
Key: BTs never bypass CallRouter or StateCache.
BTs use same validation/enforcement as manual control.
Key Invariants
- Only providers talk to hardware - No direct GPIO/serial/network from runtime
- Registry is read-only after discovery - No hot-plug (v0)
- All reads go through State Cache - Never query provider directly (external layers, BTs, HTTP, CLI)
- All writes go through Call Router - No bypass allowed (manual or automated, all use same path)
- Providers are isolated processes - No shared memory, crash-safe
- No hard-coded device semantics - Core is capability-driven, not device-specific
- External layers use core APIs only - BTs, HTTP, UI never talk directly to providers
Technology Stack
- Language: C++17
- Build: CMake 3.20+
- Dependencies: vcpkg (protobuf 6.33.4, yaml-cpp)
- Protocol: ADPP (protobuf over framed stdio)
- Platform: Windows, Linux
Concurrency Model (Current: v1)
Current Architecture: Multi-threaded
- State Polling: Dedicated background thread in StateCache
- Runs continuous loop at configured interval (default 500ms)
- Thread-safe via mutex-protected state snapshots
- Non-blocking to runtime main loop
- Behavior Tree Automation: Dedicated tick thread in BTRuntime
- Runs at configured rate (default 10Hz)
- Only active in AUTO mode
- Accesses state via thread-safe StateCache API
- HTTP Server: Embedded httplib server with thread pool
- Configurable thread pool size (default 40)
- Concurrent request handling
- Thread-safe access to all runtime components
- Provider Communication: Serialized per-provider
- Per-provider mutex locks ensure atomic call execution
- Prevents concurrent calls to same provider process
- Multiple providers can be called in parallel
- Synchronization:
- StateCache:
std::mutexguards device state map - CallRouter: Per-provider
std::mutexfor call serialization - EventEmitter: Lock-free queue with atomic operations
- ModeManager:
std::mutexguards mode transitions
- StateCache:
Configuration
runtime:
mode: MANUAL # MANUAL (default), AUTO, IDLE
providers:
- id: sim0
command: path/to/anolis-provider-sim
args: []
- id: modbus0
command: path/to/anolis-provider-modbus
args: ["--port", "/dev/ttyUSB0"]
polling:
interval_ms: 500
logging:
level: info
Extension Points
Future layers plug in via:
- State Cache:
get_device_state(),get_signal_value() - Call Router:
execute_call(device, function, args) - Registry:
get_device(),get_all_devices()
HTTP gateway, Behavior Trees, and CLI will use these APIs only.
Deferred SDK Layout (L11)
include/ vs src/ repository split is intentionally deferred out of Phase 35 to avoid mixing packaging churn with
runtime correctness hardening.
Entry criteria for that dedicated SDK-surface phase:
- Public API boundary is explicitly versioned (consumer-facing headers and compatibility policy documented).
- Export/install model is defined in CMake (
install(TARGETS ...), header install roots, package config strategy). - External consumer use-case is present (at least one out-of-tree build consuming installed headers).
- Include path migration plan is prepared with compatibility shims for one transition window.