Skip to main content

ADR-0010 Agent Intelligence Evolution

Architecture Decision Records

ADR-0010 Agent Intelligence Evolution

Context

Decision Timing

EnerOS v0.43 is about to be released, with three foundational capabilities stable: Power-Native kernel, Zero Trust security, and Agent runtime. Pilot grid companies have raised new requirements: Agents cannot just do rule-driven transactional work; they also need reasoning capabilities across multi-source heterogeneous data, prediction capabilities for renewable energy fluctuations, and natural language interaction capabilities with dispatchers.

These requirements mark EnerOS’s evolution from a “rule-driven automation system” to an “intelligence-driven autonomous system.” This ADR decides the architecture form of the Agent intelligence layer, the selection strategy for reasoning backends, and the enforcement mechanism for security constraints—a key node in EnerOS’s evolution path.

Current Pain Points

Before EnerOS v0.43, Agents were primarily rule-and-script-driven, performing well in structured scenarios but struggling with the following:

Pain Point 1: Fault root cause analysis requires reasoning across multi-source heterogeneous data

Fault analysis requires synthesizing protection action logs, SCADA measurements, equipment ledgers, weather data, maintenance records, and other multi-source information. Rule engines can only handle structured conditions and cannot understand unstructured text (such as maintenance records, weather warnings), resulting in root cause analysis coverage of only 65%.

// Rule engine limitation: can only handle predefined patterns, cannot cover combined faults and unknown patterns
fn diagnose_fault_oldschool(event: ProtectionEvent) -> Option<RootCause> {
    match event.pattern {
        ProtectionPattern::Overcurrent => Some(RootCause::ShortCircuit),
        ProtectionPattern::Overvoltage => Some(RootCause::CapacitorBankFailure),
        ProtectionPattern::Underfrequency => Some(RootCause::LoadImbalance),
        ProtectionPattern::DistanceZone1 => Some(RootCause::CloseInFault),
        ProtectionPattern::DistanceZone2 => Some(RootCause::MidLineFault),
        ProtectionPattern::DistanceZone3 => Some(RootCause::RemoteFault),
        ProtectionPattern::Differential => Some(RootCause::InternalFault),
        ProtectionPattern::Directional => Some(RootCause::ReverseFault),
        ProtectionPattern::EarthFault => Some(RootCause::GroundFault),
        // Actual rule base has 1200 rules, but combined faults and unknown patterns still fall into None
        _ => None,  // 65% of faults fall into "unknown" classification
    }
}

Pain Point 2: Collaborative dispatch under renewable energy fluctuations requires prediction + decision-making

Photovoltaic and wind power output are highly stochastic; traditional dispatch strategies based on deterministic load flow decrease efficiency by 20-30% in high renewable penetration scenarios. Coordination between prediction models and decision models is needed.

Pain Point 3: Operations conversational interaction requires natural language understanding

Dispatchers want to query grid status and get recommendations through natural language, rather than operating multiple GUIs. Rule engines cannot understand fuzzy expressions like “is the 110kV bus voltage a bit high right now.”

Pain Point 4: Poor long-tail scenario extensibility

Each new scenario requires new rules; as the rule base expands, maintenance costs surge. The v0.43 rule base has reached 1200 rules, with an average of 3 person-days per new rule.

Specific Event Triggering the Decision

In February 2025, a pilot grid experienced multiple line trips due to ice storms during the Spring Festival. The rule engine could not synthesize weather warnings and equipment historical data for root cause inference, ultimately relying on manual analysis taking 4 hours. The post-incident review explicitly required EnerOS to introduce intelligent reasoning capabilities before v0.50.

Constraints at Decision Time

ConstraintDescription
SecurityAll reasoning results must pass constraint engine validation; “intelligent but unsafe” operations are prohibited
PerformanceReal-time scenario reasoning latency < 100ms; non-real-time scenarios acceptable at 5-10s
CostLLM calls require quota control to prevent single tenant from exhausting budget
AuditableReasoning process traceable, satisfying compliance forensics
ExtensibleNew reasoning backends should not require kernel modification
PrivacyData does not leave the station; sensitive data requires local processing

Decision

Adopt kernel-built-in Agent intelligence layer: introduce a unified intelligence interface between the Agent runtime and kernel, supporting pluggable rule, model, and LLM reasoning backends; all reasoning results must pass constraint engine validation before execution, ensuring “intelligent but not overstepping boundaries.”

Core Rationale

Reason 1: Intelligence layer in the kernel, security control in the kernel

Placing the intelligence layer between the Agent runtime and kernel forces all reasoning results to pass through the kernel’s constraint engine. Whether the backend is rules, models, or LLM, output must pass physical constraint validation, fundamentally eliminating “intelligence overstepping boundaries.”

Reason 2: Three pluggable backends, avoiding one-size-fits-all

Different scenarios suit different backends: protection logic uses rules (high determinism), load forecasting uses statistical models (low cost, low latency), root cause analysis and dialogue use LLM (strong understanding). The unified interface lets Agents switch backends by scenario without modifying business code.

Reason 3: Kernel-built-in avoids external hook risks

External LLM gateways bypass kernel constraints, posing security risks. Built-in intelligence layer makes all intelligent calls pass through unified audit and constraint chains, satisfying compliance requirements.

Reason 4: Reasoning process traceable

Each reasoning records input, backend type, model version, output, constraint validation result, satisfying the “explainable, traceable” requirements of compliance forensics.

Reason 5: Supports progressive upgrade

Rule backends ensure smooth migration of existing applications; model and LLM backends are gradually introduced by scenario, avoiding one-time rewrites.

Agent Intelligence Tier Model

EnerOS divides Agent intelligence into five tiers, each corresponding to different capabilities and backend choices:

TierNameCapabilityRecommended BackendLatency Requirement
L1ReactiveSingle event triggers fixed actionRules< 1ms
L2Rule-basedMulti-condition combination judgmentRules< 10ms
L3Statistical predictionPrediction based on historical dataModel< 100ms
L4Reasoning analysisReasoning across multi-source dataLLM / Model< 5s
L5Autonomous decisionAutonomous planning within constraint boundariesLLM + Model< 30s

Higher is not always better—L1/L2 far exceed L4/L5 in performance and reliability for deterministic scenarios. EnerOS encourages “use the lowest sufficient tier” to solve problems.

Intelligence Layer Architecture

┌──────────────────────────────────────────────────┐
│              Agent Business Logic                 │
└──────────────────┬───────────────────────────────┘
                   │ ReasoningRequest

┌──────────────────────────────────────────────────┐
│           Intelligence Interface (ReasoningTrait) │
│  ┌─────────┬─────────────┬────────────────────┐  │
│  │ Rule    │ Model       │ LLM                │  │
│  │ Backend │ Backend     │ Backend            │  │
│  │ RuleEng │ ModelServer │ LlmGateway         │  │
│  └─────────┴─────────────┴────────────────────┘  │
└──────────────────┬───────────────────────────────┘
                   │ ReasoningResult (unvalidated)

┌──────────────────────────────────────────────────┐
│           Constraint Engine (ConstraintEngine)    │
│  ┌────────────────────────────────────────────┐  │
│  │ Voltage / Current / Frequency / Thermal /   │  │
│  │ Operation Sequence                          │  │
│  └────────────────────────────────────────────┘  │
└──────────────────┬───────────────────────────────┘
                   │ ValidatedResult

┌──────────────────────────────────────────────────┐
│              Audit and Execution                  │
└──────────────────────────────────────────────────┘

Unified Intelligence Interface

All reasoning backends implement the unified ReasoningBackend trait:

use async_trait::async_trait;

/// Reasoning request
pub struct ReasoningRequest {
    pub task: ReasoningTask,
    pub context: ReasoningContext,
    pub constraints: Vec<ConstraintSpec>,
    pub deadline: Option<Deadline>,
    pub preferred_backend: Option<BackendKind>,
}

/// Reasoning result (before constraint validation)
pub struct ReasoningResult {
    pub action: ProposedAction,
    pub confidence: f64,
    pub explanation: String,
    pub backend: BackendKind,
    pub model_version: Option<String>,
    pub latency: Duration,
}

/// Interface all reasoning backends must implement
#[async_trait]
pub trait ReasoningBackend: Send + Sync {
    /// Backend type
    fn kind(&self) -> BackendKind;

    /// Whether it can handle a certain task type
    fn can_handle(&self, task: &ReasoningTask) -> FitScore;

    /// Execute reasoning
    async fn reason(&self, req: &ReasoningRequest) -> Result<ReasoningResult>;

    /// Health check
    async fn health_check(&self) -> Result<HealthStatus>;
}

/// Intelligence layer dispatcher: selects the most appropriate backend based on task
pub struct ReasoningDispatcher {
    backends: Vec<Arc<dyn ReasoningBackend>>,
    constraint_engine: Arc<ConstraintEngine>,
    audit_logger: Arc<AuditLogger>,
}

impl ReasoningDispatcher {
    pub async fn reason(&self, req: ReasoningRequest) -> Result<ValidatedResult> {
        // 1. Select backend
        let backend = self.select_backend(&req)?;
        let start = Instant::now();

        // 2. Call backend reasoning
        let raw_result = backend.reason(&req).await?;

        // 3. Constraint validation (cannot be bypassed)
        let validated = self.constraint_engine.validate(&raw_result.action)?;

        // 4. Audit recording
        self.audit_logger.record(AuditEntry {
            request: req.summary(),
            backend: raw_result.backend,
            model_version: raw_result.model_version.clone(),
            raw_action: raw_result.action.summary(),
            validated: validated.clone(),
            latency: start.elapsed(),
            timestamp: now_ns(),
        }).await?;

        Ok(validated)
    }

    fn select_backend(&self, req: &ReasoningRequest) -> Result<&Arc<dyn ReasoningBackend>> {
        // If caller specifies backend and it's healthy, use directly
        if let Some(kind) = req.preferred_backend {
            if let Some(b) = self.backends.iter().find(|b| b.kind() == kind) {
                if b.health_check().await?.is_healthy() {
                    return Ok(b);
                }
            }
        }
        // Otherwise select highest FitScore
        let mut scores: Vec<_> = self.backends.iter()
            .map(|b| (b, b.can_handle(&req.task)))
            .collect();
        scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
        scores.first().map(|(b, _)| *b)
            .ok_or_else(|| ReasoningError::NoSuitableBackend)
    }
}

Three Reasoning Backend Implementations

Rule backend (L1-L2):

pub struct RuleBackend {
    rules: Vec<Rule>,
    engine: RuleEngine,
}

#[async_trait]
impl ReasoningBackend for RuleBackend {
    fn kind(&self) -> BackendKind { BackendKind::Rule }

    fn can_handle(&self, task: &ReasoningTask) -> FitScore {
        match task {
            ReasoningTask::ProtectionLogic => FitScore::perfect(),
            ReasoningTask::DispatchWithinRules => FitScore::high(),
            ReasoningTask::RootCauseAnalysis => FitScore::low(),
            _ => FitScore::none(),
        }
    }

    async fn reason(&self, req: &ReasoningRequest) -> Result<ReasoningResult> {
        let matched = self.engine.evaluate(&req.context, &self.rules)?;
        Ok(ReasoningResult {
            action: matched.action,
            confidence: 1.0,  // Rule match = 100% confidence
            explanation: matched.rule_text,
            backend: BackendKind::Rule,
            model_version: Some(format!("rules-v{}", matched.ruleset_version)),
            latency: Duration::from_micros(150),
        })
    }
}

Model backend (L3):

