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:
parent
16c749f798
commit
46db2e7237
2 changed files with 17 additions and 145 deletions
|
|
@ -116,20 +116,20 @@ fn resolve(
|
|||
) -> Option<Resolved> {
|
||||
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
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue