Skip to main content

microscope_memory/
planning.rs

1//! Planning — céllebontás és hierarchikus tervezés
2
3//!
4
5//! Képes:
6
7//! - Célok lebontása részcélokra (HTN planning)
8
9//! - Akciótervek készítése erőforrás becsléssel
10
11//! - Terv végrehajtás monitorozás és replanning
12
13//! - Kockázat becslés és alternatív tervek
14
15//!
16
17//! Kapcsolódások:
18
19//! - executive.rs → terv végrehajtás
20
21//! - heuristic_decision.rs → döntéshozatal
22
23//! - mental_sandbox.rs → előzetes szimuláció
24
25//! - agus.rs → stressz alapú terv módosítás
26
27//! - pattern_recognition.rs → minta illesztés
28
29use std::collections::HashMap;
30
31use std::sync::{Arc, Mutex, RwLock};
32
33use std::time::{SystemTime, UNIX_EPOCH};
34
35#[derive(Debug, Clone, PartialEq)]
36
37pub enum GoalStatus {
38    Active,
39    InProgress,
40    Completed,
41    Failed(String),
42    Blocked(String),
43    Suspended,
44}
45
46#[derive(Debug, Clone, PartialEq)]
47
48pub enum PlanStatus {
49    Draft,
50    InProgress,
51    Completed,
52    Failed(String),
53    /// A step was hard-blocked by the commitment gate.
54    Blocked(String),
55    Replanning,
56}
57
58#[derive(Debug, Clone)]
59
60pub struct Goal {
61    pub id: u64,
62    pub name: String,
63    pub description: String,
64
65    pub priority: u8,
66    pub deadline_ms: Option<u64>,
67
68    pub status: GoalStatus,
69    pub parent_id: Option<u64>,
70
71    pub subgoals: Vec<u64>,
72    pub progress: f64,
73    pub created_ms: u64,
74}
75
76#[derive(Debug, Clone)]
77
78pub struct Action {
79    pub id: u64,
80    pub name: String,
81    pub module: String,
82
83    pub estimated_cost: f64,
84    pub estimated_duration_ms: u64,
85
86    pub dependencies: Vec<u64>,
87    pub preconditions: Vec<String>,
88
89    pub effects: Vec<String>,
90    pub risk_score: f64,
91}
92
93#[derive(Debug, Clone)]
94
95pub struct Plan {
96    pub id: u64,
97    pub name: String,
98    pub goal_id: u64,
99
100    pub actions: Vec<Action>,
101    pub status: PlanStatus,
102
103    pub total_cost: f64,
104    pub total_duration_ms: u64,
105
106    pub risk_score: f64,
107    pub alternatives: Vec<u64>,
108
109    pub created_ms: u64,
110    pub executed_step: usize,
111}
112
113#[derive(Debug, Clone)]
114
115pub struct PlanningConfig {
116    pub max_depth: u32,
117    pub min_confidence: f64,
118
119    pub max_alternatives: usize,
120    pub auto_replan: bool,
121
122    pub risk_tolerance: f64,
123}
124
125impl Default for PlanningConfig {
126    fn default() -> Self {
127        Self {
128            max_depth: 5,
129            min_confidence: 0.3,
130            max_alternatives: 3,
131            auto_replan: true,
132            risk_tolerance: 0.7,
133        }
134    }
135}
136
137pub struct Planner {
138    goals: Arc<RwLock<HashMap<u64, Goal>>>,
139
140    plans: Arc<RwLock<HashMap<u64, Plan>>>,
141
142    config: Arc<RwLock<PlanningConfig>>,
143
144    next_id: Arc<RwLock<u64>>,
145
146    /// Mandatory commitment gate. Every candidate action selected by
147    /// `execute_step` must pass through this before it can run.
148    enforcement: Arc<Mutex<crate::enforcement::EnforcementEngine>>,
149}
150
151impl Default for Planner {
152    fn default() -> Self {
153        Self::new()
154    }
155}
156
157impl Planner {
158    pub fn new() -> Self {
159        Self {
160            goals: Arc::new(RwLock::new(HashMap::new())),
161
162            plans: Arc::new(RwLock::new(HashMap::new())),
163
164            config: Arc::new(RwLock::new(PlanningConfig::default())),
165
166            next_id: Arc::new(RwLock::new(1)),
167
168            enforcement: Arc::new(Mutex::new(crate::enforcement::EnforcementEngine::new())),
169        }
170    }
171
172    /// Install a shared enforcement engine so commitments added via the CLI
173    /// (or tests) are the exact same commitments the gate enforces.
174    pub fn set_enforcement(
175        &mut self,
176        enforcement: Arc<Mutex<crate::enforcement::EnforcementEngine>>,
177    ) {
178        self.enforcement = enforcement;
179    }
180
181    /// Access the shared enforcement gate (e.g. to seed commitments).
182    pub fn enforcement(&self) -> Arc<Mutex<crate::enforcement::EnforcementEngine>> {
183        Arc::clone(&self.enforcement)
184    }
185
186    fn nid(&self) -> u64 {
187        let mut id = crate::sync_guard::write_lock(&self.next_id);
188        let n = *id;
189        *id += 1;
190        n
191    }
192
193    fn now(&self) -> u64 {
194        SystemTime::now()
195            .duration_since(UNIX_EPOCH)
196            .unwrap_or_default()
197            .as_millis() as u64
198    }
199
200    pub fn add_goal(&self, name: &str, desc: &str, priority: u8, parent: Option<u64>) -> u64 {
201        let id = self.nid();
202
203        let goal = Goal {
204            id,
205            name: name.to_string(),
206            description: desc.to_string(),
207
208            priority,
209            deadline_ms: None,
210            status: GoalStatus::Active,
211
212            parent_id: parent,
213            subgoals: Vec::new(),
214            progress: 0.0,
215            created_ms: self.now(),
216        };
217
218        if let Some(pid) = parent {
219            if let Some(p) = crate::sync_guard::write_lock(&self.goals).get_mut(&pid) {
220                p.subgoals.push(id);
221            }
222        }
223
224        crate::sync_guard::write_lock(&self.goals).insert(id, goal);
225
226        id
227    }
228
229    pub fn get_goal(&self, id: u64) -> Option<Goal> {
230        crate::sync_guard::read_lock(&self.goals).get(&id).cloned()
231    }
232
233    pub fn list_goals(&self, status: Option<GoalStatus>) -> Vec<Goal> {
234        crate::sync_guard::read_lock(&self.goals)
235            .values()
236            .filter(|g| status.as_ref().is_none_or(|s| g.status == *s))
237            .cloned()
238            .collect()
239    }
240
241    /// Cél lebontása részcélokra (HTN dekompozíció)
242    pub fn decompose_goal(&self, goal_id: u64) -> Vec<u64> {
243        let goal = match crate::sync_guard::read_lock(&self.goals).get(&goal_id) {
244            Some(g) => g.clone(),
245            None => return vec![],
246        };
247
248        let _config = crate::sync_guard::read_lock(&self.config).clone();
249
250        let mut sub_ids = Vec::new();
251
252        if goal.priority > 200 {
253            return sub_ids;
254        } // atomic goal
255
256        // Domen-specifikus dekompozíció
257
258        let subgoals: Vec<(String, String, u8)> = match goal.name.to_lowercase().as_str() {
259            "learn" | "tanulás" => vec![
260                (
261                    "collect_data".to_string(),
262                    "Adatok gyűjtése".to_string(),
263                    goal.priority + 10,
264                ),
265                (
266                    "analyze".to_string(),
267                    "Elemzés és minták keresése".to_string(),
268                    goal.priority + 20,
269                ),
270                (
271                    "store".to_string(),
272                    "Eredmények tárolása".to_string(),
273                    goal.priority + 30,
274                ),
275                (
276                    "verify".to_string(),
277                    "Ellenőrzés és validálás".to_string(),
278                    goal.priority + 40,
279                ),
280            ],
281
282            "optimize" | "optimalizálás" => vec![
283                (
284                    "measure".to_string(),
285                    "Jelenlegi állapot mérése".to_string(),
286                    goal.priority + 10,
287                ),
288                (
289                    "identify".to_string(),
290                    "Szűk keresztmetszetek azonosítása".to_string(),
291                    goal.priority + 20,
292                ),
293                (
294                    "apply".to_string(),
295                    "Optimalizáció alkalmazása".to_string(),
296                    goal.priority + 30,
297                ),
298                (
299                    "verify".to_string(),
300                    "Javulás ellenőrzése".to_string(),
301                    goal.priority + 40,
302                ),
303            ],
304
305            "explore" | "exploráció" => vec![
306                (
307                    "scan".to_string(),
308                    "Felület szkenelése".to_string(),
309                    goal.priority + 10,
310                ),
311                (
312                    "sample".to_string(),
313                    "Mintavételezés".to_string(),
314                    goal.priority + 20,
315                ),
316                (
317                    "map".to_string(),
318                    "Térkép készítése".to_string(),
319                    goal.priority + 30,
320                ),
321                (
322                    "report".to_string(),
323                    "Eredmények jelentése".to_string(),
324                    goal.priority + 40,
325                ),
326            ],
327
328            _ => vec![
329                (
330                    "analyze".to_string(),
331                    format!("{} elemzése", goal.name),
332                    goal.priority + 10,
333                ),
334                (
335                    "plan".to_string(),
336                    format!("Terv készítése: {}", goal.name),
337                    goal.priority + 20,
338                ),
339                (
340                    "execute".to_string(),
341                    format!("Végrehajtás: {}", goal.name),
342                    goal.priority + 30,
343                ),
344                (
345                    "verify".to_string(),
346                    format!("Eredmény ellenőrzése: {}", goal.name),
347                    goal.priority + 40,
348                ),
349            ],
350        };
351
352        for (name, desc, prio) in subgoals {
353            let sid = self.add_goal(&name, &desc, prio, Some(goal_id));
354
355            sub_ids.push(sid);
356        }
357
358        sub_ids
359    }
360
361    /// Terv készítése egy célhoz
362    pub fn create_plan(&self, goal_id: u64) -> Plan {
363        let goal = match crate::sync_guard::read_lock(&self.goals).get(&goal_id) {
364            Some(g) => g.clone(),
365            None => return self.empty_plan(),
366        };
367
368        let _config = crate::sync_guard::read_lock(&self.config).clone();
369
370        let now = self.now();
371
372        let plan_id = self.nid();
373
374        let actions = match goal.name.to_lowercase().as_str() {
375            "collect_data" => self.make_collect_actions(),
376
377            "analyze" => self.make_analyze_actions(),
378
379            "execute" => self.make_execute_actions(&goal),
380
381            _ => self.make_generic_actions(&goal),
382        };
383
384        let total_cost: f64 = actions.iter().map(|a| a.estimated_cost).sum();
385
386        let total_dur: u64 = actions.iter().map(|a| a.estimated_duration_ms).sum();
387
388        let risk: f64 =
389            actions.iter().map(|a| a.risk_score).sum::<f64>() / actions.len().max(1) as f64;
390
391        let plan = Plan {
392            id: plan_id,
393            name: format!("plan_for_{}", goal.name),
394            goal_id,
395
396            actions,
397            status: PlanStatus::Draft,
398            total_cost,
399            total_duration_ms: total_dur,
400
401            risk_score: risk,
402            alternatives: Vec::new(),
403            created_ms: now,
404            executed_step: 0,
405        };
406
407        if let Some(g) = crate::sync_guard::write_lock(&self.goals).get_mut(&goal_id) {
408            g.status = GoalStatus::InProgress;
409        }
410
411        crate::sync_guard::write_lock(&self.plans).insert(plan_id, plan.clone());
412
413        plan
414    }
415
416    /// Select the next candidate action and submit it to the mandatory
417    /// commitment gate. A non-permitted action is **blocked** (returns an
418    /// error) instead of merely warned; only actions in `A_t^valid` advance.
419    fn gated_step(
420        &self,
421        plans: &mut std::sync::RwLockWriteGuard<'_, std::collections::HashMap<u64, Plan>>,
422        plan_id: u64,
423        justification: Option<&str>,
424    ) -> Result<Option<Action>, crate::enforcement::EnforcementError> {
425        let plan = match plans.get_mut(&plan_id) {
426            Some(p) => p,
427            None => return Ok(None),
428        };
429
430        if plan.executed_step >= plan.actions.len() {
431            plan.status = PlanStatus::Completed;
432            if let Some(g) = crate::sync_guard::write_lock(&self.goals).get_mut(&plan.goal_id) {
433                g.status = GoalStatus::Completed;
434                g.progress = 1.0;
435            }
436            return Ok(None);
437        }
438
439        let action = plan.actions[plan.executed_step].clone();
440        let event = crate::enforcement::ActionEvent {
441            actor: "planner".to_string(),
442            action: action.name.clone(),
443            content: plan.name.clone(),
444            ts_ms: self.now(),
445            scope: format!("plan:{}:goal:{}", plan.id, plan.goal_id),
446            provenance: "planner/execute_step".to_string(),
447        };
448
449        let mut gate = crate::sync_guard::mutex_lock(&self.enforcement);
450        match gate.decide(&event, justification) {
451            crate::enforcement::Decision::Allowed { .. }
452            | crate::enforcement::Decision::Overridden { .. } => {
453                // A member of A_t^valid; the decision was already appended to
454                // the audit chain inside decide().
455                plan.executed_step += 1;
456                plan.status = PlanStatus::InProgress;
457                Ok(Some(action))
458            }
459            crate::enforcement::Decision::Blocked { action, reason, .. } => {
460                plan.status = PlanStatus::Blocked(reason.clone());
461                Err(crate::enforcement::EnforcementError::Blocked { action, reason })
462            }
463            crate::enforcement::Decision::AttributionError { reason } => {
464                plan.status = PlanStatus::Failed(reason.clone());
465                Err(crate::enforcement::EnforcementError::FaultyAttribution { reason })
466            }
467        }
468    }
469
470    /// Terv végrehajtásának előre léptetése a kötelező gate-en keresztül.
471    pub fn execute_step(
472        &self,
473        plan_id: u64,
474    ) -> Result<Option<Action>, crate::enforcement::EnforcementError> {
475        let mut plans = crate::sync_guard::write_lock(&self.plans);
476        self.gated_step(&mut plans, plan_id, None)
477    }
478
479    /// A documented `justifiedOverride()` útvonal: ugyanaz a gate, de az
480    /// akció egy rögzített indoklással léphet (ha jogosult a felülírás).
481    pub fn execute_step_with_override(
482        &self,
483        plan_id: u64,
484        justification: &str,
485    ) -> Result<Option<Action>, crate::enforcement::EnforcementError> {
486        let mut plans = crate::sync_guard::write_lock(&self.plans);
487        self.gated_step(&mut plans, plan_id, Some(justification))
488    }
489
490    /// Rollback: visszalépés az utolsó végrehajtott lépésről, ha az sikertelen volt.
491    pub fn fail_step(&self, plan_id: u64, reason: &str) -> bool {
492        let mut plans = crate::sync_guard::write_lock(&self.plans);
493
494        let plan = match plans.get_mut(&plan_id) {
495            Some(p) => p,
496
497            None => return false,
498        };
499
500        if plan.executed_step == 0 {
501            return false;
502        }
503
504        plan.executed_step -= 1;
505
506        plan.status = PlanStatus::Failed(format!("step {} failed: {}", plan.executed_step, reason));
507
508        true
509    }
510
511    /// Replanning: terv újragenerálás változott körülmények esetén
512    pub fn replan(&self, plan_id: u64, reason: &str) -> Option<Plan> {
513        let plans = crate::sync_guard::write_lock(&self.plans);
514
515        let old = plans.get(&plan_id)?.clone();
516
517        let _goal = crate::sync_guard::read_lock(&self.goals)
518            .get(&old.goal_id)?
519            .clone();
520
521        drop(plans);
522
523        // Goal visszaállítása
524
525        if let Some(g) = crate::sync_guard::write_lock(&self.goals).get_mut(&old.goal_id) {
526            g.status = GoalStatus::Active;
527        }
528
529        let mut new_plan = self.create_plan(old.goal_id);
530
531        new_plan.name = format!("replan_{}_{}", old.name, reason);
532
533        new_plan.status = PlanStatus::Replanning;
534
535        new_plan.alternatives.push(old.id);
536
537        Some(new_plan)
538    }
539
540    pub fn plans_for_goal(&self, goal_id: u64) -> Vec<Plan> {
541        crate::sync_guard::read_lock(&self.plans)
542            .values()
543            .filter(|p| p.goal_id == goal_id)
544            .cloned()
545            .collect()
546    }
547
548    pub fn get_plan(&self, id: u64) -> Option<Plan> {
549        crate::sync_guard::read_lock(&self.plans).get(&id).cloned()
550    }
551
552    pub fn stats(&self) -> (usize, usize, usize) {
553        let g = crate::sync_guard::read_lock(&self.goals);
554
555        let p = crate::sync_guard::read_lock(&self.plans);
556
557        (
558            g.len(),
559            p.len(),
560            g.values()
561                .filter(|g| g.status == GoalStatus::Completed)
562                .count(),
563        )
564    }
565
566    // ─── Segéd akció készítők ────────────────────────────────────────────
567
568    fn empty_plan(&self) -> Plan {
569        Plan {
570            id: 0,
571            name: "empty".to_string(),
572            goal_id: 0,
573            actions: vec![],
574
575            status: PlanStatus::Draft,
576            total_cost: 0.0,
577            total_duration_ms: 0,
578
579            risk_score: 0.0,
580            alternatives: vec![],
581            created_ms: self.now(),
582            executed_step: 0,
583        }
584    }
585
586    fn make_collect_actions(&self) -> Vec<Action> {
587        let id = self.nid();
588
589        vec![
590            Action {
591                id: id + 1,
592                name: "query_source".to_string(),
593                module: "reader".to_string(),
594
595                estimated_cost: 0.3,
596                estimated_duration_ms: 100,
597                dependencies: vec![],
598
599                preconditions: vec!["config_ready".to_string()],
600                effects: vec!["data_collected".to_string()],
601                risk_score: 0.1,
602            },
603            Action {
604                id: id + 2,
605                name: "filter_relevant".to_string(),
606                module: "attention".to_string(),
607
608                estimated_cost: 0.2,
609                estimated_duration_ms: 50,
610                dependencies: vec![id + 1],
611
612                preconditions: vec!["data_collected".to_string()],
613                effects: vec!["data_filtered".to_string()],
614                risk_score: 0.1,
615            },
616            Action {
617                id: id + 3,
618                name: "store_results".to_string(),
619                module: "hippocampus".to_string(),
620
621                estimated_cost: 0.1,
622                estimated_duration_ms: 30,
623                dependencies: vec![id + 2],
624
625                preconditions: vec!["data_filtered".to_string()],
626                effects: vec!["data_stored".to_string()],
627                risk_score: 0.05,
628            },
629        ]
630    }
631
632    fn make_analyze_actions(&self) -> Vec<Action> {
633        let id = self.nid();
634
635        vec![
636            Action {
637                id: id + 1,
638                name: "load_data".to_string(),
639                module: "reader".to_string(),
640
641                estimated_cost: 0.1,
642                estimated_duration_ms: 20,
643                dependencies: vec![],
644
645                preconditions: vec!["data_available".to_string()],
646                effects: vec!["data_loaded".to_string()],
647                risk_score: 0.05,
648            },
649            Action {
650                id: id + 2,
651                name: "find_patterns".to_string(),
652                module: "pattern_recognition".to_string(),
653
654                estimated_cost: 0.5,
655                estimated_duration_ms: 200,
656                dependencies: vec![id + 1],
657
658                preconditions: vec!["data_loaded".to_string()],
659                effects: vec!["patterns_found".to_string()],
660                risk_score: 0.2,
661            },
662            Action {
663                id: id + 3,
664                name: "store_insights".to_string(),
665                module: "knowledge_base".to_string(),
666
667                estimated_cost: 0.1,
668                estimated_duration_ms: 30,
669                dependencies: vec![id + 2],
670
671                preconditions: vec!["patterns_found".to_string()],
672                effects: vec!["insights_stored".to_string()],
673                risk_score: 0.05,
674            },
675        ]
676    }
677
678    fn make_execute_actions(&self, goal: &Goal) -> Vec<Action> {
679        let id = self.nid();
680
681        vec![
682            Action {
683                id: id + 1,
684                name: format!("prepare_{}", goal.name),
685                module: "executive".to_string(),
686
687                estimated_cost: 0.2,
688                estimated_duration_ms: 50,
689                dependencies: vec![],
690
691                preconditions: vec![],
692                effects: vec!["ready".to_string()],
693                risk_score: 0.1,
694            },
695            Action {
696                id: id + 2,
697                name: format!("run_{}", goal.name),
698                module: "executive".to_string(),
699
700                estimated_cost: 0.8,
701                estimated_duration_ms: 500,
702                dependencies: vec![id + 1],
703
704                preconditions: vec!["ready".to_string()],
705                effects: vec!["executed".to_string()],
706                risk_score: 0.3,
707            },
708            Action {
709                id: id + 3,
710                name: "verify_result".to_string(),
711                module: "meta_supervision".to_string(),
712
713                estimated_cost: 0.1,
714                estimated_duration_ms: 30,
715                dependencies: vec![id + 2],
716
717                preconditions: vec!["executed".to_string()],
718                effects: vec!["verified".to_string()],
719                risk_score: 0.1,
720            },
721        ]
722    }
723
724    fn make_generic_actions(&self, goal: &Goal) -> Vec<Action> {
725        let id = self.nid();
726
727        vec![
728            Action {
729                id: id + 1,
730                name: format!("init_{}", goal.name),
731                module: "executive".to_string(),
732
733                estimated_cost: 0.2,
734                estimated_duration_ms: 50,
735                dependencies: vec![],
736
737                preconditions: vec![],
738                effects: vec!["initialized".to_string()],
739                risk_score: 0.1,
740            },
741            Action {
742                id: id + 2,
743                name: format!("process_{}", goal.name),
744                module: "executive".to_string(),
745
746                estimated_cost: 0.5,
747                estimated_duration_ms: 300,
748                dependencies: vec![id + 1],
749
750                preconditions: vec!["initialized".to_string()],
751                effects: vec!["processed".to_string()],
752                risk_score: 0.2,
753            },
754            Action {
755                id: id + 3,
756                name: "finalize".to_string(),
757                module: "executive".to_string(),
758
759                estimated_cost: 0.1,
760                estimated_duration_ms: 30,
761                dependencies: vec![id + 2],
762
763                preconditions: vec!["processed".to_string()],
764                effects: vec!["done".to_string()],
765                risk_score: 0.05,
766            },
767        ]
768    }
769}