Add graph_topology, graph_health, interference_pairs tools

Convert {{topology}}, {{health}}, {{pairs}} placeholders to
{{tool:}} calls. Made format_topology_header, format_health_section,
format_pairs_section pub so tools can call them.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-04-10 15:25:57 -04:00
parent 1a03264233
commit fd722662da
9 changed files with 49 additions and 13 deletions

View file

@ -33,7 +33,7 @@ async fn get_provenance(agent: &Option<std::sync::Arc<crate::agent::Agent>>) ->
// ── Definitions ──────────────────────────────────────────────── // ── Definitions ────────────────────────────────────────────────
pub fn memory_tools() -> [super::Tool; 11] { pub fn memory_tools() -> [super::Tool; 14] {
use super::Tool; use super::Tool;
[ [
Tool { name: "memory_render", description: "Read a memory node's content and links.", Tool { name: "memory_render", description: "Read a memory node's content and links.",
@ -69,6 +69,15 @@ pub fn memory_tools() -> [super::Tool; 11] {
Tool { name: "memory_query", description: "Run a structured query against the memory graph.", Tool { name: "memory_query", description: "Run a structured query against the memory graph.",
parameters_json: r#"{"type":"object","properties":{"query":{"type":"string","description":"Query expression"}},"required":["query"]}"#, parameters_json: r#"{"type":"object","properties":{"query":{"type":"string","description":"Query expression"}},"required":["query"]}"#,
handler: Arc::new(|_a, v| Box::pin(async move { query(&v).await })) }, handler: Arc::new(|_a, v| Box::pin(async move { query(&v).await })) },
Tool { name: "graph_topology", description: "Show graph topology stats (nodes, edges, clustering, hubs).",
parameters_json: r#"{"type":"object","properties":{}}"#,
handler: Arc::new(|_a, _v| Box::pin(async { graph_topology().await })) },
Tool { name: "graph_health", description: "Show graph health report with maintenance recommendations.",
parameters_json: r#"{"type":"object","properties":{}}"#,
handler: Arc::new(|_a, _v| Box::pin(async { graph_health().await })) },
Tool { name: "interference_pairs", description: "Find similar nodes that may interfere (duplicates or near-duplicates).",
parameters_json: r#"{"type":"object","properties":{"threshold":{"type":"number","description":"Similarity threshold (default 0.5)"},"limit":{"type":"integer","description":"Max pairs to return (default 10)"}}}"#,
handler: Arc::new(|_a, v| Box::pin(async move { interference_pairs(&v).await })) },
] ]
} }
@ -317,3 +326,30 @@ async fn journal_update(agent: &Option<std::sync::Arc<crate::agent::Agent>>, arg
let word_count = body.split_whitespace().count(); let word_count = body.split_whitespace().count();
Ok(format!("Updated last entry (+{} words)", word_count)) Ok(format!("Updated last entry (+{} words)", word_count))
} }
// ── Graph tools ───────────────────────────────────────────────
async fn graph_topology() -> Result<String> {
let arc = cached_store().await?;
let store = arc.lock().await;
let graph = store.build_graph();
Ok(crate::subconscious::prompts::format_topology_header(&graph))
}
async fn graph_health() -> Result<String> {
let arc = cached_store().await?;
let store = arc.lock().await;
let graph = store.build_graph();
Ok(crate::subconscious::prompts::format_health_section(&store, &graph))
}
async fn interference_pairs(args: &serde_json::Value) -> Result<String> {
let threshold = args.get("threshold").and_then(|v| v.as_f64()).unwrap_or(0.5) as f32;
let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
let arc = cached_store().await?;
let store = arc.lock().await;
let graph = store.build_graph();
let mut pairs = crate::neuro::detect_interference(&store, &graph, threshold);
pairs.truncate(limit);
Ok(crate::subconscious::prompts::format_pairs_section(&pairs, &store, &graph))
}

View file

@ -46,7 +46,7 @@ For each target node, one of:
- **Don't be contrarian for its own sake.** If a node is correct, - **Don't be contrarian for its own sake.** If a node is correct,
say so and move on. say so and move on.
{{TOPOLOGY}} {{tool: graph_topology}}
{{SIBLINGS}} {{SIBLINGS}}

View file

@ -79,7 +79,7 @@ Set with: `poc-memory graph link-set <source> <target> <strength>`
If you see default-strength links (0.10 or 0.30) in the neighborhoods If you see default-strength links (0.10 or 0.30) in the neighborhoods
you're exploring and you have context to judge them, reweight those too. you're exploring and you have context to judge them, reweight those too.
{{TOPOLOGY}} {{tool: graph_topology}}
## Nodes to examine for cross-community connections ## Nodes to examine for cross-community connections

View file

@ -44,7 +44,7 @@ pattern you've found.
- **Preserve diversity.** Multiple perspectives on the same concept are - **Preserve diversity.** Multiple perspectives on the same concept are
valuable. Only delete actual duplicates. valuable. Only delete actual duplicates.
{{TOPOLOGY}} {{tool: graph_topology}}
## Neighborhood nodes ## Neighborhood nodes

View file

@ -36,8 +36,8 @@ overall structure.
- Most output should be observations about system health. Act on structural - Most output should be observations about system health. Act on structural
problems you find — link orphans, refine outdated nodes. problems you find — link orphans, refine outdated nodes.
{{topology}} {{tool: graph_topology}}
## Current health data ## Current health data
{{health}} {{tool: graph_health}}

View file

@ -40,7 +40,7 @@ clusters and determine how it fits.
- **Trust the decay.** Unimportant nodes don't need pruning — just - **Trust the decay.** Unimportant nodes don't need pruning — just
don't link them. don't link them.
{{TOPOLOGY}} {{tool: graph_topology}}
## Nodes to review ## Nodes to review

View file

@ -35,8 +35,8 @@ overlapping inputs and orthogonalize them.
- **Session summaries are the biggest source of interference.** - **Session summaries are the biggest source of interference.**
- **Look for the supersession pattern.** - **Look for the supersession pattern.**
{{topology}} {{tool: graph_topology}}
## Interfering pairs to review ## Interfering pairs to review
{{pairs}} {{tool: interference_pairs}}

View file

@ -45,7 +45,7 @@ entries, and extract those patterns into semantic nodes.
- **The best extractions change how you think, not just what you know.** - **The best extractions change how you think, not just what you know.**
Extract the conceptual version, not just the factual one. Extract the conceptual version, not just the factual one.
{{TOPOLOGY}} {{tool: graph_topology}}
{{SIBLINGS}} {{SIBLINGS}}

View file

@ -23,7 +23,7 @@ pub struct AgentBatch {
pub node_keys: Vec<String>, pub node_keys: Vec<String>,
} }
pub(super) fn format_topology_header(graph: &Graph) -> String { pub 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();
@ -139,7 +139,7 @@ pub(super) fn format_nodes_section(store: &Store, items: &[ReplayItem], graph: &
out out
} }
pub(super) fn format_health_section(store: &Store, graph: &Graph) -> String { pub 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);
@ -195,7 +195,7 @@ pub(super) fn format_health_section(store: &Store, graph: &Graph) -> String {
out out
} }
pub(super) fn format_pairs_section( pub fn format_pairs_section(
pairs: &[(String, String, f32)], pairs: &[(String, String, f32)],
store: &Store, store: &Store,
graph: &Graph, graph: &Graph,