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> {
|
) -> Option<Resolved> {
|
||||||
match name {
|
match name {
|
||||||
"topology" => Some(Resolved {
|
"topology" => Some(Resolved {
|
||||||
text: super::prompts::format_topology_header_pub(graph),
|
text: super::prompts::format_topology_header(graph),
|
||||||
keys: vec![],
|
keys: vec![],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
"nodes" | "episodes" => {
|
"nodes" | "episodes" => {
|
||||||
let items = keys_to_replay_items(store, keys, graph);
|
let items = keys_to_replay_items(store, keys, graph);
|
||||||
Some(Resolved {
|
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
|
keys: vec![], // keys already tracked from query
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
"health" => Some(Resolved {
|
"health" => Some(Resolved {
|
||||||
text: super::prompts::format_health_section_pub(store, graph),
|
text: super::prompts::format_health_section(store, graph),
|
||||||
keys: vec![],
|
keys: vec![],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
@ -140,20 +140,20 @@ fn resolve(
|
||||||
.flat_map(|(a, b, _)| vec![a.clone(), b.clone()])
|
.flat_map(|(a, b, _)| vec![a.clone(), b.clone()])
|
||||||
.collect();
|
.collect();
|
||||||
Some(Resolved {
|
Some(Resolved {
|
||||||
text: super::prompts::format_pairs_section_pub(&pairs, store, graph),
|
text: super::prompts::format_pairs_section(&pairs, store, graph),
|
||||||
keys: pair_keys,
|
keys: pair_keys,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
"rename" => {
|
"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 })
|
Some(Resolved { text: section, keys: rename_keys })
|
||||||
}
|
}
|
||||||
|
|
||||||
"split" => {
|
"split" => {
|
||||||
let key = keys.first()?;
|
let key = keys.first()?;
|
||||||
Some(Resolved {
|
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
|
keys: vec![], // key already tracked from query
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@
|
||||||
use crate::store::Store;
|
use crate::store::Store;
|
||||||
use crate::graph::Graph;
|
use crate::graph::Graph;
|
||||||
use crate::similarity;
|
use crate::similarity;
|
||||||
use crate::spectral;
|
|
||||||
|
|
||||||
use crate::neuro::{
|
use crate::neuro::{
|
||||||
ReplayItem, consolidation_priority,
|
ReplayItem,
|
||||||
replay_queue, replay_queue_with_graph, detect_interference,
|
replay_queue, detect_interference,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Result of building an agent prompt — includes both the prompt text
|
/// 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)
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format topology header for agent prompts — current graph health metrics.
|
pub fn format_topology_header(graph: &Graph) -> String {
|
||||||
/// 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 {
|
|
||||||
let sigma = graph.small_world_sigma();
|
let sigma = graph.small_world_sigma();
|
||||||
let alpha = graph.degree_power_law_exponent();
|
let alpha = graph.degree_power_law_exponent();
|
||||||
let gini = graph.degree_gini();
|
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)
|
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(store: &Store, items: &[ReplayItem], graph: &Graph) -> String {
|
||||||
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 {
|
|
||||||
let hub_thresh = graph.hub_threshold();
|
let hub_thresh = graph.hub_threshold();
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
for item in items {
|
for item in items {
|
||||||
|
|
@ -185,12 +172,7 @@ fn format_nodes_section(store: &Store, items: &[ReplayItem], graph: &Graph) -> S
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format health data for the health agent prompt
|
pub fn format_health_section(store: &Store, graph: &Graph) -> String {
|
||||||
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 {
|
|
||||||
use crate::graph;
|
use crate::graph;
|
||||||
|
|
||||||
let health = graph::health_report(graph, store);
|
let health = graph::health_report(graph, store);
|
||||||
|
|
@ -246,16 +228,7 @@ fn format_health_section(store: &Store, graph: &Graph) -> String {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_pairs_section_pub(
|
pub fn format_pairs_section(
|
||||||
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(
|
|
||||||
pairs: &[(String, String, f32)],
|
pairs: &[(String, String, f32)],
|
||||||
store: &Store,
|
store: &Store,
|
||||||
graph: &Graph,
|
graph: &Graph,
|
||||||
|
|
@ -290,12 +263,7 @@ fn format_pairs_section(
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_rename_candidates_pub(store: &Store, count: usize) -> (Vec<String>, String) {
|
pub fn format_rename_candidates(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) {
|
|
||||||
let mut candidates: Vec<(&str, &crate::store::Node)> = store.nodes.iter()
|
let mut candidates: Vec<(&str, &crate::store::Node)> = store.nodes.iter()
|
||||||
.filter(|(key, _)| {
|
.filter(|(key, _)| {
|
||||||
if key.len() < 60 { return false; }
|
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)
|
/// Format a single node for split-plan prompt (phase 1)
|
||||||
pub fn format_split_plan_node_pub(store: &Store, graph: &Graph, key: &str) -> String {
|
pub fn format_split_plan_node(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 {
|
|
||||||
let communities = graph.communities();
|
let communities = graph.communities();
|
||||||
let node = match store.nodes.get(key) {
|
let node = match store.nodes.get(key) {
|
||||||
Some(n) => n,
|
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
|
/// Returns an AgentBatch with the prompt text and the keys of nodes
|
||||||
/// selected for processing (for visit tracking on success).
|
/// selected for processing (for visit tracking on success).
|
||||||
pub fn agent_prompt(store: &Store, agent: &str, count: usize) -> Result<AgentBatch, String> {
|
pub fn agent_prompt(store: &Store, agent: &str, count: usize) -> Result<AgentBatch, String> {
|
||||||
// Config-driven agents take priority over hardcoded ones
|
let def = super::defs::get_def(agent)
|
||||||
if let Some(def) = super::defs::get_def(agent) {
|
.ok_or_else(|| format!("Unknown agent: {}", agent))?;
|
||||||
return super::defs::run_agent(store, &def, count);
|
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)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue