formal_ai/
dreaming_runtime.rs1use std::io;
10use std::path::Path;
11use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
12use std::sync::Once;
13use std::time::{Duration, SystemTime, UNIX_EPOCH};
14
15use crate::dreaming::{apply_dreaming_plan, DreamingOutcome};
16use crate::memory::MemoryStore;
17use crate::storage_policy::{auto_free_space_enabled, plan_for_real_storage};
18
19const DEFAULT_IDLE_SECONDS: u64 = 60;
20const DEFAULT_INTERVAL_SECONDS: u64 = 6 * 60 * 60;
21
22static ACTIVE_FOREGROUND: AtomicUsize = AtomicUsize::new(0);
23static LAST_FOREGROUND_SECONDS: AtomicU64 = AtomicU64::new(0);
24static START: Once = Once::new();
25
26pub struct ForegroundActivity;
29
30impl ForegroundActivity {
31 #[must_use]
32 pub fn begin() -> Self {
33 ACTIVE_FOREGROUND.fetch_add(1, Ordering::SeqCst);
34 LAST_FOREGROUND_SECONDS.store(now_seconds(), Ordering::SeqCst);
35 Self
36 }
37}
38
39impl Drop for ForegroundActivity {
40 fn drop(&mut self) {
41 LAST_FOREGROUND_SECONDS.store(now_seconds(), Ordering::SeqCst);
42 ACTIVE_FOREGROUND.fetch_sub(1, Ordering::SeqCst);
43 }
44}
45
46#[must_use]
48pub fn core_is_idle(idle_for: Duration) -> bool {
49 if ACTIVE_FOREGROUND.load(Ordering::SeqCst) != 0 {
50 return false;
51 }
52 now_seconds().saturating_sub(LAST_FOREGROUND_SECONDS.load(Ordering::SeqCst))
53 >= idle_for.as_secs()
54}
55
56pub fn start_core_dreaming() {
62 if dreaming_disabled() {
63 return;
64 }
65 let path = crate::shared_memory::shared_memory_path();
66 if let Err(error) = crate::shared_memory::ensure_shared_memory_file(&path) {
67 eprintln!(
68 "[dreaming] could not initialize {}: {error}",
69 path.display()
70 );
71 return;
72 }
73 START.call_once(|| {
74 LAST_FOREGROUND_SECONDS.store(now_seconds(), Ordering::SeqCst);
75 std::thread::Builder::new()
76 .name(String::from("formal-ai-dreaming"))
77 .spawn(move || {
78 lower_current_thread_priority();
79 loop {
80 std::thread::sleep(Duration::from_secs(DEFAULT_IDLE_SECONDS));
81 if core_is_idle(Duration::from_secs(DEFAULT_IDLE_SECONDS)) {
82 if let Err(error) = run_core_dreaming_once(&path) {
83 if std::env::var("FORMAL_AI_DREAMING_DEBUG").as_deref() == Ok("1") {
84 eprintln!("[dreaming] background run failed: {error}");
85 }
86 }
87 std::thread::sleep(Duration::from_secs(DEFAULT_INTERVAL_SECONDS));
88 }
89 }
90 })
91 .expect("spawn core dreaming worker");
92 });
93}
94
95fn lower_current_thread_priority() {
100 let _ = thread_priority::set_current_thread_priority(thread_priority::ThreadPriority::Min);
101}
102
103pub fn run_core_dreaming_once(memory_path: &Path) -> io::Result<DreamingOutcome> {
109 let mut store = MemoryStore::load_from_file(memory_path)?;
110 let mut plan = plan_for_real_storage(&store, memory_path, 0)?;
111 if !auto_free_space_enabled(memory_path) {
112 plan.actions.clear();
113 plan.selected_reclaim_bytes = 0;
114 }
115 if ACTIVE_FOREGROUND.load(Ordering::SeqCst) != 0 {
118 return Ok(DreamingOutcome::default());
119 }
120 let outcome = apply_dreaming_plan(&mut store, &plan);
121 if outcome.removed_events > 0
122 || outcome.learned_amendments > 0
123 || outcome.learned_patterns > 0
124 || outcome.recorded_failures > 0
125 || outcome.recorded_trials > 0
126 {
127 store.save_to_file(memory_path)?;
128 let recipe = crate::dreaming::compose_recipe_with_amendments(store.events());
132 let recipe_path = memory_path.with_extension("recipe.lino");
133 crate::memory::write_locked_atomic(&recipe_path, &recipe)?;
134 }
135 Ok(outcome)
136}
137
138#[must_use]
141pub fn dreaming_disabled() -> bool {
142 std::env::var("FORMAL_AI_DREAMING")
143 .ok()
144 .is_some_and(|value| matches!(value.to_ascii_lowercase().as_str(), "0" | "off" | "false"))
145}
146
147fn now_seconds() -> u64 {
148 SystemTime::now()
149 .duration_since(UNIX_EPOCH)
150 .unwrap_or_default()
151 .as_secs()
152}