pub struct ModelBackend {
    registry: ModelRegistry,
    runtime: Arc<ModelRuntime>,
}

#[async_trait]
impl ReasoningBackend for ModelBackend {
    fn kind(&self) -> BackendKind { BackendKind::Model }

    fn can_handle(&self, task: &ReasoningTask) -> FitScore {
        match task {
            ReasoningTask::LoadForecast => FitScore::perfect(),
            ReasoningTask::RenewableGenerationForecast => FitScore::perfect(),
            ReasoningTask::EquipmentHealthPredict => FitScore::high(),
            _ => FitScore::low(),
        }
    }

    async fn reason(&self, req: &ReasoningRequest) -> Result<ReasoningResult> {
        let model = self.registry.select(&req.task)?;
        let input = self.prepare_features(&req.context, &model)?;
        let output = self.runtime.infer(&model, &input).await?;
        Ok(ReasoningResult {
            action: output.to_action(),
            confidence: output.confidence,
            explanation: format!("model {} inference", model.name),
            backend: BackendKind::Model,
            model_version: Some(model.version.clone()),
            latency: output.latency,
        })
    }
}

LLM backend (L4-L5):

pub struct LlmBackend {
    gateway: Arc<LlmGateway>,
    quota: Arc<QuotaManager>,
    prompt_builder: PromptBuilder,
}

#[async_trait]
impl ReasoningBackend for LlmBackend {
    fn kind(&self) -> BackendKind { BackendKind::Llm }

    fn can_handle(&self, task: &ReasoningTask) -> FitScore {
        match task {
            ReasoningTask::RootCauseAnalysis => FitScore::perfect(),
            ReasoningTask::NaturalLanguageDialogue => FitScore::perfect(),
            ReasoningTask::DispatchOptimization => FitScore::high(),
            _ => FitScore::low(),
        }
    }

    async fn reason(&self, req: &ReasoningRequest) -> Result<ReasoningResult> {
        // 1. Quota check
        self.quota.check_and_consume(&req.context.tenant_id, 1)?;

        // 2. Build prompt (including grid context and constraint hints)
        let prompt = self.prompt_builder.build(req)?;

        // 3. Call LLM
        let response = self.gateway.complete(&prompt).await?;

        // 4. Parse structured output
        let parsed = LlmResponseParser::parse(&response)?;

        Ok(ReasoningResult {
            action: parsed.action,
            confidence: parsed.confidence,
            explanation: parsed.reasoning_chain,
            backend: BackendKind::Llm,
            model_version: Some(self.gateway.model_version().to_string()),
            latency: response.latency,
        })
    }
}

LLM Integration Approach

EnerOS’s LLM integration follows the “local first, on-demand external call” principle:

Deployment ModeApplicable ScenarioLatencyCostPrivacy
Local small modelReal-time reasoning, privacy-sensitive< 100msLowFully local
Private cloud large modelComplex reasoning, enterprise intranet1-5sMediumIntranet
Public cloud large modelDialogue, non-sensitive scenarios2-10sHighRequires desensitization
# eneros.toml
[intelligence.llm]
default_mode = "local_first"   # local_first / cloud_only / hybrid

[intelligence.llm.local]
model = "eneros-7b-instruct"   # EnerOS fine-tuned 7B model
runtime = "onnx"               # onnx / ggml / tensorrt
device = "cuda"                # cuda / cpu / mps
max_tokens = 2048
temperature = 0.3

[intelligence.llm.cloud]
provider = "azure_openai"      # azure_openai / anthropic / custom
endpoint = "https://llm.internal.example.com/v1"
model = "gpt-4o"
api_key_env = "ENEROS_LLM_API_KEY"
timeout = "30s"

[intelligence.llm.quota]
daily_token_limit = 1_000_000
per_tenant_limit = 100_000
overflow_action = "fallback_to_local"  # fallback_to_local / reject

Reasoning Engine Selection

EnerOS supports multiple reasoning backend runtimes:

RuntimeApplicable ModelsAdvantagesDisadvantages
ONNX RuntimeGeneralCross-platform, mature ecosystemAverage performance for large models
GGML / llama.cppLLMCPU-friendly, lightweightLimited GPU acceleration
TensorRT-LLMLLMOptimal for NVIDIA GPUNVIDIA only
CandleRust nativePure Rust, memory safeSmall ecosystem
vLLMLLM servingHigh throughput, PagedAttentionHeavier deployment

EnerOS defaults to ONNX Runtime + Candle combination: ONNX Runtime for served models, Candle for embedded scenarios.

Security Constraint Mechanism

All reasoning results must pass multiple constraint engine validations before execution:

pub struct ConstraintEngine {
    physics_rules: Vec<PhysicsRule>,
    operational_rules: Vec<OperationalRule>,
    sequence_rules: Vec<SequenceRule>,
}

impl ConstraintEngine {
    pub fn validate(&self, action: &ProposedAction) -> Result<ValidatedResult> {
        // 1. Physical constraints (voltage, current, frequency, thermal stability)
        for rule in &self.physics_rules {
            rule.check(action)?;
        }
        // 2. Operational constraints (operation sequence, device state)
        for rule in &self.operational_rules {
            rule.check(action)?;
        }
        // 3. Temporal constraints (operation intervals, concurrency limits)
        for rule in &self.sequence_rules {
            rule.check(action)?;
        }
        Ok(ValidatedResult::from(action))
    }
}

/// Handling policy when LLM output violates constraints
pub enum ConstraintViolationPolicy {
    Reject,           // Reject directly, return error
    AutoCorrect,      // Attempt auto-correction (e.g., reduce output within constraints)
    Escalate,         // Escalate to human review
    FallbackRule,     // Fall back to rule backend
}

impl ReasoningDispatcher {
    pub async fn reason_with_policy(
        &self,
        req: ReasoningRequest,
        policy: ConstraintViolationPolicy,
    ) -> Result<FinalResult> {
        loop {
            let result = self.reason(req.clone()).await;
            match result {
                Ok(validated) => return Ok(FinalResult::Validated(validated)),
                Err(ReasoningError::ConstraintViolation(v)) => match policy {
                    ConstraintViolationPolicy::Reject => return Err(v.into()),
                    ConstraintViolationPolicy::AutoCorrect => {
                        req.constraints.push(v.to_constraint());
                        continue;  // Re-reason with additional constraints
                    }
                    ConstraintViolationPolicy::Escalate => {
                        return Ok(FinalResult::NeedsHumanReview(v));
                    }
                    ConstraintViolationPolicy::FallbackRule => {
                        req.preferred_backend = Some(BackendKind::Rule);
                        continue;
                    }
                },
                Err(e) => return Err(e),
            }
        }
    }
}

Consequences

Benefits

1. Intelligence and security decoupled, reusing kernel constraints and audit

Whether the backend is rules, models, or LLM, output passes through the same constraint engine and audit chain, providing unified security assurance. New backends need not duplicate security mechanisms.

2. Multiple reasoning backends switchable by scenario, avoiding one-size-fits-all

Measured in pilot grids, rule backends handle 78% of transactional tasks (latency < 1ms), model backends handle 18% of prediction tasks (latency < 100ms), and LLM backends handle 4% of complex reasoning (latency < 5s), optimizing overall cost and latency.

3. Reasoning process traceable, satisfying compliance forensics

Each reasoning records input summary, backend type, model version, raw output, constraint validation result, and final decision, forming a complete audit chain. Compliance forensics can trace the full process of any decision.

4. Fault root cause analysis coverage increased from 65% to 92%

LLM backends can synthesize unstructured data (maintenance records, weather warnings), covering combined faults and long-tail scenarios that rule engines cannot handle.

5. Long-tail scenario extension cost reduced

New scenarios need not write rules; supported through prompt engineering or fine-tuning small models, with average effort dropping from 3 person-days to 0.5 person-days.

