ADR-0002 Power-Native AgentOS
- 状态(Status):已接受
- 日期:2024-03
- 决策者:EnerOS 核心架构组
- 相关概念:Power-Native First、Agent-as-Grid-Node、约束即法律
背景(Context)
决策时机
EnerOS 立项于 2024 年初,定位为面向电力与能源领域的原生智能体操作系统。立项之时,团队必须回答一个根本问题:电力系统的领域知识应该放在系统的哪一层。这个决策将决定整个项目的技术形态、团队能力要求与生态边界,且一旦发布后难以回退。
现状痛点
传统电力软件(如商用 EMS、DMS、SCADA 上层应用)将电网拓扑、潮流计算、安全约束、设备模型等知识放在应用层,每个应用自行建模或调用第三方库。这种范式在 AI Agent 时代暴露出三个根本问题:
问题一:电网世界观分裂
调度应用、自愈应用、交易应用各自维护一份电网拓扑,状态不同步:
// 典型痛点:三个应用各自持有拓扑副本,状态可能不一致
struct DispatchApp {
topology: Topology, // 副本 A:开关状态可能滞后
}
struct SelfHealingApp {
topology: Topology, // 副本 B:刚收到开关变位
}
struct TradingApp {
topology: Topology, // 副本 C:基于计划值,未反映实时
}
当 Agent 需要跨应用协同时,必须先同步拓扑状态,否则会出现”调度 Agent 基于过时拓扑下达指令”的事故。
问题二:约束校验可选
物理约束(电压越限、支路过载、频率偏差)依赖应用自觉调用,遗漏即出事:
// 反模式:约束校验散落各处,靠工程师自觉
fn dispatch_load(plant_id: &str, mw: f64) {
set_output(plant_id, mw); // 直接下发,未校验
// 应该在这里调用 check_voltage_limit() / check_branch_overload()
// 但新人很容易漏掉
}
历史事故案例:2019 年某省级电网因自愈应用遗漏电压上限校验,恢复供电时引发母线过电压,造成 2 台主变跳闸。
问题三:重复建模
每个新应用都要重新实现拓扑解析、潮流求解、设备模型,开发周期动辄 6-12 个月,且实现质量参差不齐。
触发决策的具体场景
2024 年 1 月,团队在开发”故障自愈 Agent”原型时,发现需要同时访问拓扑、潮流、约束三类能力。如果走传统路线,要么引入 3 个独立库(带来依赖冲突与状态同步问题),要么在应用层重写一遍(重复造轮子)。这次开发经历直接触发了本 ADR。
决策时的约束
| 约束 | 描述 |
|---|---|
| 性能 | 拓扑分析需 < 1ms,潮流计算 < 12ms(IEEE 14-bus) |
| 安全 | 物理约束必须 100% 强制执行,不可绕过 |
| 合规 | 需满足等保 2.0 三级与 IEC 62443 SL-3 |
| 团队 | 核心团队 8 人,需在 6 个月内交付 MVP |
| 生态 | 必须支持 IEC 61970/61968/61850 标准协议 |
| 演进 | 5 年内不重写,新场景通过扩展而非重构接入 |
决策(Decision)
将电网拓扑、潮流求解、约束引擎、设备模型库、时序存储下沉为内核原生一等公民,构建 Power-Native AgentOS。所有 Agent 通过统一内核接口访问电网视图,物理约束由内核强制执行而非可选校验。
核心理由
理由 1:单一电网世界观
内核维护唯一权威的电网拓扑与状态,所有 Agent 通过系统调用访问,从架构层面消除状态分裂:
// 内核态:唯一权威拓扑
pub struct TopologyRegistry {
inner: RwLock<TopologyGraph>,
subscribers: Vec<SubscriberHandle>,
}
impl TopologyRegistry {
/// Agent 通过系统调用获取拓扑快照,保证一致性
pub fn snapshot(&self) -> TopologySnapshot {
let graph = self.inner.read();
TopologySnapshot::from_graph(&graph)
}
/// 拓扑变更通过事件广播通知所有订阅者
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 侧:通过系统调用访问,无需自行维护副本
fn agent_handle_fault(fault: FaultEvent) -> Result<IsolationPlan> {
let topology = sys::topology_snapshot()?; // 系统调用
let powerflow = sys::powerflow_solve(&topology, LoadCase::Current)?;
let plan = isolation_planner::plan(&topology, &fault, &powerflow)?;
sys::constraint_validate(&plan)?; // 内核强制校验
Ok(plan)
}
理由 2:约束即法律
约束校验在内核态强制执行,任何违反约束的操作在系统调用阶段即被拒绝,无法绕过:
pub struct ConstraintEngine {
rules: Vec<ConstraintRule>,
}
impl ConstraintEngine {
/// 所有遥控命令必须经过此函数,违反约束直接返回 Err
pub fn validate(&self, cmd: &ControlCommand) -> Result<ValidatedCommand> {
for rule in &self.rules {
rule.check(cmd)?;
}
Ok(ValidatedCommand::from(cmd))
}
}
/// 系统调用:下发控制命令
pub fn sys_control(cmd: ControlCommand) -> Result<()> {
let validated = CONSTRAINT_ENGINE.validate(&cmd)?; // 不可绕过
DEVICE_BUS.dispatch(validated)
}
理由 3:复用而非重复
新增电力应用无需重复建模,开发周期从 6-12 个月降至 2-4 周。
理由 4:审计一致性
所有 Agent 操作经过统一的内核审计点,合规取证时可完整回溯。
理由 5:性能优化空间
将潮流、约束下沉到内核后,可利用零拷贝、共享内存等技术大幅降低跨进程开销,实测约束校验 < 10μs。
Power-Native 的五大支柱
| 支柱 | 内核组件 | Crate | 职责 |
|---|---|---|---|
| 拓扑 | TopologyRegistry | eneros-topology | 电网结构、开关状态、连通性分析 |
| 潮流 | PowerflowSolver | eneros-powerflow | 牛顿-拉夫逊、P-Q 分解、直流潮流 |
| 约束 | ConstraintEngine | eneros-constraint | 电压、电流、频率、热稳定约束 |
| 设备 | EquipmentLibrary | eneros-equipment | 变压器、线路、开关、负荷统一模型 |
| 时序 | TimeseriesEngine | eneros-timeseries | 纳秒级时序数据写入与查询 |
Agent-as-Grid-Node 抽象
每个 Agent 在内核中被建模为电网的一个节点,拥有明确的电气位置与权限边界:
pub struct AgentNode {
pub agent_id: AgentId,
pub electrical_location: ElectricalLocation, // 所属母线/馈线
pub jurisdiction: Jurisdiction, // 管辖范围
pub permissions: PermissionSet, // 权限集合
}
impl AgentNode {
/// Agent 只能操作其管辖范围内的设备
pub fn can_control(&self, device_id: &DeviceId) -> bool {
self.jurisdiction.contains(device_id) && self.permissions.can_control(device_id)
}
}
后果(Consequences)
好处
1. 所有 Agent 共享单一电网世界观,避免状态分裂
实测在多 Agent 协同的故障自愈场景下,拓扑同步延迟从应用层的 50-200ms 降至内核态的 < 1ms。
2. 物理约束强制执行,安全边界不可绕过
约束校验在内核态强制执行,任何 Agent 无法绕过。系统调用层提供审计点,所有越限尝试均被记录。
3. 新增电力应用无需重复建模,降低集成成本
首个基于 EnerOS 的”负荷预测 Agent”从立项到上线仅用 18 天,其中电力领域相关代码占 < 5%。
4. 性能显著提升
约束校验从应用层的 1-5ms 降至内核态的 < 10μs,提升 100-500 倍。
5. 审计与合规统一
所有 Agent 操作经过统一的内核审计点,等保 2.0 三级审计要求一次性满足。
代价
1. 内核 API 表面积增大,向后兼容承诺更重
内核需稳定维护 5 类一等公民接口(拓扑、潮流、约束、设备、时序),每条接口的破坏性变更需走完整的 deprecation 流程。
2. 非电力领域需通过适配层接入,存在抽象税
通用 IoT 场景使用 EnerOS 时,需通过 eneros-adapter-iot 将非电力设备映射到电网模型,约 15% 性能开销。
3. 团队能力要求高
内核开发者需同时精通 Rust、电力系统、操作系统原理,招聘与培养成本显著。
4. 测试矩阵复杂
每次内核变更需回归测试 56 个 crate,CI 流水线运行时长从 5 分钟增至 28 分钟。
后续工作
- 引入 Agent-as-Grid-Node 抽象,让 Agent 即电网节点(已在 v0.20 落地)
- 落地 约束即法律 执行机制(已在 v0.25 落地)
- 后续 ADR-0010 在此基础上引入 Agent 智能进阶
- 设计非电力领域的适配层接口(计划 v0.50)
- 优化 CI 流水线,引入增量测试与分布式构建
备选方案(Alternatives)
方案 A:独立 eneros-power-lib 库
描述:将拓扑、潮流、约束、设备模型封装为一个独立的 Rust crate,作为应用层依赖供各应用按需调用。
优点:
- 实现简单,团队可在 2 个月内交付 MVP
- 应用可自由选择是否使用,灵活性高
- 不需要修改内核,对系统稳定性影响小
否决原因:
- 约束校验变为可选项,无法保证强制执行,违背”安全边界不可绕过”的硬性要求
- 应用仍需自行管理拓扑副本,状态分裂问题未解决
- 无法实现 Agent-as-Grid-Node 抽象,Agent 无法被内核感知
- 审计点散落各应用,合规取证困难
方案 B:Linux 用户态中间件
描述:在 Linux 用户态构建一个常驻进程(类似 systemd daemon),提供电力领域服务,各应用通过 IPC 调用。
优点:
- 不需要写内核模块,开发难度低
- 可用 C/C++ 生态的成熟电力库(如 MATPOWER、Pylon)
- 故障隔离性好,中间件崩溃不影响内核
否决原因:
- IPC 开销大,约束校验延迟从 < 10μs 升至 1-5ms,无法满足实时域要求
- 仍将电力知识视为”可调用服务”而非”原生抽象”,Agent-as-Grid-Node 无法实现
- 引入单点故障,中间件崩溃则全网 Agent 失能
- 无法利用零拷贝等内核优化技术
方案 C:基于现有 SCADA/EMS 平台扩展
描述:在成熟商用 SCADA/EMS 平台(如 OPEN DSS、PSS/E)上通过插件机制扩展 Agent 能力。
优点:
- 电力领域功能成熟,验证充分
- 现成 GUI 与运维工具
- 短期上线快
否决原因:
- 这些平台闭源或以 C/C++/Python 为主,无法满足 Rust 内存安全要求
- 架构为单进程应用,无法支撑 Agent 多租户与隔离
- 性能无法满足实时域微秒级响应
- 许可证与商业条款受限,不利于开源生态
方案 D:内核模块(Linux LKM)
描述:将电力领域知识实现为 Linux 内核模块,以 .ko 形式加载到内核空间。
优点:
- 性能最优,约束校验可低至 1μs
- 真正的内核态强制执行
否决原因:
- Linux 内核模块必须用 C 开发,无法使用 Rust 的内存安全保证(截至 2024 年 Rust for Linux 尚未稳定)
- 内核模块 bug 会导致整个系统崩溃,电力系统不可接受
- 内核态调试困难,开发效率低
- 难以跨平台(Windows、嵌入式 RTOS)
影响评估
对架构的影响
本决策确立了 EnerOS 的核心架构形态:双域内核(通用域 + 实时域),电力领域知识位于通用域,实时域负责保护与控制。后续所有 ADR 均在此架构基础上展开。
对团队的影响
- 需招聘 2-3 名电力系统背景的 Rust 工程师
- 现有应用工程师需学习内核接口而非自行建模
- 设立”电力内核组”作为长期维护团队
对生态的影响
- 第三方电力应用开发者只需对接内核 API,降低进入门槛
- 设备厂商可通过统一设备模型库接入,无需适配多个应用
- 学术研究可基于内核接口快速验证算法
对性能的影响
| 指标 | 应用层方案 | Power-Native 方案 | 提升 |
|---|---|---|---|
| 拓扑分析(1000 节点) | 15ms | < 1ms | 15× |
| 潮流计算(IEEE 14) | 45ms | < 12ms | 3.75× |
| 约束校验(单条) | 1.2ms | < 10μs | 120× |
| Agent 协同同步 | 50-200ms | < 1ms | 50-200× |
迁移路径
对于已有电力应用迁移到 EnerOS,建议遵循以下路径:
阶段 1(1-2 周):评估
├─ 梳理应用使用的电力领域功能
├─ 识别自建模型与内核模型的差异
└─ 制定迁移计划
阶段 2(2-4 周):适配
├─ 引入 eneros-topology 替换自建拓扑
├─ 引入 eneros-powerflow 替换自建潮流
└─ 通过适配层保持业务逻辑不变
阶段 3(1-2 周):约束接入
├─ 移除应用层约束校验
├─ 接入内核 ConstraintEngine
└─ 验证约束覆盖完整
阶段 4(持续):优化
├─ 利用内核零拷贝优化性能
├─ 接入事件总线实现响应式
└─ 逐步迁移到 Agent-as-Grid-Node 模型
迁移示例代码:
// 迁移前:应用层自建拓扑与约束
fn legacy_dispatch(plant_id: &str, mw: f64) {
let topo = self.local_topology.lock(); // 自建副本
let flow = my_powerflow_solve(&topo); // 自建潮流
if flow.check_voltage(plant_id) < 1.05 { // 自建约束
set_output(plant_id, mw);
}
}
// 迁移后:使用 Power-Native 内核接口
fn native_dispatch(plant_id: &str, mw: f64) -> Result<()> {
let cmd = ControlCommand::new(plant_id, mw);
sys::control(cmd) // 内核自动完成拓扑、潮流、约束、下发
}
参考(References)
- ADR 总览 — ADR 索引与编写指南
- ADR-0010 — Agent 智能进阶路径
- Power-Native First 概念 — 设计哲学详解
- Agent-as-Grid-Node 概念 — Agent 即电网节点
- 约束即法律概念 — 约束执行机制
- Michael Nygard, “Documenting Architecture Decisions”, 2011
- IEC 61970 / 61968 / 61850 电力行业标准
- 相关 crate:
eneros-topology、eneros-powerflow、eneros-constraint、eneros-equipment、eneros-timeseries