跳到主内容

ADR-0002 Power-Native AgentOS

架构决策记录

ADR-0002 Power-Native AgentOS

背景(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职责
拓扑TopologyRegistryeneros-topology电网结构、开关状态、连通性分析
潮流PowerflowSolvereneros-powerflow牛顿-拉夫逊、P-Q 分解、直流潮流
约束ConstraintEngineeneros-constraint电压、电流、频率、热稳定约束
设备EquipmentLibraryeneros-equipment变压器、线路、开关、负荷统一模型
时序TimeseriesEngineeneros-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< 1ms15×
潮流计算(IEEE 14)45ms< 12ms3.75×
约束校验(单条)1.2ms< 10μs120×
Agent 协同同步50-200ms< 1ms50-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)