Skip to main content

v0.31.0 Release Notes

EnerOS v0.31.0

Release Date: 2026-01-18 Codename: Cortex Git Tag: v0.31.0 Support Status: Stable Total Crates: 54 (6 new) Test Cases: 7200+ (700 new)

Overview

EnerOS v0.31.0 “Cortex” is a key release of the “intelligence” phase, marking EnerOS’s official transition from “scripted Agents” to “cognitive Agents.” Cortex is taken from the cerebral cortex, signifying that the Agent runtime has gained reasoning and decision-making capabilities similar to the cerebral cortex. This release integrates Large Language Models (LLMs) as first-class citizens of the operating system, enabling each Agent bound to a grid node to perform task planning in natural language, invoke kernel power services in a structured manner, and possess intent recognition and autonomous tool-calling capabilities.

The core design philosophy of Cortex continues “constraints as law” — any action suggestions generated by the LLM must pass mandatory validation and projection through the kernel’s ConstraintEngine; AI cannot bypass physical constraints. This allows EnerOS to maintain grid operation security while introducing LLM uncertainty. This release introduces five core capabilities: LLM Integration Framework, Natural Language Query, Intent Recognition, Tool Calling, and Agent Reasoning Chain.

Key Metrics

MetricValueDescription
LLM invocation latency P99320msRemote backend
Intent recognition accuracy96.8%Power domain test set
Tool call success rate99.2%Includes constraint projection
NLQ response< 500msIncludes vector retrieval
New Crates6AI-related
New tests700+Includes 120 end-to-end

New Features

1. LLM Integration Framework

Adds the eneros-llm crate, unifying access to mainstream LLM backends, supporting local inference (llama.cpp / candle) and remote APIs (OpenAI / Anthropic / Azure / Tongyi Qianwen / Wenxin Yiyan), with four built-in governance capabilities: token budget, streaming output, timeout circuit breaking, and usage auditing.

Backend Adapters

BackendProtocolDeploymentApplicable Scenario
OpenAI CompatibleHTTP / SSERemoteGeneral, low latency
Anthropic ClaudeHTTP / SSERemoteLong context, reasoning
Azure OpenAIHTTP / SSERemoteEnterprise compliance
llama.cppLocalEdge / offlineData stays on-site
candleLocalPure RustEmbedded
Tongyi QianwenHTTPRemoteChina compliance
Wenxin YiyanHTTPRemoteChina compliance

Unified Invocation Interface

use eneros_llm::{LlmClient, LlmRequest, Message, Role};

// Create client (backend specified via config file)
let client = LlmClient::from_config("llm.openai").await?;

// Build request
let request = LlmRequest::new()
    .system("You are a power dispatch assistant. Answers must comply with grid operation procedures.")
    .user(Message::new(Role::User, "Current 110kV bus voltage is 0.88pu, does it need adjustment?"))
    .temperature(0.2)
    .max_tokens(1024)
    .timeout(Duration::seconds(15));

// Streaming output
let mut stream = client.stream(request).await?;
while let Some(chunk) = stream.next().await {
    print!("{}", chunk.delta?);
}

Token Budget and Auditing

Each Agent has an independent token budget account to prevent runaway LLM calls from consuming excessive resources:

use eneros_llm::budget::{TokenBudget, BudgetPolicy};

let budget = TokenBudget::new()
    .daily_limit(500_000)
    .per_request_limit(8192)
    .policy(BudgetPolicy::RejectWhenExceeded);

agent.set_llm_budget(budget);

All LLM calls are automatically recorded in audit logs, including prompt summary, model version, token count, duration, and caller Agent ID, satisfying MLPS 2.0 Level 3 requirements.

2. Natural Language Query

Adds the eneros-nlq crate, allowing operators to query grid operation status in natural language. The NLQ engine converts natural language into structured kernel system calls, combining vector retrieval with schema-guided generation.

Query Example

use eneros_nlq::{NlqEngine, NlqQuery};

let engine = NlqEngine::new(&ctx)
    .with_schema(&topology_schema)
    .with_history_db(&timeseries)
    .build().await?;

// Natural language query
let query = NlqQuery::from_text("What was the voltage trend of bus 5 between 14:00 and 16:00 yesterday?");
let result = engine.execute(query).await?;

// result contains: original question, generated system call, query results, natural language answer
println!("{}", result.answer);
// "During 2026-01-17 14:00-16:00, bus 5 voltage decreased from 1.02pu to 0.97pu,
//  with a minimum of 0.96pu at 15:23, not exceeding limits but close to the lower bound."

Query Capability Matrix

Query TypeExampleUnderlying syscall
Real-time status”What’s the current voltage of bus 1”GetBus / QueryTimeSeries
Historical trend”Load curve over the past 24 hours”QueryTimeSeries
Topology analysis”Which bus is generator G1 connected to”GetBus / GetNeighbors
Constraint check”Is line L-3 overloaded”CheckConstraint
Fault tracing”What caused the last trip”QueryEventLog
Statistical aggregation”What’s the average frequency today”QueryTimeSeries + Avg

3. Intent Recognition

Adds the eneros-intent crate, classifying user natural language input into power domain intents to determine subsequent execution paths. Intent recognition combines rule matching with LLM classification to achieve high accuracy and low latency.

Intent Classification System

Primary IntentSecondary IntentExample
QueryReal-time query”What’s the voltage of bus 1”
QueryHistorical query”Yesterday’s load curve”
QueryTopology query”Where is G1 connected”
OperationSwitch operation”Close switch 42”
OperationOutput adjustment”Set generator G2 output to 80MW”
AnalysisFault analysis”Why did the line trip”
AnalysisTrend prediction”What’s tomorrow’s load peak”
ReportGenerate report”Generate this week’s operation report”

Intent Recognizer

use eneros_intent::{IntentRecognizer, Intent};

let recognizer = IntentRecognizer::new()
    .with_rules(&power_domain_rules)   // Rule matching first
    .with_llm_fallback(client.clone()) // Use LLM when rules don't match
    .confidence_threshold(0.85);

let intent = recognizer.recognize("Put the capacitor at bus 3 into operation").await?;

match intent {
    Intent::Operation { action, target, params } => {
        // action = "switch_capacitor"
        // target = BusId(3)
        // params = { state: "on" }
        ctx.syscall(ExecuteCommand(action.into())).await?;
    }
    _ => { /* ... */ }
}

4. Tool Calling

Adds the eneros-tool crate, registering kernel power services as LLM-callable tools, enabling the LLM to autonomously decide which kernel capabilities to invoke. Tool call results are fed back to the LLM for the next reasoning step, forming a ReAct loop.

Tool Registration

use eneros_tool::{ToolRegistry, Tool, ToolSchema};

let mut registry = ToolRegistry::new();

// Register kernel power services as tools
registry.register(Tool::new("get_bus_voltage")
    .description("Get the real-time voltage (per-unit) of a specified bus")
    .schema(ToolSchema::object()
        .field("bus_id", "integer", "Bus number"))
    .handler(|args, ctx| {
        let bus_id: u32 = args.get("bus_id")?;
        let bus = ctx.syscall(GetBus { id: bus_id })?;
        Ok(json!({ "voltage_pu": bus.voltage.magnitude }))
    }))?;

registry.register(Tool::new("set_generation")
    .description("Set generator output (MW)")
    .schema(ToolSchema::object()
        .field("bus_id", "integer", "Bus where the generator is located")
        .field("mw", "number", "Target output"))
    .handler(|args, ctx| {
        let bus_id: u32 = args.get("bus_id")?;
        let mw: f64 = args.get("mw")?;
        ctx.syscall(SetGeneratorOutput { bus: bus_id, mw })?;
        Ok(json!({ "success": true }))
    }))?;

ReAct Loop

use eneros_reasoning::{ReactAgent, ReactConfig};

let agent = ReactAgent::new(client, registry)
    .max_steps(8)              // Max 8 reasoning rounds
    .step_timeout(Duration::seconds(10));

let result = agent.run(
    "Bus 1 voltage is low, please adjust generator output to restore it above 0.98pu"
).await?;

// Agent autonomously executes:
// 1. Call get_bus_voltage(bus_id=1) -> 0.92pu
// 2. Call list_generators(island=...) -> [G1, G2]
// 3. Call set_generation(bus_id=1, mw=90)
// 4. Call get_bus_voltage(bus_id=1) -> 0.99pu
// 5. Output: "Adjusted generator G1 output to 90MW, bus 1 voltage restored to 0.99pu"

Tool Calling Security Model

All tool calls go through constraint validation by the security gateway:

let result = agent.run("Set generator G1 output to 200MW").await?;
// Kernel constraint validation: G1 rated capacity 100MW
// LLM suggestion projected to 100MW, with warning returned

5. Agent Reasoning Chain

Adds the eneros-reasoning crate, providing structured reasoning chains, supporting three reasoning modes: ReAct, Plan-and-Execute, and Tree-of-Thoughts.

Reasoning Mode Comparison

ModeApplicable ScenarioStepsLatencyAccuracy
ReActReal-time interaction1-8LowHigh
Plan-and-ExecuteComplex tasks10-50MediumVery high
Tree-of-ThoughtsDecision optimization20-100HighHighest

Improvements

  • Agent Communication: Inter-agent message serialization changed from JSON to bincode, latency reduced by 40%
  • Vector Retrieval: eneros-memory vector index supports HNSW, retrieval latency reduced from 50ms to 3ms
  • Token Auditing: Audit logs support multi-dimensional aggregation queries by Agent, model, and time period
  • Constraint Projection: Constraint projector adds sequential quadratic programming (SQP) solver, convergence speed improved 3x
  • Security Gateway: Tool call path adds sandbox isolation, restricting file system and network access

Bug Fixes

  • Fixed eneros-llm connection pool exhaustion during streaming output causing subsequent requests to block (#3102)
  • Fixed eneros-reasoning not properly rolling back after tool call failure in ReAct loop (#3108)
  • Fixed eneros-intent rule matcher memory out-of-bounds during Chinese tokenization (#3115)
  • Fixed eneros-nlq timezone conversion error in timeseries queries causing 8-hour offset (#3120)
  • Fixed eneros-tool tool schema validation handling nested objects incorrectly (#3125)

Breaking Changes

  • Agent::run signature change: Added llm_ctx: &LlmContext parameter; original signature migrated to Agent::run_classical
  • ToolRegistry::register: Return type changed from () to Result<()>; conflicts must be handled
  • Intent enum: Intent::Unknown renamed to Intent::Unrecognized

Upgrade Guide

  1. Update the eneros dependency in Cargo.toml to 0.31.0
  2. Run eneros migrate 0.31.0 to auto-migrate Agent code
  3. Configure LLM backend and token budget in eneros.toml
  4. Refer to migration docs to adjust Agent::run signature

Acknowledgments

Thanks to the 22 contributors who submitted 340+ commits, and to power industry partners for domain knowledge validation.