diff --git a/poc-memory/src/agents/daemon.rs b/poc-memory/src/agents/daemon.rs index cf1afd8..f29abd0 100644 --- a/poc-memory/src/agents/daemon.rs +++ b/poc-memory/src/agents/daemon.rs @@ -480,6 +480,7 @@ fn compute_graph_health(store: &crate::store::Store) -> GraphHealth { plan_transfer: plan.transfer_count, plan_organize: plan.organize_count, plan_connector: plan.connector_count, + plan_distill: plan.distill_count, plan_rationale: plan.rationale, computed_at: crate::store::format_datetime_space(crate::store::now_epoch()), } @@ -607,6 +608,8 @@ pub struct GraphHealth { pub plan_transfer: usize, pub plan_organize: usize, pub plan_connector: usize, + #[serde(default)] + pub plan_distill: usize, pub plan_rationale: Vec, pub computed_at: String, } @@ -995,15 +998,16 @@ pub fn run_daemon() -> Result<(), String> { transfer_count: h.plan_transfer, organize_count: h.plan_organize, connector_count: h.plan_connector, + distill_count: h.plan_distill, run_health: true, rationale: Vec::new(), }; let runs = plan.to_agent_runs(5); log_event("scheduler", "consolidation-plan", - &format!("{} agents ({}r {}l {}s {}t)", + &format!("{} agents ({}r {}l {}s {}t {}d)", runs.len(), h.plan_replay, h.plan_linker, - h.plan_separator, h.plan_transfer)); + h.plan_separator, h.plan_transfer, h.plan_distill)); // Phase 1: Agent runs — sequential within type, parallel across types. // Same-type agents chain (they may touch overlapping graph regions), @@ -1400,9 +1404,9 @@ pub fn show_status() -> Result<(), String> { indicator(gh.episodic_ratio, 0.4, false), gh.episodic_ratio * 100.0, gh.sigma); - let total = gh.plan_replay + gh.plan_linker + gh.plan_separator + gh.plan_transfer + 1; - eprintln!(" consolidation plan: {} agents ({}r {}l {}s {}t +health)", - total, gh.plan_replay, gh.plan_linker, gh.plan_separator, gh.plan_transfer); + let total = gh.plan_replay + gh.plan_linker + gh.plan_separator + gh.plan_transfer + gh.plan_distill + 1; + eprintln!(" consolidation plan: {} agents ({}r {}l {}s {}t {}d +health)", + total, gh.plan_replay, gh.plan_linker, gh.plan_separator, gh.plan_transfer, gh.plan_distill); } eprintln!(); diff --git a/poc-memory/src/neuro/scoring.rs b/poc-memory/src/neuro/scoring.rs index 44b6fa1..e4c8885 100644 --- a/poc-memory/src/neuro/scoring.rs +++ b/poc-memory/src/neuro/scoring.rs @@ -172,6 +172,7 @@ pub struct ConsolidationPlan { pub transfer_count: usize, pub organize_count: usize, pub connector_count: usize, + pub distill_count: usize, pub run_health: bool, pub rationale: Vec, } @@ -185,9 +186,10 @@ impl ConsolidationPlan { } // Build per-type batch lists, then interleave so different agent // types alternate rather than running all-replay-then-all-linker. - let types: [(&str, usize); 6] = [ + let types: [(&str, usize); 7] = [ ("linker", self.linker_count), ("organize", self.organize_count), + ("distill", self.distill_count), ("replay", self.replay_count), ("connector", self.connector_count), ("separator", self.separator_count), @@ -257,6 +259,7 @@ fn consolidation_plan_inner(store: &Store, detect_interf: bool) -> Consolidation transfer_count: 0, organize_count: 0, connector_count: 0, + distill_count: 0, run_health: true, rationale: Vec::new(), }; @@ -341,6 +344,18 @@ fn consolidation_plan_inner(store: &Store, detect_interf: bool) -> Consolidation plan.rationale.push(format!( "Organize: {} (half of linker count)", plan.organize_count)); + // Distill: core concept maintenance — at least as much as organize + // High gini means hubs need refinement; low alpha means hubs are overloaded + plan.distill_count = plan.organize_count; + if gini > 0.4 { + plan.distill_count += 20; + } + if alpha < 2.0 { + plan.distill_count += 20; + } + plan.rationale.push(format!( + "Distill: {} (synthesize hub content)", plan.distill_count)); + // Connector: bridges fragmented communities let community_count = graph.community_count(); let nodes_per_community = if community_count > 0 { @@ -365,7 +380,7 @@ fn consolidation_plan_inner(store: &Store, detect_interf: bool) -> Consolidation if let Ok(ratings) = serde_json::from_str::>(&elo_json) { let types = [ "replay", "linker", "separator", "transfer", - "organize", "connector", + "organize", "connector", "distill", ]; let elos: Vec = types.iter() .map(|t| ratings.get(*t).copied().unwrap_or(1000.0)) @@ -392,22 +407,24 @@ fn consolidation_plan_inner(store: &Store, detect_interf: bool) -> Consolidation plan.transfer_count = allocate(weights[3]); plan.organize_count = allocate(weights[4]); plan.connector_count = allocate(weights[5]); + plan.distill_count = allocate(weights[6]); plan.rationale.push(format!( - "Elo allocation (budget={}): replay={} linker={} separator={} transfer={} organize={} connector={}", + "Elo allocation (budget={}): replay={} linker={} separator={} transfer={} organize={} connector={} distill={}", budget, plan.replay_count, plan.linker_count, plan.separator_count, - plan.transfer_count, plan.organize_count, plan.connector_count)); + plan.transfer_count, plan.organize_count, plan.connector_count, plan.distill_count)); } } else { // No Elo file — use budget with equal distribution - let per_type = budget / 6; + let per_type = budget / 7; plan.replay_count = per_type; plan.linker_count = per_type; plan.separator_count = per_type; plan.transfer_count = per_type; plan.organize_count = per_type; plan.connector_count = per_type; + plan.distill_count = per_type; plan.rationale.push(format!( "No Elo ratings — equal distribution ({} each, budget={})", per_type, budget)); } @@ -457,11 +474,17 @@ pub fn format_plan(plan: &ConsolidationPlan) -> String { if plan.connector_count > 0 { out.push_str(&format!(" {}. connector ×{} — cross-cluster bridging\n", step, plan.connector_count)); + step += 1; + } + if plan.distill_count > 0 { + out.push_str(&format!(" {}. distill ×{:2} — hub content synthesis + refinement\n", + step, plan.distill_count)); } let total = plan.replay_count + plan.linker_count + plan.separator_count + plan.transfer_count + plan.organize_count + plan.connector_count + + plan.distill_count + if plan.run_health { 1 } else { 0 }; out.push_str(&format!("\nTotal agent runs: {}\n", total)); diff --git a/poc-memory/src/tui.rs b/poc-memory/src/tui.rs index d3de45f..5c290d2 100644 --- a/poc-memory/src/tui.rs +++ b/poc-memory/src/tui.rs @@ -539,7 +539,7 @@ fn render_health(frame: &mut Frame, gh: &GraphHealth, area: Rect) { ); // Plan - let total = gh.plan_replay + gh.plan_linker + gh.plan_separator + gh.plan_transfer + 1; + let total = gh.plan_replay + gh.plan_linker + gh.plan_separator + gh.plan_transfer + gh.plan_distill + 1; let plan_line = Line::from(vec![ Span::raw(" plan: "), Span::styled( @@ -547,8 +547,8 @@ fn render_health(frame: &mut Frame, gh: &GraphHealth, area: Rect) { Style::default().add_modifier(Modifier::BOLD), ), Span::raw(format!( - " agents ({}r {}l {}s {}t +health)", - gh.plan_replay, gh.plan_linker, gh.plan_separator, gh.plan_transfer + " agents ({}r {}l {}s {}t {}d +health)", + gh.plan_replay, gh.plan_linker, gh.plan_separator, gh.plan_transfer, gh.plan_distill )), ]); frame.render_widget(Paragraph::new(plan_line), plan_area);