Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions crates/sona/src/loops/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,17 +162,41 @@ impl LoopCoordinator {
}
}

/// Serialize state to JSON for persistence (fixes #274)
/// Serialize full state to JSON for persistence (fixes #274)
pub fn serialize_state(&self) -> String {
let rb = self.reasoning_bank.read();
let patterns = rb.get_all_patterns();
let ewc = self.ewc.read();
serde_json::json!({
"reasoning_bank_patterns": rb.pattern_count(),
"version": 1,
"patterns": patterns,
"ewc_task_count": ewc.task_count(),
"instant_enabled": self.instant_enabled,
"background_enabled": self.background_enabled,
}).to_string()
}

/// Restore state from JSON (fixes #274)
/// Call after construction to restore learned patterns from a previous session.
pub fn load_state(&self, json: &str) -> Result<usize, String> {
let state: serde_json::Value = serde_json::from_str(json)
.map_err(|e| format!("Invalid state JSON: {}", e))?;

let mut loaded = 0;

// Restore patterns into reasoning bank
if let Some(patterns) = state.get("patterns").and_then(|p| p.as_array()) {
let mut rb = self.reasoning_bank.write();
for p in patterns {
if let Ok(pattern) = serde_json::from_value::<crate::LearnedPattern>(p.clone()) {
rb.insert_pattern(pattern);
loaded += 1;
}
}
}

Ok(loaded)
}
}

/// Coordinator statistics
Expand Down
14 changes: 14 additions & 0 deletions crates/sona/src/napi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,20 @@ impl SonaEngine {
self.inner.coordinator().serialize_state()
}

/// Restore engine state from JSON (fixes #274)
/// @param state_json - JSON string from saveState()
/// @returns Number of patterns restored
#[napi]
pub fn load_state(&self, state_json: String) -> u32 {
match self.inner.coordinator().load_state(&state_json) {
Ok(count) => count as u32,
Err(e) => {
eprintln!("SONA load_state error: {}", e);
0
}
}
}

/// Enable or disable the engine
/// @param enabled - Whether to enable the engine
#[napi]
Expand Down
10 changes: 10 additions & 0 deletions crates/sona/src/reasoning_bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,16 @@ impl ReasoningBank {
self.patterns.values().cloned().collect()
}

/// Insert a pattern directly (for state restoration, fixes #274)
pub fn insert_pattern(&mut self, pattern: LearnedPattern) {
let id = pattern.id;
if id >= self.next_pattern_id {
self.next_pattern_id = id + 1;
}
self.pattern_index.push((pattern.centroid.clone(), id));
self.patterns.insert(id, pattern);
}

/// Consolidate similar patterns
pub fn consolidate(&mut self, similarity_threshold: f32) {
let pattern_ids: Vec<u64> = self.patterns.keys().copied().collect();
Expand Down
9 changes: 9 additions & 0 deletions npm/packages/ruvector/src/core/sona-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,15 @@ export class SonaEngine {
return this._native.saveState();
}

/**
* Restore engine state from JSON saved by saveState() (fixes #274)
* @param stateJson - JSON string from a previous saveState() call
* @returns Number of patterns restored
*/
loadState(stateJson: string): number {
return this._native.loadState(stateJson);
}

/**
* Enable or disable the engine
* @param enabled Whether to enable
Expand Down
Loading