agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
// Agent definitions: self-contained files with query + prompt template.
|
2026-03-10 15:22:19 -04:00
|
|
|
//
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
// Each agent is a file in the agents/ directory:
|
|
|
|
|
// - First line: JSON header (agent, query, model, schedule)
|
|
|
|
|
// - After blank line: prompt template with {{placeholder}} lookups
|
2026-03-10 15:22:19 -04:00
|
|
|
//
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
// Placeholders are resolved at runtime:
|
|
|
|
|
// {{topology}} — graph topology header
|
|
|
|
|
// {{nodes}} — query results formatted as node sections
|
|
|
|
|
// {{episodes}} — alias for {{nodes}}
|
|
|
|
|
// {{health}} — graph health report
|
|
|
|
|
// {{pairs}} — interference pairs from detect_interference
|
|
|
|
|
// {{rename}} — rename candidates
|
|
|
|
|
// {{split}} — split detail for the first query result
|
|
|
|
|
//
|
|
|
|
|
// The query selects what to operate on; placeholders pull in context.
|
2026-03-10 15:22:19 -04:00
|
|
|
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
use crate::graph::Graph;
|
2026-03-10 15:22:19 -04:00
|
|
|
use crate::neuro::{consolidation_priority, ReplayItem};
|
|
|
|
|
use crate::search;
|
|
|
|
|
use crate::store::Store;
|
|
|
|
|
|
|
|
|
|
use serde::Deserialize;
|
|
|
|
|
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
|
|
/// Agent definition: config (from JSON header) + prompt (raw markdown body).
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct AgentDef {
|
|
|
|
|
pub agent: String,
|
|
|
|
|
pub query: String,
|
|
|
|
|
pub prompt: String,
|
|
|
|
|
pub model: String,
|
|
|
|
|
pub schedule: String,
|
2026-03-13 18:49:49 -04:00
|
|
|
pub tools: Vec<String>,
|
2026-03-10 15:22:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The JSON header portion (first line of the file).
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
struct AgentHeader {
|
|
|
|
|
agent: String,
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
#[serde(default)]
|
2026-03-10 15:22:19 -04:00
|
|
|
query: String,
|
|
|
|
|
#[serde(default = "default_model")]
|
|
|
|
|
model: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
schedule: String,
|
2026-03-13 18:49:49 -04:00
|
|
|
#[serde(default)]
|
|
|
|
|
tools: Vec<String>,
|
2026-03-10 15:22:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_model() -> String { "sonnet".into() }
|
|
|
|
|
|
|
|
|
|
/// Parse an agent file: first line is JSON config, rest is the prompt.
|
|
|
|
|
fn parse_agent_file(content: &str) -> Option<AgentDef> {
|
2026-03-10 16:03:10 -04:00
|
|
|
let (first_line, rest) = content.split_once('\n')?;
|
|
|
|
|
let header: AgentHeader = serde_json::from_str(first_line.trim()).ok()?;
|
|
|
|
|
// Skip optional blank line between header and prompt body
|
|
|
|
|
let prompt = rest.strip_prefix('\n').unwrap_or(rest);
|
2026-03-10 15:22:19 -04:00
|
|
|
Some(AgentDef {
|
|
|
|
|
agent: header.agent,
|
|
|
|
|
query: header.query,
|
|
|
|
|
prompt: prompt.to_string(),
|
|
|
|
|
model: header.model,
|
|
|
|
|
schedule: header.schedule,
|
2026-03-13 18:49:49 -04:00
|
|
|
tools: header.tools,
|
2026-03-10 15:22:19 -04:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn agents_dir() -> PathBuf {
|
|
|
|
|
let repo = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("agents");
|
|
|
|
|
if repo.is_dir() { return repo; }
|
|
|
|
|
crate::store::memory_dir().join("agents")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Load all agent definitions.
|
|
|
|
|
pub fn load_defs() -> Vec<AgentDef> {
|
|
|
|
|
let dir = agents_dir();
|
|
|
|
|
let Ok(entries) = std::fs::read_dir(&dir) else { return Vec::new() };
|
|
|
|
|
|
|
|
|
|
entries
|
|
|
|
|
.filter_map(|e| e.ok())
|
|
|
|
|
.filter(|e| {
|
|
|
|
|
let p = e.path();
|
|
|
|
|
p.extension().map(|x| x == "agent" || x == "md").unwrap_or(false)
|
|
|
|
|
})
|
|
|
|
|
.filter_map(|e| {
|
|
|
|
|
let content = std::fs::read_to_string(e.path()).ok()?;
|
|
|
|
|
parse_agent_file(&content)
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Look up a single agent definition by name.
|
|
|
|
|
pub fn get_def(name: &str) -> Option<AgentDef> {
|
|
|
|
|
let dir = agents_dir();
|
|
|
|
|
for ext in ["agent", "md"] {
|
|
|
|
|
let path = dir.join(format!("{}.{}", name, ext));
|
|
|
|
|
if let Ok(content) = std::fs::read_to_string(&path) {
|
|
|
|
|
if let Some(def) = parse_agent_file(&content) {
|
|
|
|
|
return Some(def);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
load_defs().into_iter().find(|d| d.agent == name)
|
|
|
|
|
}
|
|
|
|
|
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
/// Result of resolving a placeholder: text + any affected node keys.
|
|
|
|
|
struct Resolved {
|
|
|
|
|
text: String,
|
|
|
|
|
keys: Vec<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Resolve a single {{placeholder}} by name.
|
|
|
|
|
/// Returns the replacement text and any node keys it produced (for visit tracking).
|
|
|
|
|
fn resolve(
|
|
|
|
|
name: &str,
|
2026-03-10 15:22:19 -04:00
|
|
|
store: &Store,
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
graph: &Graph,
|
|
|
|
|
keys: &[String],
|
2026-03-10 15:22:19 -04:00
|
|
|
count: usize,
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
) -> Option<Resolved> {
|
|
|
|
|
match name {
|
|
|
|
|
"topology" => Some(Resolved {
|
2026-03-10 15:53:53 -04:00
|
|
|
text: super::prompts::format_topology_header(graph),
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
keys: vec![],
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
"nodes" | "episodes" => {
|
|
|
|
|
let items = keys_to_replay_items(store, keys, graph);
|
|
|
|
|
Some(Resolved {
|
2026-03-10 15:53:53 -04:00
|
|
|
text: super::prompts::format_nodes_section(store, &items, graph),
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
keys: vec![], // keys already tracked from query
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-03-10 15:22:19 -04:00
|
|
|
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
"health" => Some(Resolved {
|
2026-03-10 15:53:53 -04:00
|
|
|
text: super::prompts::format_health_section(store, graph),
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
keys: vec![],
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
"pairs" => {
|
|
|
|
|
let mut pairs = crate::neuro::detect_interference(store, graph, 0.5);
|
|
|
|
|
pairs.truncate(count);
|
|
|
|
|
let pair_keys: Vec<String> = pairs.iter()
|
|
|
|
|
.flat_map(|(a, b, _)| vec![a.clone(), b.clone()])
|
|
|
|
|
.collect();
|
|
|
|
|
Some(Resolved {
|
2026-03-10 15:53:53 -04:00
|
|
|
text: super::prompts::format_pairs_section(&pairs, store, graph),
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
keys: pair_keys,
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-03-10 15:22:19 -04:00
|
|
|
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
"rename" => {
|
2026-03-10 15:53:53 -04:00
|
|
|
let (rename_keys, section) = super::prompts::format_rename_candidates(store, count);
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
Some(Resolved { text: section, keys: rename_keys })
|
|
|
|
|
}
|
2026-03-10 15:22:19 -04:00
|
|
|
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
"split" => {
|
|
|
|
|
let key = keys.first()?;
|
|
|
|
|
Some(Resolved {
|
2026-03-10 15:53:53 -04:00
|
|
|
text: super::prompts::format_split_plan_node(store, graph, key),
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
keys: vec![], // key already tracked from query
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-03-10 15:22:19 -04:00
|
|
|
|
2026-03-13 18:49:49 -04:00
|
|
|
"organize" => {
|
2026-03-13 22:50:39 -04:00
|
|
|
// Show seed nodes with their neighbors for exploratory organizing
|
2026-03-13 21:37:21 -04:00
|
|
|
use crate::store::NodeType;
|
2026-03-13 18:49:49 -04:00
|
|
|
|
2026-03-13 21:37:21 -04:00
|
|
|
// Helper: shell-quote keys containing #
|
|
|
|
|
let sq = |k: &str| -> String {
|
|
|
|
|
if k.contains('#') { format!("'{}'", k) } else { k.to_string() }
|
|
|
|
|
};
|
2026-03-13 18:49:49 -04:00
|
|
|
|
2026-03-13 22:50:39 -04:00
|
|
|
let mut text = format!("### Seed nodes ({} starting points)\n\n", keys.len());
|
|
|
|
|
let mut result_keys = Vec::new();
|
2026-03-13 18:49:49 -04:00
|
|
|
|
2026-03-13 22:50:39 -04:00
|
|
|
for key in keys {
|
|
|
|
|
let Some(node) = store.nodes.get(key) else { continue };
|
|
|
|
|
if node.deleted { continue; }
|
2026-03-13 18:49:49 -04:00
|
|
|
|
2026-03-13 22:50:39 -04:00
|
|
|
let is_journal = node.node_type == NodeType::EpisodicSession;
|
|
|
|
|
let tag = if is_journal { " [JOURNAL — no delete]" } else { "" };
|
|
|
|
|
let words = node.content.split_whitespace().count();
|
|
|
|
|
|
|
|
|
|
text.push_str(&format!("#### {}{} ({} words)\n\n", sq(key), tag, words));
|
|
|
|
|
|
|
|
|
|
// Show first ~200 words of content as preview
|
|
|
|
|
let preview: String = node.content.split_whitespace()
|
|
|
|
|
.take(200).collect::<Vec<_>>().join(" ");
|
|
|
|
|
if words > 200 {
|
|
|
|
|
text.push_str(&format!("{}...\n\n", preview));
|
|
|
|
|
} else {
|
|
|
|
|
text.push_str(&format!("{}\n\n", node.content));
|
2026-03-13 18:49:49 -04:00
|
|
|
}
|
2026-03-13 22:50:39 -04:00
|
|
|
|
|
|
|
|
// Show neighbors with strengths
|
|
|
|
|
let neighbors = graph.neighbors(key);
|
|
|
|
|
if !neighbors.is_empty() {
|
|
|
|
|
text.push_str("**Neighbors:**\n");
|
|
|
|
|
for (nbr, strength) in neighbors.iter().take(15) {
|
|
|
|
|
let nbr_type = store.nodes.get(nbr.as_str())
|
|
|
|
|
.map(|n| match n.node_type {
|
|
|
|
|
NodeType::EpisodicSession => " [journal]",
|
|
|
|
|
NodeType::EpisodicDaily => " [daily]",
|
|
|
|
|
_ => "",
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or("");
|
|
|
|
|
text.push_str(&format!(" [{:.1}] {}{}\n", strength, sq(nbr), nbr_type));
|
|
|
|
|
}
|
|
|
|
|
if neighbors.len() > 15 {
|
|
|
|
|
text.push_str(&format!(" ... and {} more\n", neighbors.len() - 15));
|
2026-03-13 18:49:49 -04:00
|
|
|
}
|
2026-03-13 22:50:39 -04:00
|
|
|
text.push('\n');
|
2026-03-13 18:49:49 -04:00
|
|
|
}
|
|
|
|
|
|
2026-03-13 22:50:39 -04:00
|
|
|
text.push_str("---\n\n");
|
2026-03-13 18:49:49 -04:00
|
|
|
result_keys.push(key.clone());
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 22:50:39 -04:00
|
|
|
text.push_str("Use `poc-memory render KEY` and `poc-memory query \"neighbors('KEY')\"` to explore further.\n");
|
|
|
|
|
|
2026-03-13 18:49:49 -04:00
|
|
|
Some(Resolved { text, keys: result_keys })
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 17:04:44 -04:00
|
|
|
"conversations" => {
|
|
|
|
|
let fragments = super::knowledge::select_conversation_fragments(count);
|
|
|
|
|
let text = fragments.iter()
|
2026-03-12 18:08:58 -04:00
|
|
|
.map(|(id, text)| format!("### Session {}\n\n{}", id, text))
|
2026-03-10 17:04:44 -04:00
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join("\n\n---\n\n");
|
|
|
|
|
Some(Resolved { text, keys: vec![] })
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-12 18:08:58 -04:00
|
|
|
"siblings" | "neighborhood" => {
|
|
|
|
|
let mut seen: std::collections::HashSet<String> = keys.iter().cloned().collect();
|
|
|
|
|
let mut siblings = Vec::new();
|
|
|
|
|
for key in keys {
|
|
|
|
|
for (neighbor, _) in graph.neighbors(key) {
|
|
|
|
|
if seen.insert(neighbor.clone()) {
|
|
|
|
|
if let Some(node) = store.nodes.get(neighbor.as_str()) {
|
|
|
|
|
siblings.push((neighbor.clone(), node.content.clone()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if siblings.len() >= count { break; }
|
|
|
|
|
}
|
|
|
|
|
if siblings.len() >= count { break; }
|
|
|
|
|
}
|
|
|
|
|
let text = if siblings.is_empty() {
|
|
|
|
|
String::new()
|
|
|
|
|
} else {
|
|
|
|
|
let mut out = String::from("## Sibling nodes (one hop in graph)\n\n");
|
|
|
|
|
for (key, content) in &siblings {
|
|
|
|
|
out.push_str(&format!("### {}\n{}\n\n", key, content));
|
|
|
|
|
}
|
|
|
|
|
out
|
|
|
|
|
};
|
|
|
|
|
Some(Resolved { text, keys: vec![] })
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 17:04:44 -04:00
|
|
|
// targets/context: aliases for challenger-style presentation
|
|
|
|
|
"targets" => {
|
|
|
|
|
let items = keys_to_replay_items(store, keys, graph);
|
|
|
|
|
Some(Resolved {
|
|
|
|
|
text: super::prompts::format_nodes_section(store, &items, graph),
|
|
|
|
|
keys: vec![],
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
_ => None,
|
2026-03-10 15:22:19 -04:00
|
|
|
}
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
}
|
2026-03-10 15:22:19 -04:00
|
|
|
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
/// Resolve all {{placeholder}} patterns in a prompt template.
|
|
|
|
|
/// Returns the resolved text and all node keys collected from placeholders.
|
2026-03-10 17:51:32 -04:00
|
|
|
pub fn resolve_placeholders(
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
template: &str,
|
|
|
|
|
store: &Store,
|
|
|
|
|
graph: &Graph,
|
|
|
|
|
keys: &[String],
|
|
|
|
|
count: usize,
|
|
|
|
|
) -> (String, Vec<String>) {
|
|
|
|
|
let mut result = template.to_string();
|
|
|
|
|
let mut extra_keys = Vec::new();
|
|
|
|
|
loop {
|
|
|
|
|
let Some(start) = result.find("{{") else { break };
|
|
|
|
|
let Some(end) = result[start + 2..].find("}}") else { break };
|
|
|
|
|
let end = start + 2 + end;
|
|
|
|
|
let name = result[start + 2..end].trim().to_lowercase();
|
|
|
|
|
match resolve(&name, store, graph, keys, count) {
|
|
|
|
|
Some(resolved) => {
|
|
|
|
|
extra_keys.extend(resolved.keys);
|
|
|
|
|
result.replace_range(start..end + 2, &resolved.text);
|
|
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
let msg = format!("(unknown: {})", name);
|
|
|
|
|
result.replace_range(start..end + 2, &msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
(result, extra_keys)
|
|
|
|
|
}
|
2026-03-10 15:22:19 -04:00
|
|
|
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
/// Run a config-driven agent: query → resolve placeholders → prompt.
|
|
|
|
|
pub fn run_agent(
|
|
|
|
|
store: &Store,
|
|
|
|
|
def: &AgentDef,
|
|
|
|
|
count: usize,
|
|
|
|
|
) -> Result<super::prompts::AgentBatch, String> {
|
|
|
|
|
let graph = store.build_graph();
|
|
|
|
|
|
|
|
|
|
// Run the query if present
|
|
|
|
|
let keys = if !def.query.is_empty() {
|
|
|
|
|
let mut stages = search::Stage::parse_pipeline(&def.query)?;
|
|
|
|
|
let has_limit = stages.iter().any(|s|
|
|
|
|
|
matches!(s, search::Stage::Transform(search::Transform::Limit(_))));
|
|
|
|
|
if !has_limit {
|
|
|
|
|
stages.push(search::Stage::Transform(search::Transform::Limit(count)));
|
|
|
|
|
}
|
|
|
|
|
let results = search::run_query(&stages, vec![], &graph, store, false, count);
|
|
|
|
|
if results.is_empty() {
|
|
|
|
|
return Err(format!("{}: query returned no results", def.agent));
|
|
|
|
|
}
|
|
|
|
|
results.into_iter().map(|(k, _)| k).collect::<Vec<_>>()
|
|
|
|
|
} else {
|
|
|
|
|
vec![]
|
|
|
|
|
};
|
2026-03-10 15:22:19 -04:00
|
|
|
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
let (prompt, extra_keys) = resolve_placeholders(&def.prompt, store, &graph, &keys, count);
|
2026-03-10 15:22:19 -04:00
|
|
|
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
// Merge query keys with any keys produced by placeholder resolution
|
|
|
|
|
let mut all_keys = keys;
|
|
|
|
|
all_keys.extend(extra_keys);
|
|
|
|
|
Ok(super::prompts::AgentBatch { prompt, node_keys: all_keys })
|
2026-03-10 15:22:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convert a list of keys to ReplayItems with priority and graph metrics.
|
|
|
|
|
pub fn keys_to_replay_items(
|
|
|
|
|
store: &Store,
|
|
|
|
|
keys: &[String],
|
agents: placeholder-based prompt templates, port remaining 4 agents
Replace the formatter dispatch with a generic {{placeholder}} lookup
system. Placeholders in prompt templates are resolved at runtime from
a table: topology, nodes, episodes, health, pairs, rename, split.
The query in the header selects what to operate on (keys for visit
tracking); placeholders pull in formatted context. Placeholders that
produce their own node selection (pairs, rename) contribute keys back.
Port health, separator, rename, and split agents to .agent files.
All 7 agents now use the config-driven path.
2026-03-10 15:50:54 -04:00
|
|
|
graph: &Graph,
|
2026-03-10 15:22:19 -04:00
|
|
|
) -> Vec<ReplayItem> {
|
|
|
|
|
keys.iter()
|
|
|
|
|
.filter_map(|key| {
|
|
|
|
|
let node = store.nodes.get(key)?;
|
|
|
|
|
let priority = consolidation_priority(store, key, graph, None);
|
|
|
|
|
let cc = graph.clustering_coefficient(key);
|
|
|
|
|
|
|
|
|
|
Some(ReplayItem {
|
|
|
|
|
key: key.clone(),
|
|
|
|
|
priority,
|
|
|
|
|
interval_days: node.spaced_repetition_interval,
|
|
|
|
|
emotion: node.emotion,
|
|
|
|
|
cc,
|
|
|
|
|
classification: "unknown",
|
|
|
|
|
outlier_score: 0.0,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|