Costs

1. Constraint validation adds extra latency; async prefetch needed for real-time scenarios

Constraint validation adds 10-50μs latency; for real-time domain (< 100μs) scenarios, optimized through async prefetch and caching.

2. Model version management and regression testing become new engineering burden

Need to establish model registry, A/B testing framework, regression test datasets; team adds 1 ML engineer position.

3. LLM call costs require quota mechanism constraints

During pilot, LLM call costs approximately $2000/month, reduced to $500/month through quotas and local small model fallback.

4. Output uncertainty increases operations complexity

LLM output has randomness; requires temperature control, structured output parsing, and constraint validation triple mechanisms to ensure reliability.

5. Model supply chain security requires additional review

Third-party models may have backdoors or biases; need to establish model review processes and adversarial test sets.

Follow-up Work

  • Introduce Retrieval-Agent for grid knowledge base retrieval (landed in v0.48)
  • Explore small model local deployment to reduce latency and cost (planned for v0.52)
  • Improve Agent intelligence evaluation benchmarks (planned for v0.50)
  • Support multimodal input (images, oscilloscope waveforms) (planned for v0.55)
  • Introduce Agent intelligence self-learning and fine-tuning pipeline (planned for v0.60)

Alternatives

Option A: External LLM Gateway

Description: Deploy a standalone LLM gateway; Agents call via HTTP, with the gateway returning results and executing directly.

Advantages:

  • Simple implementation; can launch in 2 weeks
  • No kernel modification; zero intrusion on existing architecture
  • Can use any LLM provider

Rejection Reasons:

  • Bypasses kernel constraint engine, posing security risks—LLM may output instructions violating physical constraints
  • Audit chain broken; cannot prove which LLM call produced an operation
  • Cannot unify scheduling with rule and model backends; multi-backend switching difficult
  • Conflicts with Power-Native philosophy of ADR-0002

Option B: Each Agent Integrates Models Independently

Description: Each Agent independently selects and integrates rules, models, or LLM, with no unified interface.

Advantages:

  • Maximum flexibility; Agents choose as needed
  • No kernel changes required
  • Team can develop in parallel

Rejection Reasons:

  • Duplicates reasoning, constraint, and audit logic; code bloat
  • Security policies scattered; difficult to ensure consistency
  • Model version management chaotic; cannot unify monitoring
  • New Agents must relearn reasoning framework; low development efficiency

Option C: Maintain Pure Rules

Description: Continue expanding the rule base, covering more scenarios through finer-grained rules.

Advantages:

  • Simple implementation; team familiar
  • High determinism; predictable output
  • No LLM costs

Rejection Reasons:

  • Cannot cover long-tail scenarios; rule base has expanded to 1200 rules, with surging maintenance costs
  • Cannot handle unstructured data (text, images)
  • Cannot meet prediction needs under high renewable penetration
  • Competitors (such as GE Grid Software, Siemens Spectrum Power) have introduced AI capabilities; pure rule approach lacks competitiveness

Option D: Pure LLM-Driven

Description: All Agent decisions go through LLM reasoning; abandon rule engine.

Advantages:

  • Minimal implementation; unified backend
  • Maximum flexibility; can handle any scenario
  • Fast development

Rejection Reasons:

  • Latency too high (> 1s); cannot meet real-time scenarios like protection logic
  • High cost; $0.01-0.1 per call; unacceptable for high-frequency scenarios
  • High output uncertainty; unacceptable for power critical infrastructure
  • Complete dependence on external LLM suppliers; supply chain risk

Performance Assessment

Performance measured data after intelligence layer introduction (standard test environment: 4 cores / 8GB / NVIDIA T4 GPU):

