From 6d6da07f914100a3250ed0b8a739c039c1b98fe1 Mon Sep 17 00:00:00 2001 From: ProofOfConcept Date: Sat, 4 Apr 2026 15:22:03 -0400 Subject: [PATCH] tools: each module owns its Tool list, no duplication Each tool module exports its own tools() returning Vec. mod.rs::tools() chains them. Individual _def() and handler functions are pub(super), not exported. Aggregate definitions derived from the Tool lists. - memory: memory_tools(), journal_tools() - channels: tools() - control: tools() - mod.rs: just chains + adds file/bash/web/vision Co-Authored-By: Proof of Concept --- src/agent/tools/channels.rs | 11 ++++- src/agent/tools/control.rs | 21 ++++++++- src/agent/tools/memory.rs | 87 ++++++++++++++++++++++--------------- src/agent/tools/mod.rs | 53 +++++----------------- 4 files changed, 91 insertions(+), 81 deletions(-) diff --git a/src/agent/tools/channels.rs b/src/agent/tools/channels.rs index bc02a68..c26555c 100644 --- a/src/agent/tools/channels.rs +++ b/src/agent/tools/channels.rs @@ -51,7 +51,16 @@ pub fn definitions() -> Vec { ] } -// ── Dispatch ─────────────────────────────────────────────────── +pub fn tools() -> Vec { + use super::Tool; + let defs = definitions(); + vec![ + Tool { def: defs[0].clone(), handler: |_a, _v| Box::pin(async move { channel_list().await }) }, + Tool { def: defs[1].clone(), handler: |_a, v| Box::pin(async move { channel_recv(&v).await }) }, + Tool { def: defs[2].clone(), handler: |_a, v| Box::pin(async move { channel_send(&v).await }) }, + Tool { def: defs[3].clone(), handler: |_a, _v| Box::pin(async move { channel_notifications().await }) }, + ] +} // ── Tool implementations ─────────────────────────────────────── diff --git a/src/agent/tools/control.rs b/src/agent/tools/control.rs index 8123ad3..3c13c91 100644 --- a/src/agent/tools/control.rs +++ b/src/agent/tools/control.rs @@ -50,7 +50,26 @@ pub(super) fn yield_to_user(args: &serde_json::Value) -> Result { }) } -pub(super) fn definitions() -> Vec { +pub(super) fn tools() -> Vec { + use super::Tool; + let defs = definitions(); + vec![ + Tool { def: defs[0].clone(), // switch_model + handler: |_a, v| Box::pin(async move { + let model = v.get("model").and_then(|v| v.as_str()).unwrap_or(""); + Ok(format!("Switching to model: {}", model)) + }) }, + Tool { def: defs[1].clone(), // pause + handler: |_a, _v| Box::pin(async { Ok("Pausing autonomous behavior. Only user input will wake you.".into()) }) }, + Tool { def: defs[2].clone(), // yield_to_user + handler: |_a, v| Box::pin(async move { + let msg = v.get("message").and_then(|v| v.as_str()).unwrap_or("(yielding to user)"); + Ok(msg.to_string()) + }) }, + ] +} + +fn definitions() -> Vec { vec![ ToolDef::new( "switch_model", diff --git a/src/agent/tools/memory.rs b/src/agent/tools/memory.rs index b626693..583a19b 100644 --- a/src/agent/tools/memory.rs +++ b/src/agent/tools/memory.rs @@ -29,40 +29,55 @@ fn provenance() -> &'static str { "manual" } // ── Definitions ──────────────────────────────────────────────── -pub fn definitions() -> Vec { +pub fn memory_tools() -> Vec { + use super::Tool; vec![ - render_def(), write_def(), search_def(), links_def(), - link_set_def(), link_add_def(), used_def(), weight_set_def(), - rename_def(), supersede_def(), query_def(), output_def(), + Tool { def: render_def(), handler: |_a, v| Box::pin(async move { render(&v) }) }, + Tool { def: write_def(), handler: |_a, v| Box::pin(async move { write(&v) }) }, + Tool { def: search_def(), handler: |_a, v| Box::pin(async move { search(&v) }) }, + Tool { def: links_def(), handler: |_a, v| Box::pin(async move { links(&v) }) }, + Tool { def: link_set_def(), handler: |_a, v| Box::pin(async move { link_set(&v) }) }, + Tool { def: link_add_def(), handler: |_a, v| Box::pin(async move { link_add(&v) }) }, + Tool { def: used_def(), handler: |_a, v| Box::pin(async move { used(&v) }) }, + Tool { def: weight_set_def(), handler: |_a, v| Box::pin(async move { weight_set(&v) }) }, + Tool { def: rename_def(), handler: |_a, v| Box::pin(async move { rename(&v) }) }, + Tool { def: supersede_def(), handler: |_a, v| Box::pin(async move { supersede(&v) }) }, + Tool { def: query_def(), handler: |_a, v| Box::pin(async move { query(&v) }) }, + Tool { def: output_def(), handler: |_a, v| Box::pin(async move { output(&v) }) }, ] } -pub fn journal_definitions() -> Vec { - vec![journal_tail_def(), journal_new_def(), journal_update_def()] +pub fn journal_tools() -> Vec { + use super::Tool; + vec![ + Tool { def: journal_tail_def(), handler: |_a, v| Box::pin(async move { journal_tail(&v) }) }, + Tool { def: journal_new_def(), handler: |_a, v| Box::pin(async move { journal_new(&v) }) }, + Tool { def: journal_update_def(), handler: |_a, v| Box::pin(async move { journal_update(&v) }) }, + ] } // ── Memory tools ─────────────────────────────────────────────── -pub fn render_def() -> ToolDef { +fn render_def() -> ToolDef { ToolDef::new("memory_render", "Read a memory node's content and links.", json!({"type":"object","properties":{"key":{"type":"string","description":"Node key"}},"required":["key"]})) } -pub fn render(args: &serde_json::Value) -> Result { +fn render(args: &serde_json::Value) -> Result { let key = get_str(args, "key")?; Ok(MemoryNode::load(key) .ok_or_else(|| anyhow::anyhow!("node not found: {}", key))? .render()) } -pub fn write_def() -> ToolDef { +fn write_def() -> ToolDef { ToolDef::new("memory_write", "Create or update a memory node.", json!({"type":"object","properties":{"key":{"type":"string","description":"Node key"},"content":{"type":"string","description":"Full content (markdown)"}},"required":["key","content"]})) } -pub fn write(args: &serde_json::Value) -> Result { +fn write(args: &serde_json::Value) -> Result { let key = get_str(args, "key")?; let content = get_str(args, "content")?; let mut store = load_store()?; @@ -72,7 +87,7 @@ pub fn write(args: &serde_json::Value) -> Result { Ok(format!("{} '{}'", result, key)) } -pub fn search_def() -> ToolDef { +fn search_def() -> ToolDef { ToolDef::new("memory_search", "Search the memory graph via spreading activation. Give 2-4 seed \ node keys related to what you're looking for. Returns nodes ranked \ @@ -81,7 +96,7 @@ pub fn search_def() -> ToolDef { json!({"type":"object","properties":{"keys":{"type":"array","items":{"type":"string"},"description":"Seed node keys to activate from"}},"required":["keys"]})) } -pub fn search(args: &serde_json::Value) -> Result { +fn search(args: &serde_json::Value) -> Result { let keys: Vec = args.get("keys") .and_then(|v| v.as_array()) .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()) @@ -114,13 +129,13 @@ pub fn search(args: &serde_json::Value) -> Result { .collect::>().join("\n")) } -pub fn links_def() -> ToolDef { +fn links_def() -> ToolDef { ToolDef::new("memory_links", "Show a node's neighbors with link strengths.", json!({"type":"object","properties":{"key":{"type":"string","description":"Node key"}},"required":["key"]})) } -pub fn links(args: &serde_json::Value) -> Result { +fn links(args: &serde_json::Value) -> Result { let key = get_str(args, "key")?; let node = MemoryNode::load(key) .ok_or_else(|| anyhow::anyhow!("node not found: {}", key))?; @@ -132,13 +147,13 @@ pub fn links(args: &serde_json::Value) -> Result { Ok(out) } -pub fn link_set_def() -> ToolDef { +fn link_set_def() -> ToolDef { ToolDef::new("memory_link_set", "Set link strength between two nodes.", json!({"type":"object","properties":{"source":{"type":"string"},"target":{"type":"string"},"strength":{"type":"number","description":"0.01 to 1.0"}},"required":["source","target","strength"]})) } -pub fn link_set(args: &serde_json::Value) -> Result { +fn link_set(args: &serde_json::Value) -> Result { let mut store = load_store()?; let s = store.resolve_key(get_str(args, "source")?).map_err(|e| anyhow::anyhow!("{}", e))?; let t = store.resolve_key(get_str(args, "target")?).map_err(|e| anyhow::anyhow!("{}", e))?; @@ -148,13 +163,13 @@ pub fn link_set(args: &serde_json::Value) -> Result { Ok(format!("{} ↔ {} strength {:.2} → {:.2}", s, t, old, strength)) } -pub fn link_add_def() -> ToolDef { +fn link_add_def() -> ToolDef { ToolDef::new("memory_link_add", "Add a new link between two nodes.", json!({"type":"object","properties":{"source":{"type":"string"},"target":{"type":"string"}},"required":["source","target"]})) } -pub fn link_add(args: &serde_json::Value) -> Result { +fn link_add(args: &serde_json::Value) -> Result { let mut store = load_store()?; let s = store.resolve_key(get_str(args, "source")?).map_err(|e| anyhow::anyhow!("{}", e))?; let t = store.resolve_key(get_str(args, "target")?).map_err(|e| anyhow::anyhow!("{}", e))?; @@ -163,13 +178,13 @@ pub fn link_add(args: &serde_json::Value) -> Result { Ok(format!("linked {} → {} (strength={:.2})", s, t, strength)) } -pub fn used_def() -> ToolDef { +fn used_def() -> ToolDef { ToolDef::new("memory_used", "Mark a node as useful (boosts weight).", json!({"type":"object","properties":{"key":{"type":"string","description":"Node key"}},"required":["key"]})) } -pub fn used(args: &serde_json::Value) -> Result { +fn used(args: &serde_json::Value) -> Result { let key = get_str(args, "key")?; let mut store = load_store()?; if !store.nodes.contains_key(key) { @@ -180,13 +195,13 @@ pub fn used(args: &serde_json::Value) -> Result { Ok(format!("marked {} as used", key)) } -pub fn weight_set_def() -> ToolDef { +fn weight_set_def() -> ToolDef { ToolDef::new("memory_weight_set", "Set a node's weight directly (0.01 to 1.0).", json!({"type":"object","properties":{"key":{"type":"string"},"weight":{"type":"number","description":"0.01 to 1.0"}},"required":["key","weight"]})) } -pub fn weight_set(args: &serde_json::Value) -> Result { +fn weight_set(args: &serde_json::Value) -> Result { let mut store = load_store()?; let key = store.resolve_key(get_str(args, "key")?).map_err(|e| anyhow::anyhow!("{}", e))?; let weight = get_f64(args, "weight")? as f32; @@ -195,13 +210,13 @@ pub fn weight_set(args: &serde_json::Value) -> Result { Ok(format!("weight {} {:.2} → {:.2}", key, old, new)) } -pub fn rename_def() -> ToolDef { +fn rename_def() -> ToolDef { ToolDef::new("memory_rename", "Rename a node key in place. Same content, same links, new key.", json!({"type":"object","properties":{"old_key":{"type":"string"},"new_key":{"type":"string"}},"required":["old_key","new_key"]})) } -pub fn rename(args: &serde_json::Value) -> Result { +fn rename(args: &serde_json::Value) -> Result { let old_key = get_str(args, "old_key")?; let new_key = get_str(args, "new_key")?; let mut store = load_store()?; @@ -211,13 +226,13 @@ pub fn rename(args: &serde_json::Value) -> Result { Ok(format!("Renamed '{}' → '{}'", resolved, new_key)) } -pub fn supersede_def() -> ToolDef { +fn supersede_def() -> ToolDef { ToolDef::new("memory_supersede", "Mark a node as superseded by another (sets weight to 0.01).", json!({"type":"object","properties":{"old_key":{"type":"string"},"new_key":{"type":"string"},"reason":{"type":"string"}},"required":["old_key","new_key"]})) } -pub fn supersede(args: &serde_json::Value) -> Result { +fn supersede(args: &serde_json::Value) -> Result { let old_key = get_str(args, "old_key")?; let new_key = get_str(args, "new_key")?; let reason = args.get("reason").and_then(|v| v.as_str()).unwrap_or("superseded"); @@ -234,7 +249,7 @@ pub fn supersede(args: &serde_json::Value) -> Result { Ok(format!("superseded {} → {} ({})", old_key, new_key, reason)) } -pub fn query_def() -> ToolDef { +fn query_def() -> ToolDef { ToolDef::new("memory_query", "Run a structured query against the memory graph. Supports filtering, \ sorting, field selection. Examples: \"degree > 10 | sort weight | limit 5\", \ @@ -242,7 +257,7 @@ pub fn query_def() -> ToolDef { json!({"type":"object","properties":{"query":{"type":"string","description":"Query expression"}},"required":["query"]})) } -pub fn query(args: &serde_json::Value) -> Result { +fn query(args: &serde_json::Value) -> Result { let query_str = get_str(args, "query")?; let store = load_store()?; let graph = store.build_graph(); @@ -250,7 +265,7 @@ pub fn query(args: &serde_json::Value) -> Result { .map_err(|e| anyhow::anyhow!("{}", e)) } -pub fn output_def() -> ToolDef { +pub(super) fn output_def() -> ToolDef { ToolDef::new("output", "Produce a named output value. Use this to pass structured results \ between steps — subsequent prompts can see these in the conversation history.", @@ -260,7 +275,7 @@ pub fn output_def() -> ToolDef { },"required":["key","value"]})) } -pub fn output(args: &serde_json::Value) -> Result { +pub(super) fn output(args: &serde_json::Value) -> Result { let key = get_str(args, "key")?; if key.starts_with("pid-") || key.contains('/') || key.contains("..") { anyhow::bail!("invalid output key: {}", key); @@ -276,7 +291,7 @@ pub fn output(args: &serde_json::Value) -> Result { // ── Journal tools ────────────────────────────────────────────── -pub fn journal_tail_def() -> ToolDef { +pub(super) fn journal_tail_def() -> ToolDef { ToolDef::new("journal_tail", "Read the last N journal entries (default 1).", json!({"type":"object","properties":{ @@ -284,7 +299,7 @@ pub fn journal_tail_def() -> ToolDef { }})) } -pub fn journal_tail(args: &serde_json::Value) -> Result { +pub(super) fn journal_tail(args: &serde_json::Value) -> Result { let count = args.get("count").and_then(|v| v.as_u64()).unwrap_or(1) as usize; let store = load_store()?; let mut entries: Vec<&crate::store::Node> = store.nodes.values() @@ -302,7 +317,7 @@ pub fn journal_tail(args: &serde_json::Value) -> Result { } } -pub fn journal_new_def() -> ToolDef { +pub(super) fn journal_new_def() -> ToolDef { ToolDef::new("journal_new", "Start a new journal entry.", json!({"type":"object","properties":{ @@ -312,7 +327,7 @@ pub fn journal_new_def() -> ToolDef { },"required":["name","title","body"]})) } -pub fn journal_new(args: &serde_json::Value) -> Result { +pub(super) fn journal_new(args: &serde_json::Value) -> Result { let name = get_str(args, "name")?; let title = get_str(args, "title")?; let body = get_str(args, "body")?; @@ -348,7 +363,7 @@ pub fn journal_new(args: &serde_json::Value) -> Result { Ok(format!("New entry '{}' ({} words)", title, word_count)) } -pub fn journal_update_def() -> ToolDef { +pub(super) fn journal_update_def() -> ToolDef { ToolDef::new("journal_update", "Append text to the most recent journal entry (same thread continuing).", json!({"type":"object","properties":{ @@ -356,7 +371,7 @@ pub fn journal_update_def() -> ToolDef { },"required":["body"]})) } -pub fn journal_update(args: &serde_json::Value) -> Result { +pub(super) fn journal_update(args: &serde_json::Value) -> Result { let body = get_str(args, "body")?; let mut store = load_store()?; let latest_key = store.nodes.values() diff --git a/src/agent/tools/mod.rs b/src/agent/tools/mod.rs index b3a6936..e032f0d 100644 --- a/src/agent/tools/mod.rs +++ b/src/agent/tools/mod.rs @@ -202,56 +202,22 @@ pub async fn dispatch_shared( /// Return all registered tools with definitions + handlers. pub fn tools() -> Vec { - vec![ + let mut all = vec![ // File tools Tool { def: read::definition(), handler: |_a, v| Box::pin(async move { read::read_file(&v) }) }, Tool { def: write::definition(), handler: |_a, v| Box::pin(async move { write::write_file(&v) }) }, Tool { def: edit::definition(), handler: |_a, v| Box::pin(async move { edit::edit_file(&v) }) }, Tool { def: grep::definition(), handler: |_a, v| Box::pin(async move { grep::grep(&v) }) }, Tool { def: glob::definition(), handler: |_a, v| Box::pin(async move { glob::glob_search(&v) }) }, - - // Execution tools Tool { def: bash::definition(), handler: |_a, v| Box::pin(async move { bash::run_bash(&v).await }) }, Tool { def: web::fetch_definition(), handler: |_a, v| Box::pin(async move { web::web_fetch(&v).await }) }, Tool { def: web::search_definition(), handler: |_a, v| Box::pin(async move { web::web_search(&v).await }) }, - - // Vision Tool { def: vision::definition(), handler: |_a, v| Box::pin(async move { vision::view_image_text(&v) }) }, - - // Memory tools - Tool { def: memory::render_def(), handler: |_a, v| Box::pin(async move { memory::render(&v) }) }, - Tool { def: memory::write_def(), handler: |_a, v| Box::pin(async move { memory::write(&v) }) }, - Tool { def: memory::search_def(), handler: |_a, v| Box::pin(async move { memory::search(&v) }) }, - Tool { def: memory::links_def(), handler: |_a, v| Box::pin(async move { memory::links(&v) }) }, - Tool { def: memory::link_set_def(), handler: |_a, v| Box::pin(async move { memory::link_set(&v) }) }, - Tool { def: memory::link_add_def(), handler: |_a, v| Box::pin(async move { memory::link_add(&v) }) }, - Tool { def: memory::used_def(), handler: |_a, v| Box::pin(async move { memory::used(&v) }) }, - Tool { def: memory::weight_set_def(), handler: |_a, v| Box::pin(async move { memory::weight_set(&v) }) }, - Tool { def: memory::rename_def(), handler: |_a, v| Box::pin(async move { memory::rename(&v) }) }, - Tool { def: memory::supersede_def(), handler: |_a, v| Box::pin(async move { memory::supersede(&v) }) }, - Tool { def: memory::query_def(), handler: |_a, v| Box::pin(async move { memory::query(&v) }) }, - Tool { def: memory::output_def(), handler: |_a, v| Box::pin(async move { memory::output(&v) }) }, - - // Channel tools - Tool { def: channels::definitions()[0].clone(), handler: |_a, v| Box::pin(async move { channels::channel_list().await }) }, - Tool { def: channels::definitions()[1].clone(), handler: |_a, v| Box::pin(async move { channels::channel_recv(&v).await }) }, - Tool { def: channels::definitions()[2].clone(), handler: |_a, v| Box::pin(async move { channels::channel_send(&v).await }) }, - Tool { def: channels::definitions()[3].clone(), handler: |_a, v| Box::pin(async move { channels::channel_notifications().await }) }, - - // Control tools - Tool { def: control::definitions()[0].clone(), - handler: |_a, _v| Box::pin(async { Ok("Pausing autonomous behavior. Only user input will wake you.".into()) }) }, - Tool { def: control::definitions()[1].clone(), - handler: |_a, v| Box::pin(async move { - let model = v.get("model").and_then(|v| v.as_str()).unwrap_or(""); - Ok(format!("Switching to model: {}", model)) - }) }, - Tool { def: control::definitions()[2].clone(), - handler: |_a, v| Box::pin(async move { - let msg = v.get("message").and_then(|v| v.as_str()).unwrap_or("(yielding to user)"); - Ok(msg.to_string()) - }) }, - ] + ]; + all.extend(memory::memory_tools()); + all.extend(channels::tools()); + all.extend(control::tools()); + all } /// Return all tool definitions (extracted from tools()). @@ -261,9 +227,10 @@ pub fn definitions() -> Vec { /// Return memory + journal tool definitions only. pub fn memory_and_journal_definitions() -> Vec { - let mut defs = memory::definitions(); - defs.extend(memory::journal_definitions()); - defs + memory::memory_tools().into_iter() + .chain(memory::journal_tools()) + .map(|t| t.def) + .collect() } /// Create a short summary of tool args for the tools pane header.