Skip to main content

agentic_planning/
lib.rs

1pub mod audit;
2pub mod auth;
3pub mod bridges;
4pub mod cache;
5pub mod contracts;
6mod error;
7mod file_format;
8#[path = "indexes.rs"]
9mod indexes;
10mod inventions;
11pub mod isolation;
12pub mod locking;
13pub mod metrics;
14pub mod query;
15mod query_engine;
16pub mod types;
17mod validation;
18mod write_engine;
19
20pub use audit::{AuditAction, AuditEntityType, AuditEntry, AuditLog};
21pub use error::{Error, Result};
22pub use indexes::PlanIndexes;
23pub use types::*;
24pub use validation::{validators, ValidationError, ValidationResult};
25
26use std::collections::HashMap;
27use std::path::PathBuf;
28
29#[derive(Debug, Clone)]
30pub struct PlanningEngine {
31    pub(crate) path: Option<PathBuf>,
32    pub(crate) dirty: bool,
33    pub(crate) goal_store: HashMap<GoalId, Goal>,
34    pub(crate) decision_store: HashMap<DecisionId, Decision>,
35    pub(crate) commitment_store: HashMap<CommitmentId, Commitment>,
36    pub(crate) dream_store: HashMap<DreamId, Dream>,
37    pub(crate) federation_store: HashMap<FederationId, Federation>,
38    pub(crate) soul_archive: HashMap<GoalId, GoalSoulArchive>,
39    pub(crate) consensus_store: HashMap<DecisionId, DecisionConsensus>,
40    pub(crate) indexes: PlanIndexes,
41    pub(crate) audit_log: AuditLog,
42    pub(crate) write_count: u64,
43    pub(crate) session_id: uuid::Uuid,
44}
45
46impl PlanningEngine {
47    pub fn in_memory() -> Self {
48        Self {
49            path: None,
50            dirty: false,
51            goal_store: HashMap::new(),
52            decision_store: HashMap::new(),
53            commitment_store: HashMap::new(),
54            dream_store: HashMap::new(),
55            federation_store: HashMap::new(),
56            soul_archive: HashMap::new(),
57            consensus_store: HashMap::new(),
58            indexes: PlanIndexes::new(),
59            audit_log: AuditLog::new(),
60            write_count: 0,
61            session_id: uuid::Uuid::new_v4(),
62        }
63    }
64
65    pub fn goal_count(&self) -> usize {
66        self.goal_store.len()
67    }
68
69    pub fn decision_count(&self) -> usize {
70        self.decision_store.len()
71    }
72
73    pub fn commitment_count(&self) -> usize {
74        self.commitment_store.len()
75    }
76
77    pub fn session_id(&self) -> uuid::Uuid {
78        self.session_id
79    }
80
81    pub fn audit_log_mut(&mut self) -> &mut AuditLog {
82        &mut self.audit_log
83    }
84
85    /// Iterate over all goals (for ghost bridge context).
86    pub fn goals(&self) -> impl Iterator<Item = &Goal> {
87        self.goal_store.values()
88    }
89
90    /// Iterate over all decisions (for ghost bridge context).
91    pub fn decisions(&self) -> impl Iterator<Item = &Decision> {
92        self.decision_store.values()
93    }
94
95    /// Iterate over all commitments (for ghost bridge context).
96    pub fn commitments(&self) -> impl Iterator<Item = &Commitment> {
97        self.commitment_store.values()
98    }
99
100    pub(crate) fn mark_dirty(&mut self) {
101        self.dirty = true;
102    }
103
104    #[allow(dead_code)]
105    pub(crate) fn rebuild_indexes(&mut self) {
106        self.indexes.rebuild_full(
107            &self.goal_store,
108            &self.decision_store,
109            &self.commitment_store,
110            &self.dream_store,
111            &self.federation_store,
112        );
113    }
114
115    pub(crate) fn calculate_initial_urgency(&self, request: &CreateGoalRequest) -> f64 {
116        let priority: f64 = match request.priority.unwrap_or(Priority::Medium) {
117            Priority::Critical => 1.0,
118            Priority::High => 0.8,
119            Priority::Medium => 0.5,
120            Priority::Low => 0.3,
121            Priority::Someday => 0.1,
122        };
123
124        let deadline_factor: f64 = request
125            .deadline
126            .map(|d| {
127                let days = (d.0 - Timestamp::now().0) as f64 / (86_400.0 * 1e9);
128                if days <= 1.0 {
129                    1.0
130                } else if days <= 7.0 {
131                    0.8
132                } else if days <= 30.0 {
133                    0.6
134                } else {
135                    0.3
136                }
137            })
138            .unwrap_or(0.4);
139
140        ((priority + deadline_factor) / 2.0).clamp(0.0, 1.0)
141    }
142
143    pub(crate) fn calculate_initial_gravity(&self, request: &CreateGoalRequest) -> f64 {
144        let emotional_weight = request.emotional_weight.unwrap_or(0.5);
145        let priority = match request.priority.unwrap_or(Priority::Medium) {
146            Priority::Critical => 0.95,
147            Priority::High => 0.8,
148            Priority::Medium => 0.6,
149            Priority::Low => 0.4,
150            Priority::Someday => 0.2,
151        };
152        ((emotional_weight + priority) / 2.0).clamp(0.0, 1.0)
153    }
154
155    pub(crate) fn calculate_initial_inertia(&self, request: &CreateGoalRequest) -> f64 {
156        let mut inertia = 0.35;
157        if let Some(deps) = &request.dependencies {
158            inertia += (deps.len() as f64 * 0.08).min(0.4);
159        }
160        if request.parent.is_some() {
161            inertia += 0.1;
162        }
163        inertia.clamp(0.1, 1.0)
164    }
165
166    pub(crate) fn calculate_velocity(&self, history: &[ProgressPoint]) -> f64 {
167        if history.len() < 2 {
168            return 0.0;
169        }
170
171        let first = &history[0];
172        let last = &history[history.len() - 1];
173        let dt_days = ((last.timestamp.0 - first.timestamp.0) as f64 / (86_400.0 * 1e9)).max(1.0);
174        let dp = (last.percentage - first.percentage).max(0.0);
175        (dp / dt_days).clamp(0.0, 1.0)
176    }
177
178    pub(crate) fn calculate_momentum_from_goal(&self, goal: &Goal) -> f64 {
179        let recent_factor = goal
180            .progress
181            .history
182            .last()
183            .map(|p| {
184                let age_days =
185                    ((Timestamp::now().0 - p.timestamp.0) as f64 / (86_400.0 * 1e9)).max(0.0);
186                (1.0 - (age_days / 30.0)).clamp(0.0, 1.0)
187            })
188            .unwrap_or(0.0);
189        (goal.progress.velocity * 0.6 + recent_factor * 0.4).clamp(0.0, 1.0)
190    }
191
192    pub(crate) fn calculate_confidence_from_goal(&self, goal: &Goal) -> f64 {
193        let blocker_penalty: f64 = goal
194            .blockers
195            .iter()
196            .filter(|b| b.resolved_at.is_none())
197            .map(|b| b.severity)
198            .sum::<f64>()
199            .min(1.0);
200        let progress_boost = goal.progress.percentage * 0.5;
201        (0.5 + progress_boost - blocker_penalty * 0.6).clamp(0.0, 1.0)
202    }
203
204    pub(crate) fn calculate_reincarnation_potential(&self, goal: &Goal) -> f64 {
205        let progress_component = goal.progress.percentage;
206        let soul_component = goal.soul.emotional_weight;
207        ((progress_component + soul_component) / 2.0).clamp(0.0, 1.0)
208    }
209
210    #[allow(dead_code)]
211    pub(crate) fn check_success_criteria(&mut self, goal: &mut Goal) {
212        let now = Timestamp::now();
213        for c in &mut goal.soul.success_criteria {
214            if c.achieved {
215                continue; // already achieved, skip
216            }
217
218            let met = if c.measurable {
219                // For measurable criteria with a target, check against goal progress
220                match c.target {
221                    Some(target) => goal.progress.percentage >= target,
222                    None => goal.progress.percentage >= 1.0,
223                }
224            } else {
225                // Non-measurable criteria: consider achieved only if goal is fully complete
226                goal.progress.percentage >= 1.0
227            };
228
229            if met {
230                c.achieved = true;
231                c.achieved_at = Some(now);
232            }
233        }
234    }
235
236    pub(crate) fn release_completion_energy(&mut self, goal_id: GoalId) {
237        let dependent_ids = self
238            .goal_store
239            .get(&goal_id)
240            .map(|g| g.dependents.clone())
241            .unwrap_or_default();
242
243        for dep_id in dependent_ids {
244            if let Some(dep) = self.goal_store.get_mut(&dep_id) {
245                dep.physics.energy = (dep.physics.energy + 0.2).clamp(0.0, 2.0);
246                dep.physics.momentum = (dep.physics.momentum + 0.1).clamp(0.0, 1.0);
247            }
248        }
249    }
250
251    pub(crate) fn check_unblock(&mut self, goal_id: &GoalId) {
252        let should_unblock = self
253            .goal_store
254            .get(goal_id)
255            .map(|g| {
256                g.status == GoalStatus::Blocked
257                    && g.blockers.iter().all(|b| b.resolved_at.is_some())
258                    && g.dependencies.iter().all(|d| {
259                        self.goal_store
260                            .get(d)
261                            .map(|dg| dg.status == GoalStatus::Completed)
262                            .unwrap_or(false)
263                    })
264            })
265            .unwrap_or(false);
266
267        if should_unblock {
268            if let Some(goal) = self.goal_store.get_mut(goal_id) {
269                goal.status = GoalStatus::Active;
270            }
271        }
272    }
273
274    pub(crate) fn calculate_resurrection_cost(
275        &self,
276        decision: &Decision,
277        shadow: &CrystalShadow,
278    ) -> f64 {
279        let base = if decision.status == DecisionStatus::Crystallized {
280            0.6
281        } else {
282            0.3
283        };
284        let complexity = (shadow.path.pros.len() + shadow.path.cons.len()) as f64 * 0.03;
285        (base + complexity).clamp(0.0, 1.0)
286    }
287
288    pub(crate) fn calculate_reversibility(&self, decision: &Decision) -> Reversibility {
289        let complexity = decision.shadows.len() as f64;
290        Reversibility {
291            is_reversible: complexity < 6.0,
292            reversal_cost: (0.2 + complexity * 0.1).clamp(0.0, 1.0),
293            reversal_window: Some(Timestamp::days_from_now(14.0)),
294            cascade_count: decision.causes.len(),
295        }
296    }
297
298    pub(crate) fn calculate_regret(&self, decision: &Decision) -> f64 {
299        let negative = decision
300            .consequences
301            .iter()
302            .filter(|c| matches!(c.impact, Impact::Negative))
303            .count() as f64;
304        let total = decision.consequences.len().max(1) as f64;
305        (negative / total).clamp(0.0, 1.0)
306    }
307
308    pub(crate) fn calculate_commitment_weight(&self, request: &CreateCommitmentRequest) -> f64 {
309        let stakeholder = request.stakeholder.importance.clamp(0.0, 1.0);
310        let complexity = (request.promise.deliverables.len() as f64 * 0.05).clamp(0.0, 0.4);
311        (0.3 + stakeholder * 0.5 + complexity).clamp(0.0, 1.0)
312    }
313
314    pub(crate) fn calculate_commitment_inertia(&self, request: &CreateCommitmentRequest) -> f64 {
315        let due_pressure: f64 = request
316            .due
317            .map(|d| {
318                let days = (d.0 - Timestamp::now().0) as f64 / (86_400.0 * 1e9);
319                if days <= 7.0 {
320                    0.7
321                } else {
322                    0.4
323                }
324            })
325            .unwrap_or(0.3);
326        due_pressure.clamp(0.0, 1.0)
327    }
328
329    pub(crate) fn calculate_breaking_cost(
330        &self,
331        request: &CreateCommitmentRequest,
332    ) -> BreakingCost {
333        let trust = request.stakeholder.importance.clamp(0.0, 1.0);
334        BreakingCost {
335            trust_damage: trust,
336            relationship_impact: (trust * 0.8).clamp(0.0, 1.0),
337            reputation_cost: (trust * 0.6).clamp(0.0, 1.0),
338            energy_to_break: 0.5,
339            cascading_effects: Vec::new(),
340        }
341    }
342
343    pub(crate) fn calculate_chain_bonus(&self, id: CommitmentId) -> f64 {
344        let commitment = match self.commitment_store.get(&id) {
345            Some(c) => c,
346            None => return 0.0,
347        };
348
349        // Walk entanglement chain: fulfilled neighbors boost, broken neighbors penalize
350        let mut fulfilled_count: usize = 0;
351        let mut broken_count: usize = 0;
352
353        for entanglement in &commitment.entanglements {
354            if let Some(linked) = self.commitment_store.get(&entanglement.with) {
355                match linked.status {
356                    CommitmentStatus::Fulfilled => fulfilled_count += 1,
357                    CommitmentStatus::Broken => broken_count += 1,
358                    _ => {}
359                }
360            }
361        }
362
363        let bonus = fulfilled_count as f64 * 0.05 - broken_count as f64 * 0.02;
364        bonus.clamp(-0.1, 0.3)
365    }
366
367    pub(crate) fn boost_commitment(&mut self, id: CommitmentId, bonus: f64) -> Result<()> {
368        let c = self
369            .commitment_store
370            .get_mut(&id)
371            .ok_or(Error::CommitmentNotFound(id))?;
372        c.weight = (c.weight + bonus).clamp(0.0, 1.0);
373        Ok(())
374    }
375
376    pub(crate) fn destabilize_commitment(&mut self, id: CommitmentId) -> Result<()> {
377        let c = self
378            .commitment_store
379            .get_mut(&id)
380            .ok_or(Error::CommitmentNotFound(id))?;
381        c.status = CommitmentStatus::AtRisk;
382        Ok(())
383    }
384
385    pub(crate) fn generate_completion_scenario(&self, goal: &Goal) -> CompletionScenario {
386        CompletionScenario {
387            vision: format!(
388                "{} has been achieved. Success criteria are met.",
389                goal.title
390            ),
391            feeling: "Clarity and momentum".to_string(),
392            world_changes: goal
393                .soul
394                .success_criteria
395                .iter()
396                .map(|c| format!("✓ {}", c.description))
397                .collect(),
398            stakeholder_reactions: HashMap::new(),
399        }
400    }
401
402    pub(crate) fn predict_obstacles(&self, goal: &Goal) -> Vec<DreamObstacle> {
403        let mut obstacles = Vec::new();
404
405        for dep_id in &goal.dependencies {
406            if let Some(dep) = self.goal_store.get(dep_id) {
407                if dep.status != GoalStatus::Completed {
408                    obstacles.push(DreamObstacle {
409                        description: format!("Dependency '{}' not complete", dep.title),
410                        severity: 0.7,
411                        timing: "Before completion".to_string(),
412                        mitigation: Some(format!("Complete {} first", dep.title)),
413                    });
414                }
415            }
416        }
417
418        obstacles
419    }
420
421    pub(crate) fn extract_insights(
422        &self,
423        goal: &Goal,
424        scenario: &CompletionScenario,
425        obstacles: &[DreamObstacle],
426    ) -> Vec<DreamInsight> {
427        let mut insights = vec![DreamInsight {
428            insight: format!("Primary completion signal: {}", scenario.feeling),
429            actionable: true,
430            action: Some("Prioritize highest leverage task this week".to_string()),
431        }];
432
433        if !obstacles.is_empty() {
434            insights.push(DreamInsight {
435                insight: format!("{} blockers predicted", obstacles.len()),
436                actionable: true,
437                action: Some("Resolve blockers before deep execution".to_string()),
438            });
439        }
440
441        if goal.progress.percentage < 0.2 {
442            insights.push(DreamInsight {
443                insight: "Early-stage goal: momentum building is critical".to_string(),
444                actionable: true,
445                action: Some("Ship a visible milestone in 7 days".to_string()),
446            });
447        }
448
449        insights
450    }
451
452    pub(crate) fn discover_sub_goals(
453        &self,
454        goal: &Goal,
455        obstacles: &[DreamObstacle],
456    ) -> Vec<GoalSeed> {
457        obstacles
458            .iter()
459            .take(2)
460            .enumerate()
461            .map(|(i, o)| GoalSeed {
462                title: format!("Mitigate blocker {}", i + 1),
463                description: o.description.clone(),
464                parent: goal.id,
465                reason: "Dream-derived mitigation".to_string(),
466            })
467            .collect()
468    }
469
470    pub(crate) fn calculate_dream_confidence(&self, goal: &Goal) -> f64 {
471        let blocker_penalty = goal
472            .blockers
473            .iter()
474            .filter(|b| b.resolved_at.is_none())
475            .map(|b| b.severity)
476            .sum::<f64>()
477            .min(1.0);
478        (0.8 - blocker_penalty * 0.5 + goal.progress.percentage * 0.2).clamp(0.1, 1.0)
479    }
480}