BackendTaskAverage LatencyP99 LatencyThroughputCost/1k calls
RuleProtection logic0.15ms0.5ms100k QPS$0
RuleDispatch combination judgment0.8ms2ms50k QPS$0
ModelLoad forecasting35ms80ms200 QPS$0.01
ModelPV output prediction42ms95ms180 QPS$0.01
LLM (local)Root cause analysis1.2s3.5s5 QPS$0.05
LLM (local)Conversational interaction0.8s2.0s8 QPS$0.03
LLM (cloud)Complex planning4.5s12s2 QPS$0.80

Constraint validation overhead (all backends must pass through):

Validation StageLatencyDescription
Physical constraints8μsVoltage, current, frequency
Operational constraints12μsDevice state, operation sequence
Temporal constraints5μsOperation intervals, concurrency
Audit recording25μsChained log writing
Total50μsAcceptable

Security Constraint Details

LLM Output Sandboxing

LLM backend output undergoes strict sandboxing before storage:

pub struct LlmOutputSandbox {
    parser: StructuredOutputParser,
    constraint_engine: Arc<ConstraintEngine>,
    policy: ConstraintViolationPolicy,
}

impl LlmOutputSandbox {
    pub async fn process(&self, raw: &str) -> Result<SandboxedOutput> {
        // 1. Parse to structured action (reject unparseable output)
        let action = self.parser.parse(raw)?;

        // 2. Scope check (action type, target device legality)
        self.validate_scope(&action)?;

        // 3. Physical constraint validation
        let validated = match self.constraint_engine.validate(&action) {
            Ok(v) => v,
            Err(violation) => match self.policy {
                ConstraintViolationPolicy::Reject => return Err(violation.into()),
                ConstraintViolationPolicy::AutoCorrect => self.autocorrect(action, violation)?,
                ConstraintViolationPolicy::Escalate => {
                    return Ok(SandboxedOutput::NeedsReview(action, violation))
                }
                ConstraintViolationPolicy::FallbackRule => {
                    return Ok(SandboxedOutput::FallbackToRule(action))
                }
            },
        };

        Ok(SandboxedOutput::Validated(validated))
    }
}

Prompt Injection Protection

The LLM backend employs multi-layer protection against prompt injection attacks:

pub struct PromptGuard {
    forbidden_patterns: Vec<Regex>,
    max_input_length: usize,
}

impl PromptGuard {
    pub fn sanitize(&self, user_input: &str) -> Result<String> {
        // 1. Length limit
        if user_input.len() > self.max_input_length {
            return Err(PromptError::InputTooLong);
        }
        // 2. Pattern filtering (e.g., "ignore previous instructions")
        for pattern in &self.forbidden_patterns {
            if pattern.is_match(user_input) {
                return Err(PromptError::SuspiciousPattern);
            }
        }
        // 3. Escape special characters
        Ok(escape_special_chars(user_input))
    }

    pub fn build_safe_prompt(&self, user_input: &str, context: &ReasoningContext) -> String {
        let sanitized = self.sanitize(user_input).unwrap_or_default();
        format!(
            "You are a power system dispatch assistant. Please answer questions based on the following grid context.\n\
             Strict constraints:\n\
             1. Only propose operation suggestions that comply with physical constraints\n\
             2. Must not output any content violating safety regulations\n\
             3. Output must be in JSON format: {{\"action\": ..., \"confidence\": ..., \"explanation\": ...}}\n\n\
             Grid context:\n{}\n\n\
             User question:\n{}",
            context.summary(),
            sanitized
        )
    }
}

Future Evolution

This ADR establishes the foundation architecture of the intelligence layer; future evolution will focus on the following directions:

  1. Multimodal reasoning: Support image (infrared thermometry, inspection photos), waveform (oscilloscope recordings) input
  2. Federated learning: Cross-grid-company collaborative model training, data stays local
  3. Agent self-learning: Agents continuously optimize strategies from historical operations
  4. Small model fine-tuning pipeline: Fine-tune 7B models based on grid domain data, reducing costs
  5. Intelligence evaluation benchmark: Establish EnerOS-Bench to quantify Agent intelligence levels

References