agents: remove hardcoded dispatch, clean up pub wrappers

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.
This commit is contained in:
ProofOfConcept 2026-03-10 15:53:53 -04:00
parent 16c749f798
commit 46db2e7237
2 changed files with 17 additions and 145 deletions

View file

@ -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,
Ok(content)
}
/// Format topology header for agent prompts — current graph health metrics.
/// Public alias for use from defs.rs (config-driven agents).
pub fn format_topology_header_pub(graph: &Graph) -> 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>, 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>, String) {
pub fn format_rename_candidates(store: &Store, count: usize) -> (Vec<String>, 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<String> {
}
/// 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<AgentBatch, String> {
// 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<String> = 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<String> = 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<String> = 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<ReplayItem> = 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)
}