spectral decomposition, search improvements, char boundary fix
- New spectral module: Laplacian eigendecomposition of the memory graph. Commands: spectral, spectral-save, spectral-neighbors, spectral-positions, spectral-suggest. Spectral neighbors expand search results beyond keyword matching to structural proximity. - Search: use StoreView trait to avoid 6MB state.bin rewrite on every query. Append-only retrieval logging. Spectral expansion shows structurally nearby nodes after text results. - Fix panic in journal-tail: string truncation at byte 67 could land inside a multi-byte character (em dash). Now walks back to char boundary. - Replay queue: show classification and spectral outlier score. - Knowledge agents: extractor, challenger, connector prompts and runner scripts for automated graph enrichment. - memory-search hook: stale state file cleanup (24h expiry).
This commit is contained in:
parent
94dbca6018
commit
71e6f15d82
16 changed files with 3600 additions and 103 deletions
56
src/graph.rs
56
src/graph.rs
|
|
@ -7,7 +7,7 @@
|
|||
// connections), but relation type and direction are preserved for
|
||||
// specific queries.
|
||||
|
||||
use crate::capnp_store::{Store, RelationType};
|
||||
use crate::capnp_store::{Store, RelationType, StoreView};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
|
|
@ -377,38 +377,46 @@ impl Graph {
|
|||
}
|
||||
}
|
||||
|
||||
/// Build graph from store data
|
||||
pub fn build_graph(store: &Store) -> Graph {
|
||||
/// Build graph from store data (with community detection)
|
||||
pub fn build_graph(store: &impl StoreView) -> Graph {
|
||||
let (adj, keys) = build_adjacency(store);
|
||||
let communities = label_propagation(&keys, &adj, 20);
|
||||
Graph { adj, keys, communities }
|
||||
}
|
||||
|
||||
/// Build graph without community detection — for spreading activation
|
||||
/// searches where we only need the adjacency list.
|
||||
pub fn build_graph_fast(store: &impl StoreView) -> Graph {
|
||||
let (adj, keys) = build_adjacency(store);
|
||||
Graph { adj, keys, communities: HashMap::new() }
|
||||
}
|
||||
|
||||
fn build_adjacency(store: &impl StoreView) -> (HashMap<String, Vec<Edge>>, HashSet<String>) {
|
||||
let mut adj: HashMap<String, Vec<Edge>> = HashMap::new();
|
||||
let keys: HashSet<String> = store.nodes.keys().cloned().collect();
|
||||
let mut keys: HashSet<String> = HashSet::new();
|
||||
|
||||
// Build adjacency from relations
|
||||
for rel in &store.relations {
|
||||
let source_key = &rel.source_key;
|
||||
let target_key = &rel.target_key;
|
||||
store.for_each_node(|key, _, _| {
|
||||
keys.insert(key.to_owned());
|
||||
});
|
||||
|
||||
// Both keys must exist as nodes
|
||||
store.for_each_relation(|source_key, target_key, strength, rel_type| {
|
||||
if !keys.contains(source_key) || !keys.contains(target_key) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
// Add bidirectional edges (even for causal — direction is metadata)
|
||||
adj.entry(source_key.clone()).or_default().push(Edge {
|
||||
target: target_key.clone(),
|
||||
strength: rel.strength,
|
||||
rel_type: rel.rel_type,
|
||||
adj.entry(source_key.to_owned()).or_default().push(Edge {
|
||||
target: target_key.to_owned(),
|
||||
strength,
|
||||
rel_type,
|
||||
});
|
||||
adj.entry(target_key.clone()).or_default().push(Edge {
|
||||
target: source_key.clone(),
|
||||
strength: rel.strength,
|
||||
rel_type: rel.rel_type,
|
||||
adj.entry(target_key.to_owned()).or_default().push(Edge {
|
||||
target: source_key.to_owned(),
|
||||
strength,
|
||||
rel_type,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Run community detection
|
||||
let communities = label_propagation(&keys, &adj, 20);
|
||||
|
||||
Graph { adj, keys, communities }
|
||||
(adj, keys)
|
||||
}
|
||||
|
||||
/// Label propagation community detection.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue