Skip to main content

wm_tools/expansion/
autonomous.rs

1//! Autonomous cycle tools — spiral.report, consolidation.connect, consolidation.compress, emergence.scan, retention.prune.
2
3#![forbid(unsafe_code)]
4
5use async_trait::async_trait;
6
7use serde_json::{Value, json};
8use std::sync::Arc;
9use wm_core::{Context, EffectRow, Gana, Resource, Tool, ToolStats};
10use wm_memory::{AssociationStore, MemoryStore};
11
12pub struct SpiralReportTool {
13    tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>,
14    stats: ToolStats,
15    effects: EffectRow,
16}
17
18impl SpiralReportTool {
19    pub fn new(tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>) -> Self {
20        Self {
21            tracker,
22            stats: ToolStats::default(),
23            effects: EffectRow::pure(),
24        }
25    }
26}
27
28#[async_trait]
29impl Tool for SpiralReportTool {
30    fn name(&self) -> &str {
31        "spiral.report"
32    }
33    fn gana(&self) -> Gana {
34        Gana::Encampment
35    }
36    fn effects(&self) -> &EffectRow {
37        &self.effects
38    }
39    fn description(&self) -> &str {
40        "Report on autonomy expansion or circling (spiral direction, novelty, suspensions)"
41    }
42    async fn call(&self, _ctx: &mut Context, _args: Value) -> wm_core::Result<Value> {
43        let report = {
44            let tracker = self
45                .tracker
46                .lock()
47                .map_err(|e| wm_core::CoreError::Internal(format!("spiral tracker lock: {e}")))?;
48            tracker.report()
49        };
50        Ok(report.to_json())
51    }
52    fn stats(&self) -> &ToolStats {
53        &self.stats
54    }
55}
56
57/// `consolidation.connect` — propose typed associations for disconnected memories.
58///
59/// Runs the connect autonomous cycle, gated by Harmony Vector health score.
60/// Proposes typed associations for memories that have no incoming or outgoing
61/// links. Proposals require human review before action.
62pub struct ConsolidationConnectTool {
63    store: Arc<MemoryStore>,
64    associations: Arc<AssociationStore>,
65    spiral_tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>,
66    stats: ToolStats,
67    effects: EffectRow,
68}
69
70impl ConsolidationConnectTool {
71    pub fn new(
72        store: Arc<MemoryStore>,
73        associations: Arc<AssociationStore>,
74        spiral_tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>,
75    ) -> Self {
76        Self {
77            store,
78            associations,
79            spiral_tracker,
80            stats: ToolStats::default(),
81            effects: EffectRow {
82                // Runs an autonomous cycle: scans memory galaxies and
83                // logs the cycle record to the Substrate galaxy.
84                reads: super::common::memory_galaxy_reads(),
85                writes: vec![Resource::Galaxy("substrate".into())],
86                ..Default::default()
87            },
88        }
89    }
90}
91
92#[async_trait]
93impl Tool for ConsolidationConnectTool {
94    fn name(&self) -> &str {
95        "consolidation.connect"
96    }
97    fn gana(&self) -> Gana {
98        Gana::Encampment
99    }
100    fn effects(&self) -> &EffectRow {
101        &self.effects
102    }
103    fn description(&self) -> &str {
104        "Propose typed associations for disconnected memories (gated, human review)"
105    }
106    async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
107        let health_score = args
108            .get("health_score")
109            .and_then(Value::as_f64)
110            .unwrap_or(0.8) as f32;
111
112        let mut runner = wm_cognitive::AutonomousCycleRunner::default();
113        let cycle_ctx =
114            wm_cognitive::CycleContext::new(&self.store, &self.associations, health_score);
115        let result = runner.run_cycle(wm_cognitive::CycleType::Connect, &cycle_ctx);
116
117        // Record in spiral tracker
118        if let Ok(mut tracker) = self.spiral_tracker.lock() {
119            tracker.record(&result);
120        }
121
122        Ok(json!({
123            "status": "success",
124            "cycle": result.cycle.name(),
125            "cycle_status": format!("{:?}", result.status),
126            "purpose": result.purpose,
127            "memories_scanned": result.memories_scanned,
128            "proposals_generated": result.proposals_generated,
129            "duration_ms": result.duration_ms,
130            "requires_human_review": true,
131            "notes": result.notes,
132            "connections": result.connections,
133        }))
134    }
135    fn stats(&self) -> &ToolStats {
136        &self.stats
137    }
138}
139
140/// `consolidation.compress` — propose merging semantically overlapping memories.
141///
142/// Runs the compress autonomous cycle. Finds pairs of memories with high
143/// semantic similarity and proposes merging the lower-importance one into
144/// the higher-importance one. Requires human review.
145pub struct ConsolidationCompressTool {
146    store: Arc<MemoryStore>,
147    associations: Arc<AssociationStore>,
148    spiral_tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>,
149    stats: ToolStats,
150    effects: EffectRow,
151}
152
153impl ConsolidationCompressTool {
154    pub fn new(
155        store: Arc<MemoryStore>,
156        associations: Arc<AssociationStore>,
157        spiral_tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>,
158    ) -> Self {
159        Self {
160            store,
161            associations,
162            spiral_tracker,
163            stats: ToolStats::default(),
164            effects: EffectRow {
165                // Runs an autonomous cycle: scans memory galaxies and
166                // logs the cycle record to the Substrate galaxy.
167                reads: super::common::memory_galaxy_reads(),
168                writes: vec![Resource::Galaxy("substrate".into())],
169                ..Default::default()
170            },
171        }
172    }
173}
174
175#[async_trait]
176impl Tool for ConsolidationCompressTool {
177    fn name(&self) -> &str {
178        "consolidation.compress"
179    }
180    fn gana(&self) -> Gana {
181        Gana::Encampment
182    }
183    fn effects(&self) -> &EffectRow {
184        &self.effects
185    }
186    fn description(&self) -> &str {
187        "Propose merging semantically overlapping memories (gated, human review)"
188    }
189    async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
190        let health_score = args
191            .get("health_score")
192            .and_then(Value::as_f64)
193            .unwrap_or(0.8) as f32;
194
195        let mut runner = wm_cognitive::AutonomousCycleRunner::default();
196        let cycle_ctx =
197            wm_cognitive::CycleContext::new(&self.store, &self.associations, health_score);
198        let result = runner.run_cycle(wm_cognitive::CycleType::Compress, &cycle_ctx);
199
200        // Record in spiral tracker
201        if let Ok(mut tracker) = self.spiral_tracker.lock() {
202            tracker.record(&result);
203        }
204
205        Ok(json!({
206            "status": "success",
207            "cycle": result.cycle.name(),
208            "cycle_status": format!("{:?}", result.status),
209            "purpose": result.purpose,
210            "memories_scanned": result.memories_scanned,
211            "proposals_generated": result.proposals_generated,
212            "duration_ms": result.duration_ms,
213            "requires_human_review": true,
214            "notes": result.notes,
215            "compressions": result.compressions,
216        }))
217    }
218    fn stats(&self) -> &ToolStats {
219        &self.stats
220    }
221}
222
223/// `emergence.scan` — detect tag/topic emergence patterns.
224///
225/// Runs the emergence autonomous cycle. Scans all galaxies and aggregates
226/// tag frequencies to detect emerging patterns. Logged to Gnosis but does
227/// not require human review (no destructive action).
228pub struct EmergenceScanTool {
229    store: Arc<MemoryStore>,
230    associations: Arc<AssociationStore>,
231    spiral_tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>,
232    stats: ToolStats,
233    effects: EffectRow,
234}
235
236impl EmergenceScanTool {
237    pub fn new(
238        store: Arc<MemoryStore>,
239        associations: Arc<AssociationStore>,
240        spiral_tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>,
241    ) -> Self {
242        Self {
243            store,
244            associations,
245            spiral_tracker,
246            stats: ToolStats::default(),
247            effects: EffectRow {
248                // Runs an autonomous cycle: scans memory galaxies and
249                // logs the cycle record to the Substrate galaxy.
250                reads: super::common::memory_galaxy_reads(),
251                writes: vec![Resource::Galaxy("substrate".into())],
252                ..Default::default()
253            },
254        }
255    }
256}
257
258#[async_trait]
259impl Tool for EmergenceScanTool {
260    fn name(&self) -> &str {
261        "emergence.scan"
262    }
263    fn gana(&self) -> Gana {
264        Gana::Encampment
265    }
266    fn effects(&self) -> &EffectRow {
267        &self.effects
268    }
269    fn description(&self) -> &str {
270        "Detect tag/topic emergence patterns across memories (gated, logged)"
271    }
272    async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
273        let health_score = args
274            .get("health_score")
275            .and_then(Value::as_f64)
276            .unwrap_or(0.8) as f32;
277
278        let mut runner = wm_cognitive::AutonomousCycleRunner::default();
279        let cycle_ctx =
280            wm_cognitive::CycleContext::new(&self.store, &self.associations, health_score);
281        let result = runner.run_cycle(wm_cognitive::CycleType::Emergence, &cycle_ctx);
282
283        // Record in spiral tracker
284        if let Ok(mut tracker) = self.spiral_tracker.lock() {
285            tracker.record(&result);
286        }
287
288        Ok(json!({
289            "status": "success",
290            "cycle": result.cycle.name(),
291            "cycle_status": format!("{:?}", result.status),
292            "purpose": result.purpose,
293            "memories_scanned": result.memories_scanned,
294            "proposals_generated": result.proposals_generated,
295            "duration_ms": result.duration_ms,
296            "requires_human_review": false,
297            "notes": result.notes,
298            "emergences": result.emergences,
299        }))
300    }
301    fn stats(&self) -> &ToolStats {
302        &self.stats
303    }
304}
305
306/// `retention.prune` — identify memories ready for forgetting.
307///
308/// Runs the prune autonomous cycle. Computes composite retention scores
309/// from importance, neuro_score, and access recency. High-importance
310/// memories require human review before any action.
311pub struct RetentionPruneTool {
312    store: Arc<MemoryStore>,
313    associations: Arc<AssociationStore>,
314    spiral_tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>,
315    stats: ToolStats,
316    effects: EffectRow,
317}
318
319impl RetentionPruneTool {
320    pub fn new(
321        store: Arc<MemoryStore>,
322        associations: Arc<AssociationStore>,
323        spiral_tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>,
324    ) -> Self {
325        Self {
326            store,
327            associations,
328            spiral_tracker,
329            stats: ToolStats::default(),
330            effects: EffectRow {
331                // Runs an autonomous cycle: scans memory galaxies and
332                // logs the cycle record to the Substrate galaxy.
333                reads: super::common::memory_galaxy_reads(),
334                writes: vec![Resource::Galaxy("substrate".into())],
335                ..Default::default()
336            },
337        }
338    }
339}
340
341#[async_trait]
342impl Tool for RetentionPruneTool {
343    fn name(&self) -> &str {
344        "retention.prune"
345    }
346    fn gana(&self) -> Gana {
347        Gana::Encampment
348    }
349    fn effects(&self) -> &EffectRow {
350        &self.effects
351    }
352    fn description(&self) -> &str {
353        "Identify memories ready for forgetting based on decay + neuro_score (gated, human review)"
354    }
355    async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
356        let health_score = args
357            .get("health_score")
358            .and_then(Value::as_f64)
359            .unwrap_or(0.8) as f32;
360
361        let mut runner = wm_cognitive::AutonomousCycleRunner::default();
362        let cycle_ctx =
363            wm_cognitive::CycleContext::new(&self.store, &self.associations, health_score);
364        let result = runner.run_cycle(wm_cognitive::CycleType::Prune, &cycle_ctx);
365
366        // Record in spiral tracker
367        if let Ok(mut tracker) = self.spiral_tracker.lock() {
368            tracker.record(&result);
369        }
370
371        Ok(json!({
372            "status": "success",
373            "cycle": result.cycle.name(),
374            "cycle_status": format!("{:?}", result.status),
375            "purpose": result.purpose,
376            "memories_scanned": result.memories_scanned,
377            "proposals_generated": result.proposals_generated,
378            "duration_ms": result.duration_ms,
379            "requires_human_review": true,
380            "notes": result.notes,
381            "prunes": result.prunes,
382        }))
383    }
384    fn stats(&self) -> &ToolStats {
385        &self.stats
386    }
387}
388
389/// `sensorimotor.scan` — poll sensors, evaluate reflexes, execute commands.
390///
391/// Runs the sensorimotor autonomous cycle. Polls all registered sensors,
392/// evaluates reflex rules against current readings, and executes any triggered
393/// actuator commands. Results are logged to Gnosis and recorded in the spiral
394/// tracker. Does not require human review.
395pub struct SensorimotorScanTool {
396    store: Arc<MemoryStore>,
397    associations: Arc<AssociationStore>,
398    spiral_tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>,
399    sensorimotor_bus: Arc<std::sync::Mutex<wm_substrate::sensorimotor::SensorimotorBus>>,
400    reflex_loop: Arc<std::sync::Mutex<wm_substrate::sensorimotor::ReflexLoop>>,
401    stats: ToolStats,
402    effects: EffectRow,
403}
404
405impl SensorimotorScanTool {
406    pub fn new(
407        store: Arc<MemoryStore>,
408        associations: Arc<AssociationStore>,
409        spiral_tracker: Arc<std::sync::Mutex<wm_cognitive::SpiralTracker>>,
410        sensorimotor_bus: Arc<std::sync::Mutex<wm_substrate::sensorimotor::SensorimotorBus>>,
411        reflex_loop: Arc<std::sync::Mutex<wm_substrate::sensorimotor::ReflexLoop>>,
412    ) -> Self {
413        Self {
414            store,
415            associations,
416            spiral_tracker,
417            sensorimotor_bus,
418            reflex_loop,
419            stats: ToolStats::default(),
420            effects: EffectRow {
421                // Runs an autonomous cycle: scans memory galaxies and
422                // logs the cycle record to the Substrate galaxy.
423                reads: super::common::memory_galaxy_reads(),
424                writes: vec![Resource::Galaxy("substrate".into())],
425                ..Default::default()
426            },
427        }
428    }
429}
430
431#[async_trait]
432impl Tool for SensorimotorScanTool {
433    fn name(&self) -> &str {
434        "sensorimotor.scan"
435    }
436    fn gana(&self) -> Gana {
437        Gana::Encampment
438    }
439    fn effects(&self) -> &EffectRow {
440        &self.effects
441    }
442    fn description(&self) -> &str {
443        "Poll sensors, evaluate reflex rules, and execute triggered actuator commands (gated, logged)"
444    }
445    async fn call(&self, _ctx: &mut Context, args: Value) -> wm_core::Result<Value> {
446        let health_score = args
447            .get("health_score")
448            .and_then(Value::as_f64)
449            .unwrap_or(0.8) as f32;
450
451        let mut runner = wm_cognitive::AutonomousCycleRunner::default();
452        let cycle_ctx =
453            wm_cognitive::CycleContext::new(&self.store, &self.associations, health_score)
454                .with_sensorimotor(&self.sensorimotor_bus, &self.reflex_loop);
455
456        let result = runner.run_cycle(wm_cognitive::CycleType::Sensorimotor, &cycle_ctx);
457
458        if let Ok(mut tracker) = self.spiral_tracker.lock() {
459            tracker.record(&result);
460        }
461
462        Ok(json!({
463            "status": "success",
464            "cycle": result.cycle.name(),
465            "cycle_status": format!("{:?}", result.status),
466            "purpose": result.purpose,
467            "memories_scanned": result.memories_scanned,
468            "proposals_generated": result.proposals_generated,
469            "duration_ms": result.duration_ms,
470            "requires_human_review": false,
471            "notes": result.notes,
472            "sensorimotor": result.sensorimotor,
473        }))
474    }
475    fn stats(&self) -> &ToolStats {
476        &self.stats
477    }
478}