triangle-close: bulk lateral linking for clustering coefficient
New command: `poc-memory triangle-close [MIN_DEG] [SIM] [MAX_PER_HUB]` For each node above min_degree, finds pairs of its neighbors that aren't directly connected and have text similarity above threshold. Links them. This turns hub-spoke patterns into triangles, directly improving clustering coefficient and schema fit. First run results (default params: deg≥5, sim≥0.3, max 10/hub): - 636 hubs processed, 5046 lateral links added - cc: 0.14 → 0.46 (target: high) - fit: 0.09 → 0.32 (target ≥0.2) - σ: 56.9 → 84.4 (small-world coefficient improved) Also fixes separator agent prompt: truncate interference pairs to batch count (was including all 1114 pairs = 1.3M chars).
This commit is contained in:
parent
6bc11e5fb6
commit
6c7bfb9ec4
2 changed files with 108 additions and 0 deletions
23
src/main.rs
23
src/main.rs
|
|
@ -87,6 +87,7 @@ fn main() {
|
|||
"link-impact" => cmd_link_impact(&args[2..]),
|
||||
"consolidate-session" => cmd_consolidate_session(),
|
||||
"consolidate-full" => cmd_consolidate_full(),
|
||||
"triangle-close" => cmd_triangle_close(&args[2..]),
|
||||
"daily-check" => cmd_daily_check(),
|
||||
"apply-agent" => cmd_apply_agent(&args[2..]),
|
||||
"digest" => cmd_digest(&args[2..]),
|
||||
|
|
@ -149,6 +150,8 @@ Commands:
|
|||
link-impact SOURCE TARGET Simulate adding an edge, report topology impact
|
||||
consolidate-session Analyze metrics, plan agent allocation
|
||||
consolidate-full Autonomous: plan → agents → apply → digests → links
|
||||
triangle-close [DEG] [SIM] [MAX]
|
||||
Close triangles: link similar neighbors of hubs
|
||||
daily-check Brief metrics check (for cron/notifications)
|
||||
apply-agent [--all] Import pending agent results into the graph
|
||||
digest daily [DATE] Generate daily episodic digest (default: today)
|
||||
|
|
@ -437,6 +440,26 @@ fn cmd_consolidate_full() -> Result<(), String> {
|
|||
digest::consolidate_full(&mut store)
|
||||
}
|
||||
|
||||
fn cmd_triangle_close(args: &[String]) -> Result<(), String> {
|
||||
let min_degree: usize = args.first()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(5);
|
||||
let sim_threshold: f32 = args.get(1)
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(0.3);
|
||||
let max_per_hub: usize = args.get(2)
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(10);
|
||||
|
||||
println!("Triangle closure: min_degree={}, sim_threshold={}, max_per_hub={}",
|
||||
min_degree, sim_threshold, max_per_hub);
|
||||
|
||||
let mut store = capnp_store::Store::load()?;
|
||||
let (hubs, added) = neuro::triangle_close(&mut store, min_degree, sim_threshold, max_per_hub);
|
||||
println!("\nProcessed {} hubs, added {} lateral links", hubs, added);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cmd_daily_check() -> Result<(), String> {
|
||||
let store = capnp_store::Store::load()?;
|
||||
let report = neuro::daily_check(&store);
|
||||
|
|
|
|||
85
src/neuro.rs
85
src/neuro.rs
|
|
@ -974,3 +974,88 @@ pub fn find_differentiable_hubs(store: &Store) -> Vec<(String, usize, usize)> {
|
|||
hubs.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
hubs
|
||||
}
|
||||
|
||||
/// Triangle closure: for each node with degree >= min_degree, find pairs
|
||||
/// of its neighbors that aren't directly connected and have cosine
|
||||
/// similarity above sim_threshold. Add links between them.
|
||||
///
|
||||
/// This turns hub-spoke patterns into triangles, directly improving
|
||||
/// clustering coefficient and schema fit.
|
||||
pub fn triangle_close(
|
||||
store: &mut Store,
|
||||
min_degree: usize,
|
||||
sim_threshold: f32,
|
||||
max_links_per_hub: usize,
|
||||
) -> (usize, usize) {
|
||||
let graph = store.build_graph();
|
||||
let mut added = 0usize;
|
||||
let mut hubs_processed = 0usize;
|
||||
|
||||
// Get nodes sorted by degree (highest first)
|
||||
let mut candidates: Vec<(String, usize)> = graph.nodes().iter()
|
||||
.map(|k| (k.clone(), graph.degree(k)))
|
||||
.filter(|(_, d)| *d >= min_degree)
|
||||
.collect();
|
||||
candidates.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
|
||||
for (hub_key, hub_deg) in &candidates {
|
||||
let neighbors = graph.neighbor_keys(hub_key);
|
||||
if neighbors.len() < 2 { continue; }
|
||||
|
||||
// Collect neighbor content for similarity
|
||||
let neighbor_docs: Vec<(String, String)> = neighbors.iter()
|
||||
.filter_map(|&k| {
|
||||
store.nodes.get(k).map(|n| (k.to_string(), n.content.clone()))
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Find unconnected pairs with high similarity
|
||||
let mut pair_scores: Vec<(String, String, f32)> = Vec::new();
|
||||
for i in 0..neighbor_docs.len() {
|
||||
for j in (i + 1)..neighbor_docs.len() {
|
||||
// Check if already connected
|
||||
let n_i = graph.neighbor_keys(&neighbor_docs[i].0);
|
||||
if n_i.contains(neighbor_docs[j].0.as_str()) { continue; }
|
||||
|
||||
let sim = similarity::cosine_similarity(
|
||||
&neighbor_docs[i].1, &neighbor_docs[j].1);
|
||||
if sim >= sim_threshold {
|
||||
pair_scores.push((
|
||||
neighbor_docs[i].0.clone(),
|
||||
neighbor_docs[j].0.clone(),
|
||||
sim,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pair_scores.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
|
||||
let to_add = pair_scores.len().min(max_links_per_hub);
|
||||
|
||||
if to_add > 0 {
|
||||
println!(" {} (deg={}) — {} triangles to close (top {})",
|
||||
hub_key, hub_deg, pair_scores.len(), to_add);
|
||||
|
||||
for (a, b, sim) in pair_scores.iter().take(to_add) {
|
||||
let uuid_a = match store.nodes.get(a) { Some(n) => n.uuid, None => continue };
|
||||
let uuid_b = match store.nodes.get(b) { Some(n) => n.uuid, None => continue };
|
||||
|
||||
let rel = Store::new_relation(
|
||||
uuid_a, uuid_b,
|
||||
crate::capnp_store::RelationType::Auto,
|
||||
sim * 0.5, // scale by similarity
|
||||
a, b,
|
||||
);
|
||||
if let Ok(()) = store.add_relation(rel) {
|
||||
added += 1;
|
||||
}
|
||||
}
|
||||
hubs_processed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if added > 0 {
|
||||
let _ = store.save();
|
||||
}
|
||||
(hubs_processed, added)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue