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
| Metric | Value | Description |
|---|---|---|
| LLM invocation latency P99 | 320ms | Remote backend |
| Intent recognition accuracy | 96.8% | Power domain test set |
| Tool call success rate | 99.2% | Includes constraint projection |
| NLQ response | < 500ms | Includes vector retrieval |
| New Crates | 6 | AI-related |
| New tests | 700+ | 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
| Backend | Protocol | Deployment | Applicable Scenario |
|---|---|---|---|
| OpenAI Compatible | HTTP / SSE | Remote | General, low latency |
| Anthropic Claude | HTTP / SSE | Remote | Long context, reasoning |
| Azure OpenAI | HTTP / SSE | Remote | Enterprise compliance |
| llama.cpp | Local | Edge / offline | Data stays on-site |
| candle | Local | Pure Rust | Embedded |
| Tongyi Qianwen | HTTP | Remote | China compliance |
| Wenxin Yiyan | HTTP | Remote | China 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(×eries)
.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 Type | Example | Underlying 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 Intent | Secondary Intent | Example |
|---|---|---|
| Query | Real-time query | ”What’s the voltage of bus 1” |
| Query | Historical query | ”Yesterday’s load curve” |
| Query | Topology query | ”Where is G1 connected” |
| Operation | Switch operation | ”Close switch 42” |
| Operation | Output adjustment | ”Set generator G2 output to 80MW” |
| Analysis | Fault analysis | ”Why did the line trip” |
| Analysis | Trend prediction | ”What’s tomorrow’s load peak” |
| Report | Generate 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
| Mode | Applicable Scenario | Steps | Latency | Accuracy |
|---|---|---|---|---|
| ReAct | Real-time interaction | 1-8 | Low | High |
| Plan-and-Execute | Complex tasks | 10-50 | Medium | Very high |
| Tree-of-Thoughts | Decision optimization | 20-100 | High | Highest |
Improvements
- Agent Communication: Inter-agent message serialization changed from JSON to bincode, latency reduced by 40%
- Vector Retrieval:
eneros-memoryvector 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-llmconnection pool exhaustion during streaming output causing subsequent requests to block (#3102) - Fixed
eneros-reasoningnot properly rolling back after tool call failure in ReAct loop (#3108) - Fixed
eneros-intentrule matcher memory out-of-bounds during Chinese tokenization (#3115) - Fixed
eneros-nlqtimezone conversion error in timeseries queries causing 8-hour offset (#3120) - Fixed
eneros-tooltool schema validation handling nested objects incorrectly (#3125)
Breaking Changes
Agent::runsignature change: Addedllm_ctx: &LlmContextparameter; original signature migrated toAgent::run_classicalToolRegistry::register: Return type changed from()toResult<()>; conflicts must be handledIntentenum:Intent::Unknownrenamed toIntent::Unrecognized
Upgrade Guide
- Update the
enerosdependency inCargo.tomlto0.31.0 - Run
eneros migrate 0.31.0to auto-migrate Agent code - Configure LLM backend and token budget in
eneros.toml - Refer to migration docs to adjust
Agent::runsignature
Acknowledgments
Thanks to the 22 contributors who submitted 340+ commits, and to power industry partners for domain knowledge validation.