ADR-0002 Power-Native AgentOS
- Status: Accepted
- Date: 2024-03
- Decision Makers: EnerOS Core Architecture Group
- Related Concepts: Power-Native First, Agent-as-Grid-Node, Constraint-as-Law
Context
Decision Timing
EnerOS was initiated in early 2024, positioned as a native AgentOS for the power and energy domain. At inception, the team had to answer a fundamental question: which layer of the system should hold the power system domain knowledge. This decision would determine the entire project’s technical form, team capability requirements, and ecosystem boundaries, and was difficult to reverse once released.
Current Pain Points
Traditional power software (such as commercial EMS, DMS, and SCADA upper-layer applications) places grid topology, load flow calculation, security constraints, equipment models, and other knowledge at the application layer, with each application modeling independently or calling third-party libraries. This paradigm exposes three fundamental problems in the AI Agent era:
Problem 1: Fragmented Grid Worldview
Dispatch applications, self-healing applications, and trading applications each maintain their own grid topology copies, with states out of sync:
// Typical pain point: three applications each hold a topology copy, states may be inconsistent
struct DispatchApp {
topology: Topology, // Copy A: switch state may lag
}
struct SelfHealingApp {
topology: Topology, // Copy B: just received switch change
}
struct TradingApp {
topology: Topology, // Copy C: based on schedule values, not reflecting real-time
}
When Agents need to collaborate across applications, they must first synchronize topology state, otherwise incidents like “dispatch Agent issues commands based on outdated topology” occur.
Problem 2: Optional Constraint Validation
Physical constraints (voltage limits, branch overloads, frequency deviations) depend on application-level voluntary calls; omissions lead to incidents:
// Anti-pattern: constraint validation scattered everywhere, relying on engineer diligence
fn dispatch_load(plant_id: &str, mw: f64) {
set_output(plant_id, mw); // Direct dispatch, no validation
// Should call check_voltage_limit() / check_branch_overload() here
// But newcomers easily miss it
}
Historical incident case: In 2019, a provincial grid omitted voltage upper limit validation in its self-healing application, causing bus overvoltage during power restoration, tripping 2 main transformers.
Problem 3: Redundant Modeling
Each new application must reimplement topology parsing, load flow solving, and equipment models, with development cycles of 6-12 months and uneven quality.
Specific Scenario Triggering the Decision
In January 2024, while developing the “Fault Self-Healing Agent” prototype, the team found it needed to access topology, load flow, and constraint capabilities simultaneously. Following the traditional path, they would either introduce 3 independent libraries (bringing dependency conflicts and state synchronization issues) or rewrite at the application layer (reinventing the wheel). This development experience directly triggered this ADR.
Constraints at Decision Time
| Constraint | Description |
|---|---|
| Performance | Topology analysis < 1ms, load flow calculation < 12ms (IEEE 14-bus) |
| Security | Physical constraints must be 100% enforced, not bypassable |
| Compliance | Must satisfy MLPS 2.0 Level 3 and IEC 62443 SL-3 |
| Team | Core team of 8, must deliver MVP within 6 months |
| Ecosystem | Must support IEC 61970/61968/61850 standard protocols |
| Evolution | No rewrite within 5 years; new scenarios integrated through extension, not refactoring |
Decision
Sink grid topology, load flow solving, constraint engine, equipment model library, and time-series storage as kernel-native first-class citizens, building a Power-Native AgentOS. All Agents access the grid view through unified kernel interfaces, with physical constraints enforced by the kernel rather than as optional checks.
Core Rationale
Reason 1: Single Grid Worldview
The kernel maintains the唯一 authoritative grid topology and state; all Agents access through system calls, eliminating state fragmentation at the architectural level:
// Kernel mode: sole authoritative topology
pub struct TopologyRegistry {
inner: RwLock<TopologyGraph>,
subscribers: Vec<SubscriberHandle>,
}
impl TopologyRegistry {
/// Agent obtains topology snapshot via system call, ensuring consistency
pub fn snapshot(&self) -> TopologySnapshot {
let graph = self.inner.read();
TopologySnapshot::from_graph(&graph)
}
/// Topology changes broadcast to all subscribers via events
pub fn apply_switch_change(&self, switch_id: &str, state: SwitchState) -> Result<()> {
let mut graph = self.inner.write();
graph.update_switch(switch_id, state)?;
let event = TopologyEvent::SwitchChanged { switch_id, state };
self.broadcast(event)?;
Ok(())
}
}
// Agent side: access via system calls, no need to maintain own copy
fn agent_handle_fault(fault: FaultEvent) -> Result<IsolationPlan> {
let topology = sys::topology_snapshot()?; // System call
let powerflow = sys::powerflow_solve(&topology, LoadCase::Current)?;
let plan = isolation_planner::plan(&topology, &fault, &powerflow)?;
sys::constraint_validate(&plan)?; // Kernel-enforced validation
Ok(plan)
}
Reason 2: Constraint-as-Law
Constraint validation is enforced in kernel mode; any operation violating constraints is rejected at the system call stage and cannot be bypassed:
pub struct ConstraintEngine {
rules: Vec<ConstraintRule>,
}
impl ConstraintEngine {
/// All remote commands must pass through this function; constraint violations return Err directly
pub fn validate(&self, cmd: &ControlCommand) -> Result<ValidatedCommand> {
for rule in &self.rules {
rule.check(cmd)?;
}
Ok(ValidatedCommand::from(cmd))
}
}
/// System call: dispatch control command
pub fn sys_control(cmd: ControlCommand) -> Result<()> {
let validated = CONSTRAINT_ENGINE.validate(&cmd)?; // Cannot be bypassed
DEVICE_BUS.dispatch(validated)
}
Reason 3: Reuse Rather Than Reduplicate
New power applications need not duplicate modeling; development cycles drop from 6-12 months to 2-4 weeks.
Reason 4: Audit Consistency
All Agent operations pass through unified kernel audit points, enabling complete traceability for compliance forensics.
Reason 5: Performance Optimization Space
After sinking load flow and constraints to the kernel, zero-copy, shared memory, and other techniques can significantly reduce cross-process overhead; measured constraint validation < 10μs.
Five Pillars of Power-Native
| Pillar | Kernel Component | Crate | Responsibility |
|---|---|---|---|
| Topology | TopologyRegistry | eneros-topology | Grid structure, switch state, connectivity analysis |
| Load Flow | PowerflowSolver | eneros-powerflow | Newton-Raphson, P-Q decoupling, DC power flow |
| Constraint | ConstraintEngine | eneros-constraint | Voltage, current, frequency, thermal stability constraints |
| Equipment | EquipmentLibrary | eneros-equipment | Unified models for transformers, lines, switches, loads |
| Time-series | TimeseriesEngine | eneros-timeseries | Nanosecond time-series data write and query |
Agent-as-Grid-Node Abstraction
Each Agent is modeled as a node in the grid within the kernel, with clear electrical location and permission boundaries:
pub struct AgentNode {
pub agent_id: AgentId,
pub electrical_location: ElectricalLocation, // Belonging bus/feeder
pub jurisdiction: Jurisdiction, // Jurisdiction scope
pub permissions: PermissionSet, // Permission set
}
impl AgentNode {
/// Agent can only operate devices within its jurisdiction
pub fn can_control(&self, device_id: &DeviceId) -> bool {
self.jurisdiction.contains(device_id) && self.permissions.can_control(device_id)
}
}
Consequences
Benefits
1. All Agents share a single grid worldview, avoiding state fragmentation
Measured in multi-Agent collaborative fault self-healing scenarios, topology synchronization latency dropped from 50-200ms at the application layer to < 1ms at the kernel level.
2. Physical constraints enforced, security boundary cannot be bypassed
Constraint validation is enforced in kernel mode; no Agent can bypass it. The system call layer provides audit points; all limit violations are recorded.
3. New power applications need not duplicate modeling, reducing integration cost
The first “Load Forecasting Agent” based on EnerOS took only 18 days from inception to launch, with power domain code accounting for < 5%.
4. Significant performance improvement
Constraint validation dropped from 1-5ms at the application layer to < 10μs at the kernel level, a 100-500x improvement.
5. Unified audit and compliance
All Agent operations pass through unified kernel audit points, satisfying MLPS 2.0 Level 3 audit requirements in one pass.
Costs
1. Larger kernel API surface area, heavier backward compatibility commitment
The kernel must stably maintain 5 types of first-class citizen interfaces (topology, load flow, constraints, equipment, time-series); each interface’s breaking changes require a full deprecation process.
2. Non-power domains require adapter layers, incurring abstraction tax
When general IoT scenarios use EnerOS, eneros-adapter-iot must map non-power devices to the grid model, with approximately 15% performance overhead.
3. High team capability requirements
Kernel developers must be proficient in Rust, power systems, and operating system principles simultaneously; recruiting and training costs are significant.
4. Complex test matrix
Each kernel change requires regression testing of 56 crates; CI pipeline runtime increased from 5 minutes to 28 minutes.
Follow-up Work
- Introduce Agent-as-Grid-Node abstraction, making Agents grid nodes (landed in v0.20)
- Implement Constraint-as-Law enforcement mechanism (landed in v0.25)
- Subsequent ADR-0010 introduces Agent intelligence evolution on this basis
- Design adapter layer interfaces for non-power domains (planned for v0.50)
- Optimize CI pipeline with incremental testing and distributed builds
Alternatives
Option A: Standalone eneros-power-lib Library
Description: Encapsulate topology, load flow, constraints, and equipment models as a standalone Rust crate, used as an application-layer dependency for applications to call as needed.
Advantages:
- Simple implementation; team can deliver MVP in 2 months
- Applications freely choose whether to use, high flexibility
- No kernel modification, minimal impact on system stability
Rejection Reasons:
- Constraint validation becomes optional, cannot guarantee enforcement, violating the “security boundary cannot be bypassed” hard requirement
- Applications still need to manage their own topology copies; state fragmentation unresolved
- Cannot implement Agent-as-Grid-Node abstraction; Agents cannot be perceived by the kernel
- Audit points scattered across applications; compliance forensics difficult
Option B: Linux User-space Middleware
Description: Build a resident process in Linux user space (similar to systemd daemon) providing power domain services, with applications calling via IPC.
Advantages:
- No need to write kernel modules; low development difficulty
- Can use mature power libraries from the C/C++ ecosystem (such as MATPOWER, Pylon)
- Good fault isolation; middleware crashes do not affect the kernel
Rejection Reasons:
- High IPC overhead; constraint validation latency rises from < 10μs to 1-5ms, failing to meet real-time domain requirements
- Still treats power knowledge as “callable service” rather than “native abstraction”; Agent-as-Grid-Node cannot be implemented
- Introduces single point of failure; middleware crash disables all Agents network-wide
- Cannot leverage kernel optimization techniques like zero-copy
Option C: Extend Existing SCADA/EMS Platforms
Description: Extend Agent capabilities through plugin mechanisms on mature commercial SCADA/EMS platforms (such as OPEN DSS, PSS/E).
Advantages:
- Mature power domain functionality, fully validated
- Ready-made GUI and operations tools
- Fast short-term launch
Rejection Reasons:
- These platforms are closed-source or primarily C/C++/Python, failing to meet Rust memory safety requirements
- Architecture is single-process applications, cannot support Agent multi-tenancy and isolation
- Performance cannot meet real-time domain microsecond response
- License and commercial terms restricted, unfavorable for open-source ecosystem
Option D: Kernel Module (Linux LKM)
Description: Implement power domain knowledge as Linux kernel modules, loaded into kernel space as .ko files.
Advantages:
- Best performance; constraint validation can be as low as 1μs
- True kernel-mode enforcement
Rejection Reasons:
- Linux kernel modules must be developed in C, cannot use Rust’s memory safety guarantees (as of 2024, Rust for Linux is not yet stable)
- Kernel module bugs cause entire system crashes; unacceptable for power systems
- Kernel-mode debugging is difficult; low development efficiency
- Difficult to cross-platform (Windows, embedded RTOS)
Impact Assessment
Impact on Architecture
This decision established EnerOS’s core architecture form: dual-domain kernel (general domain + real-time domain), with power domain knowledge in the general domain and the real-time domain responsible for protection and control. All subsequent ADRs build on this architecture.
Impact on Team
- Need to recruit 2-3 Rust engineers with power system backgrounds
- Existing application engineers need to learn kernel interfaces rather than self-modeling
- Establish a “Power Kernel Group” as a long-term maintenance team
Impact on Ecosystem
- Third-party power application developers only need to interface with kernel APIs, lowering entry barriers
- Equipment vendors can connect through unified equipment model libraries, without adapting to multiple applications
- Academic research can quickly validate algorithms based on kernel interfaces
Impact on Performance
| Metric | Application-layer approach | Power-Native approach | Improvement |
|---|---|---|---|
| Topology analysis (1000 nodes) | 15ms | < 1ms | 15× |
| Load flow calculation (IEEE 14) | 45ms | < 12ms | 3.75× |
| Constraint validation (single) | 1.2ms | < 10μs | 120× |
| Agent collaborative sync | 50-200ms | < 1ms | 50-200× |
Migration Path
For existing power applications migrating to EnerOS, the following path is recommended:
Phase 1 (1-2 weeks): Assessment
├─ Survey power domain functions used by the application
├─ Identify differences between self-built models and kernel models
└─ Develop migration plan
Phase 2 (2-4 weeks): Adaptation
├─ Introduce eneros-topology to replace self-built topology
├─ Introduce eneros-powerflow to replace self-built load flow
└─ Keep business logic unchanged through adapter layer
Phase 3 (1-2 weeks): Constraint Integration
├─ Remove application-layer constraint validation
├─ Connect to kernel ConstraintEngine
└─ Verify complete constraint coverage
Phase 4 (ongoing): Optimization
├─ Leverage kernel zero-copy for performance optimization
├─ Connect to event bus for reactive implementation
└─ Gradually migrate to Agent-as-Grid-Node model
Migration example code:
// Before migration: application-layer self-built topology and constraints
fn legacy_dispatch(plant_id: &str, mw: f64) {
let topo = self.local_topology.lock(); // Self-built copy
let flow = my_powerflow_solve(&topo); // Self-built load flow
if flow.check_voltage(plant_id) < 1.05 { // Self-built constraints
set_output(plant_id, mw);
}
}
// After migration: using Power-Native kernel interfaces
fn native_dispatch(plant_id: &str, mw: f64) -> Result<()> {
let cmd = ControlCommand::new(plant_id, mw);
sys::control(cmd) // Kernel automatically completes topology, load flow, constraints, dispatch
}
References
- ADR Overview — ADR index and writing guidelines
- ADR-0010 — Agent intelligence evolution path
- Power-Native First concept — Design philosophy in detail
- Agent-as-Grid-Node concept — Agent as grid node
- Constraint-as-Law concept — Constraint enforcement mechanism
- Michael Nygard, “Documenting Architecture Decisions”, 2011
- IEC 61970 / 61968 / 61850 power industry standards
- Related crates:
eneros-topology,eneros-powerflow,eneros-constraint,eneros-equipment,eneros-timeseries