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
|
|
@ -4,7 +4,7 @@
|
|||
// supports circumscription parameter for blending associative vs
|
||||
// causal walks, and benefits from community-aware result grouping.
|
||||
|
||||
use crate::capnp_store::Store;
|
||||
use crate::capnp_store::StoreView;
|
||||
use crate::graph::Graph;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
|
@ -24,10 +24,10 @@ pub struct SearchResult {
|
|||
fn spreading_activation(
|
||||
seeds: &[(String, f64)],
|
||||
graph: &Graph,
|
||||
store: &Store,
|
||||
store: &impl StoreView,
|
||||
_circumscription: f64,
|
||||
) -> Vec<(String, f64)> {
|
||||
let params = &store.params;
|
||||
let params = store.params();
|
||||
|
||||
let mut activation: HashMap<String, f64> = HashMap::new();
|
||||
let mut queue: VecDeque<(String, f64, u32)> = VecDeque::new();
|
||||
|
|
@ -44,10 +44,7 @@ fn spreading_activation(
|
|||
if depth >= params.max_hops { continue; }
|
||||
|
||||
for (neighbor, strength) in graph.neighbors(&key) {
|
||||
let neighbor_weight = store.nodes.get(neighbor.as_str())
|
||||
.map(|n| n.weight as f64)
|
||||
.unwrap_or(params.default_weight);
|
||||
|
||||
let neighbor_weight = store.node_weight(neighbor.as_str());
|
||||
let propagated = act * params.edge_decay * neighbor_weight * strength as f64;
|
||||
if propagated < params.min_activation { continue; }
|
||||
|
||||
|
|
@ -65,27 +62,26 @@ fn spreading_activation(
|
|||
}
|
||||
|
||||
/// Full search: find direct hits, spread activation, return ranked results
|
||||
pub fn search(query: &str, store: &Store) -> Vec<SearchResult> {
|
||||
let graph = store.build_graph();
|
||||
pub fn search(query: &str, store: &impl StoreView) -> Vec<SearchResult> {
|
||||
let graph = crate::graph::build_graph_fast(store);
|
||||
let query_lower = query.to_lowercase();
|
||||
let query_tokens: Vec<&str> = query_lower.split_whitespace().collect();
|
||||
|
||||
let mut seeds: Vec<(String, f64)> = Vec::new();
|
||||
let mut snippets: HashMap<String, String> = HashMap::new();
|
||||
|
||||
for (key, node) in &store.nodes {
|
||||
let content_lower = node.content.to_lowercase();
|
||||
store.for_each_node(|key, content, weight| {
|
||||
let content_lower = content.to_lowercase();
|
||||
|
||||
let exact_match = content_lower.contains(&query_lower);
|
||||
let token_match = query_tokens.len() > 1
|
||||
&& query_tokens.iter().all(|t| content_lower.contains(t));
|
||||
|
||||
if exact_match || token_match {
|
||||
let weight = node.weight as f64;
|
||||
let activation = if exact_match { weight } else { weight * 0.85 };
|
||||
seeds.push((key.clone(), activation));
|
||||
let activation = if exact_match { weight as f64 } else { weight as f64 * 0.85 };
|
||||
seeds.push((key.to_owned(), activation));
|
||||
|
||||
let snippet: String = node.content.lines()
|
||||
let snippet: String = content.lines()
|
||||
.filter(|l| {
|
||||
let ll = l.to_lowercase();
|
||||
if exact_match && ll.contains(&query_lower) { return true; }
|
||||
|
|
@ -103,9 +99,9 @@ pub fn search(query: &str, store: &Store) -> Vec<SearchResult> {
|
|||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n ");
|
||||
snippets.insert(key.clone(), snippet);
|
||||
snippets.insert(key.to_owned(), snippet);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if seeds.is_empty() {
|
||||
return Vec::new();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue