From 46db2e72371896dd5ec12cab30cb92a8ac62b962 Mon Sep 17 00:00:00 2001 From: ProofOfConcept Date: Tue, 10 Mar 2026 15:53:53 -0400 Subject: [PATCH] agents: remove hardcoded dispatch, clean up pub wrappers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All agents now go through the config-driven path via .agent files. agent_prompt() just delegates to defs::run_agent(). Remove the 100+ line hardcoded match block and the _pub wrapper functions — make the formatters pub directly. --- poc-memory/src/agents/defs.rs | 12 +-- poc-memory/src/agents/prompts.rs | 150 +++---------------------------- 2 files changed, 17 insertions(+), 145 deletions(-) diff --git a/poc-memory/src/agents/defs.rs b/poc-memory/src/agents/defs.rs index 8b6c06f..ca5c7a6 100644 --- a/poc-memory/src/agents/defs.rs +++ b/poc-memory/src/agents/defs.rs @@ -116,20 +116,20 @@ fn resolve( ) -> Option { match name { "topology" => Some(Resolved { - text: super::prompts::format_topology_header_pub(graph), + text: super::prompts::format_topology_header(graph), keys: vec![], }), "nodes" | "episodes" => { let items = keys_to_replay_items(store, keys, graph); Some(Resolved { - text: super::prompts::format_nodes_section_pub(store, &items, graph), + text: super::prompts::format_nodes_section(store, &items, graph), keys: vec![], // keys already tracked from query }) } "health" => Some(Resolved { - text: super::prompts::format_health_section_pub(store, graph), + text: super::prompts::format_health_section(store, graph), keys: vec![], }), @@ -140,20 +140,20 @@ fn resolve( .flat_map(|(a, b, _)| vec![a.clone(), b.clone()]) .collect(); Some(Resolved { - text: super::prompts::format_pairs_section_pub(&pairs, store, graph), + text: super::prompts::format_pairs_section(&pairs, store, graph), keys: pair_keys, }) } "rename" => { - let (rename_keys, section) = super::prompts::format_rename_candidates_pub(store, count); + let (rename_keys, section) = super::prompts::format_rename_candidates(store, count); Some(Resolved { text: section, keys: rename_keys }) } "split" => { let key = keys.first()?; Some(Resolved { - text: super::prompts::format_split_plan_node_pub(store, graph, key), + text: super::prompts::format_split_plan_node(store, graph, key), keys: vec![], // key already tracked from query }) } diff --git a/poc-memory/src/agents/prompts.rs b/poc-memory/src/agents/prompts.rs index 3f57ec3..955aeee 100644 --- a/poc-memory/src/agents/prompts.rs +++ b/poc-memory/src/agents/prompts.rs @@ -4,11 +4,10 @@ use crate::store::Store; use crate::graph::Graph; use crate::similarity; -use crate::spectral; use crate::neuro::{ - ReplayItem, consolidation_priority, - replay_queue, replay_queue_with_graph, detect_interference, + ReplayItem, + replay_queue, detect_interference, }; /// Result of building an agent prompt — includes both the prompt text @@ -30,13 +29,7 @@ pub fn load_prompt(name: &str, replacements: &[(&str, &str)]) -> Result String { - format_topology_header(graph) -} - -fn format_topology_header(graph: &Graph) -> String { +pub fn format_topology_header(graph: &Graph) -> String { let sigma = graph.small_world_sigma(); let alpha = graph.degree_power_law_exponent(); let gini = graph.degree_gini(); @@ -79,13 +72,7 @@ fn format_topology_header(graph: &Graph) -> String { n, e, graph.community_count(), sigma, alpha, gini, avg_cc, hub_list) } -/// Public alias for use from defs.rs (config-driven agents). -pub fn format_nodes_section_pub(store: &Store, items: &[ReplayItem], graph: &Graph) -> String { - format_nodes_section(store, items, graph) -} - -/// Format node data section for prompt templates -fn format_nodes_section(store: &Store, items: &[ReplayItem], graph: &Graph) -> String { +pub fn format_nodes_section(store: &Store, items: &[ReplayItem], graph: &Graph) -> String { let hub_thresh = graph.hub_threshold(); let mut out = String::new(); for item in items { @@ -185,12 +172,7 @@ fn format_nodes_section(store: &Store, items: &[ReplayItem], graph: &Graph) -> S out } -/// Format health data for the health agent prompt -pub fn format_health_section_pub(store: &Store, graph: &Graph) -> String { - format_health_section(store, graph) -} - -fn format_health_section(store: &Store, graph: &Graph) -> String { +pub fn format_health_section(store: &Store, graph: &Graph) -> String { use crate::graph; let health = graph::health_report(graph, store); @@ -246,16 +228,7 @@ fn format_health_section(store: &Store, graph: &Graph) -> String { out } -pub fn format_pairs_section_pub( - pairs: &[(String, String, f32)], - store: &Store, - graph: &Graph, -) -> String { - format_pairs_section(pairs, store, graph) -} - -/// Format interference pairs for the separator agent prompt -fn format_pairs_section( +pub fn format_pairs_section( pairs: &[(String, String, f32)], store: &Store, graph: &Graph, @@ -290,12 +263,7 @@ fn format_pairs_section( out } -pub fn format_rename_candidates_pub(store: &Store, count: usize) -> (Vec, String) { - format_rename_candidates_with_keys(store, count) -} - -/// Format rename candidates, returning both keys and formatted section -fn format_rename_candidates_with_keys(store: &Store, count: usize) -> (Vec, String) { +pub fn format_rename_candidates(store: &Store, count: usize) -> (Vec, String) { let mut candidates: Vec<(&str, &crate::store::Node)> = store.nodes.iter() .filter(|(key, _)| { if key.len() < 60 { return false; } @@ -355,11 +323,7 @@ pub fn split_candidates(store: &Store) -> Vec { } /// Format a single node for split-plan prompt (phase 1) -pub fn format_split_plan_node_pub(store: &Store, graph: &Graph, key: &str) -> String { - format_split_plan_node(store, graph, key) -} - -fn format_split_plan_node(store: &Store, graph: &Graph, key: &str) -> String { +pub fn format_split_plan_node(store: &Store, graph: &Graph, key: &str) -> String { let communities = graph.communities(); let node = match store.nodes.get(key) { Some(n) => n, @@ -474,99 +438,7 @@ pub fn consolidation_batch(store: &Store, count: usize, auto: bool) -> Result<() /// Returns an AgentBatch with the prompt text and the keys of nodes /// selected for processing (for visit tracking on success). pub fn agent_prompt(store: &Store, agent: &str, count: usize) -> Result { - // Config-driven agents take priority over hardcoded ones - if let Some(def) = super::defs::get_def(agent) { - return super::defs::run_agent(store, &def, count); - } - - let graph = store.build_graph(); - let topology = format_topology_header(&graph); - - let emb = spectral::load_embedding().ok(); - - match agent { - "replay" => { - let items = replay_queue_with_graph(store, count, &graph, emb.as_ref()); - let keys: Vec = items.iter().map(|i| i.key.clone()).collect(); - let nodes_section = format_nodes_section(store, &items, &graph); - let prompt = load_prompt("replay", &[("{{TOPOLOGY}}", &topology), ("{{NODES}}", &nodes_section)])?; - Ok(AgentBatch { prompt, node_keys: keys }) - } - "linker" => { - // Filter to episodic entries - let mut items = replay_queue_with_graph(store, count * 2, &graph, emb.as_ref()); - items.retain(|item| { - store.nodes.get(&item.key) - .map(|n| matches!(n.node_type, crate::store::NodeType::EpisodicSession)) - .unwrap_or(false) - }); - items.truncate(count); - let keys: Vec = items.iter().map(|i| i.key.clone()).collect(); - let nodes_section = format_nodes_section(store, &items, &graph); - let prompt = load_prompt("linker", &[("{{TOPOLOGY}}", &topology), ("{{NODES}}", &nodes_section)])?; - Ok(AgentBatch { prompt, node_keys: keys }) - } - "separator" => { - let mut pairs = detect_interference(store, &graph, 0.5); - pairs.truncate(count); - // Both nodes in each pair count as visited - let keys: Vec = pairs.iter() - .flat_map(|(a, b, _)| vec![a.clone(), b.clone()]) - .collect(); - let pairs_section = format_pairs_section(&pairs, store, &graph); - let prompt = load_prompt("separator", &[("{{TOPOLOGY}}", &topology), ("{{PAIRS}}", &pairs_section)])?; - Ok(AgentBatch { prompt, node_keys: keys }) - } - "transfer" => { - // Recent episodic entries - let mut episodes: Vec<_> = store.nodes.iter() - .filter(|(_, n)| matches!(n.node_type, crate::store::NodeType::EpisodicSession)) - .map(|(k, n)| (k.clone(), n.timestamp)) - .collect(); - episodes.sort_by(|a, b| b.1.cmp(&a.1)); - episodes.truncate(count); - - let episode_keys: Vec<_> = episodes.iter().map(|(k, _)| k.clone()).collect(); - let items: Vec = episode_keys.iter() - .filter_map(|k| { - let node = store.nodes.get(k)?; - Some(ReplayItem { - key: k.clone(), - priority: consolidation_priority(store, k, &graph, None), - interval_days: node.spaced_repetition_interval, - emotion: node.emotion, - cc: graph.clustering_coefficient(k), - classification: "unknown", - outlier_score: 0.0, - }) - }) - .collect(); - let episodes_section = format_nodes_section(store, &items, &graph); - let prompt = load_prompt("transfer", &[("{{TOPOLOGY}}", &topology), ("{{EPISODES}}", &episodes_section)])?; - Ok(AgentBatch { prompt, node_keys: episode_keys }) - } - "health" => { - // Health agent analyzes the whole graph, no specific nodes - let health_section = format_health_section(store, &graph); - let prompt = load_prompt("health", &[("{{TOPOLOGY}}", &topology), ("{{HEALTH}}", &health_section)])?; - Ok(AgentBatch { prompt, node_keys: vec![] }) - } - "rename" => { - let (keys, nodes_section) = format_rename_candidates_with_keys(store, count); - let prompt = load_prompt("rename", &[("{{NODES}}", &nodes_section)])?; - Ok(AgentBatch { prompt, node_keys: keys }) - } - "split" => { - // Phase 1: plan prompt for the largest candidate - let candidates = split_candidates(store); - if candidates.is_empty() { - return Err("No nodes large enough to split".to_string()); - } - let key = candidates[0].clone(); - let node_section = format_split_plan_node(store, &graph, &key); - let prompt = load_prompt("split-plan", &[("{{TOPOLOGY}}", &topology), ("{{NODE}}", &node_section)])?; - Ok(AgentBatch { prompt, node_keys: vec![key] }) - } - _ => Err(format!("Unknown agent: {}. Use: replay, linker, separator, transfer, health, rename, split", agent)), - } + let def = super::defs::get_def(agent) + .ok_or_else(|| format!("Unknown agent: {}", agent))?; + super::defs::run_agent(store, &def, count) }