tools: static string definitions, no runtime JSON construction
Tool definitions are now &'static str (name, description, parameters_json) instead of runtime-constructed serde_json::Value. No more json!() macro, no more ToolDef::new() for tool definitions. The JSON schema strings are written directly as string literals. When sent to the API, they can be interpolated without serialization/deserialization. Multi-tool modules return fixed-size arrays instead of Vecs: - memory: [Tool; 12], journal: [Tool; 3] - channels: [Tool; 4] - control: [Tool; 3] - web: [Tool; 2] ToolDef/FunctionDef remain for backward compat (API wire format, summarize_args) but are no longer used in tool definitions. Co-Authored-By: Proof of Concept <poc@bcachefs.org> Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
ed150df628
commit
53ad8cc9df
13 changed files with 205 additions and 561 deletions
|
|
@ -4,11 +4,9 @@
|
|||
// One function per tool for use in the Tool registry.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::hippocampus::memory::MemoryNode;
|
||||
use crate::store::StoreView;
|
||||
use super::ToolDef;
|
||||
use crate::store::Store;
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────
|
||||
|
|
@ -29,41 +27,65 @@ fn provenance() -> &'static str { "manual" }
|
|||
|
||||
// ── Definitions ────────────────────────────────────────────────
|
||||
|
||||
pub fn memory_tools() -> Vec<super::Tool> {
|
||||
pub fn memory_tools() -> [super::Tool; 12] {
|
||||
use super::Tool;
|
||||
vec![
|
||||
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) }) },
|
||||
[
|
||||
Tool { name: "memory_render", description: "Read a memory node's content and links.",
|
||||
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string","description":"Node key"}},"required":["key"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { render(&v) }) },
|
||||
Tool { name: "memory_write", description: "Create or update a memory node.",
|
||||
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string","description":"Node key"},"content":{"type":"string","description":"Full content (markdown)"}},"required":["key","content"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { write(&v) }) },
|
||||
Tool { name: "memory_search", description: "Search the memory graph via spreading activation. Give 2-4 seed node keys.",
|
||||
parameters_json: r#"{"type":"object","properties":{"keys":{"type":"array","items":{"type":"string"},"description":"Seed node keys to activate from"}},"required":["keys"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { search(&v) }) },
|
||||
Tool { name: "memory_links", description: "Show a node's neighbors with link strengths.",
|
||||
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string","description":"Node key"}},"required":["key"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { links(&v) }) },
|
||||
Tool { name: "memory_link_set", description: "Set link strength between two nodes.",
|
||||
parameters_json: r#"{"type":"object","properties":{"source":{"type":"string"},"target":{"type":"string"},"strength":{"type":"number","description":"0.01 to 1.0"}},"required":["source","target","strength"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { link_set(&v) }) },
|
||||
Tool { name: "memory_link_add", description: "Add a new link between two nodes.",
|
||||
parameters_json: r#"{"type":"object","properties":{"source":{"type":"string"},"target":{"type":"string"}},"required":["source","target"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { link_add(&v) }) },
|
||||
Tool { name: "memory_used", description: "Mark a node as useful (boosts weight).",
|
||||
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string","description":"Node key"}},"required":["key"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { used(&v) }) },
|
||||
Tool { name: "memory_weight_set", description: "Set a node's weight directly (0.01 to 1.0).",
|
||||
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string"},"weight":{"type":"number","description":"0.01 to 1.0"}},"required":["key","weight"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { weight_set(&v) }) },
|
||||
Tool { name: "memory_rename", description: "Rename a node key in place.",
|
||||
parameters_json: r#"{"type":"object","properties":{"old_key":{"type":"string"},"new_key":{"type":"string"}},"required":["old_key","new_key"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { rename(&v) }) },
|
||||
Tool { name: "memory_supersede", description: "Mark a node as superseded by another (sets weight to 0.01).",
|
||||
parameters_json: r#"{"type":"object","properties":{"old_key":{"type":"string"},"new_key":{"type":"string"},"reason":{"type":"string"}},"required":["old_key","new_key"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { supersede(&v) }) },
|
||||
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"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { query(&v) }) },
|
||||
Tool { name: "output", description: "Produce a named output value for passing between steps.",
|
||||
parameters_json: r#"{"type":"object","properties":{"key":{"type":"string","description":"Output name"},"value":{"type":"string","description":"Output value"}},"required":["key","value"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { output(&v) }) },
|
||||
]
|
||||
}
|
||||
|
||||
pub fn journal_tools() -> Vec<super::Tool> {
|
||||
pub fn journal_tools() -> [super::Tool; 3] {
|
||||
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) }) },
|
||||
[
|
||||
Tool { name: "journal_tail", description: "Read the last N journal entries (default 1).",
|
||||
parameters_json: r#"{"type":"object","properties":{"count":{"type":"integer","description":"Number of entries (default 1)"}}}"#,
|
||||
handler: |_a, v| Box::pin(async move { journal_tail(&v) }) },
|
||||
Tool { name: "journal_new", description: "Start a new journal entry.",
|
||||
parameters_json: r#"{"type":"object","properties":{"name":{"type":"string","description":"Short node name (becomes the key)"},"title":{"type":"string","description":"Descriptive title"},"body":{"type":"string","description":"Entry body"}},"required":["name","title","body"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { journal_new(&v) }) },
|
||||
Tool { name: "journal_update", description: "Append text to the most recent journal entry.",
|
||||
parameters_json: r#"{"type":"object","properties":{"body":{"type":"string","description":"Text to append"}},"required":["body"]}"#,
|
||||
handler: |_a, v| Box::pin(async move { journal_update(&v) }) },
|
||||
]
|
||||
}
|
||||
|
||||
// ── Memory tools ───────────────────────────────────────────────
|
||||
|
||||
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"]}))
|
||||
}
|
||||
|
||||
fn render(args: &serde_json::Value) -> Result<String> {
|
||||
let key = get_str(args, "key")?;
|
||||
Ok(MemoryNode::load(key)
|
||||
|
|
@ -71,12 +93,6 @@ fn render(args: &serde_json::Value) -> Result<String> {
|
|||
.render())
|
||||
}
|
||||
|
||||
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"]}))
|
||||
}
|
||||
|
||||
fn write(args: &serde_json::Value) -> Result<String> {
|
||||
let key = get_str(args, "key")?;
|
||||
let content = get_str(args, "content")?;
|
||||
|
|
@ -87,15 +103,6 @@ fn write(args: &serde_json::Value) -> Result<String> {
|
|||
Ok(format!("{} '{}'", result, key))
|
||||
}
|
||||
|
||||
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 \
|
||||
by how strongly they connect to your seeds — bridging nodes score \
|
||||
highest. This finds conceptual connections, not just keyword matches.",
|
||||
json!({"type":"object","properties":{"keys":{"type":"array","items":{"type":"string"},"description":"Seed node keys to activate from"}},"required":["keys"]}))
|
||||
}
|
||||
|
||||
fn search(args: &serde_json::Value) -> Result<String> {
|
||||
let keys: Vec<String> = args.get("keys")
|
||||
.and_then(|v| v.as_array())
|
||||
|
|
@ -129,12 +136,6 @@ fn search(args: &serde_json::Value) -> Result<String> {
|
|||
.collect::<Vec<_>>().join("\n"))
|
||||
}
|
||||
|
||||
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"]}))
|
||||
}
|
||||
|
||||
fn links(args: &serde_json::Value) -> Result<String> {
|
||||
let key = get_str(args, "key")?;
|
||||
let node = MemoryNode::load(key)
|
||||
|
|
@ -147,12 +148,6 @@ fn links(args: &serde_json::Value) -> Result<String> {
|
|||
Ok(out)
|
||||
}
|
||||
|
||||
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"]}))
|
||||
}
|
||||
|
||||
fn link_set(args: &serde_json::Value) -> Result<String> {
|
||||
let mut store = load_store()?;
|
||||
let s = store.resolve_key(get_str(args, "source")?).map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
|
|
@ -163,12 +158,6 @@ fn link_set(args: &serde_json::Value) -> Result<String> {
|
|||
Ok(format!("{} ↔ {} strength {:.2} → {:.2}", s, t, old, strength))
|
||||
}
|
||||
|
||||
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"]}))
|
||||
}
|
||||
|
||||
fn link_add(args: &serde_json::Value) -> Result<String> {
|
||||
let mut store = load_store()?;
|
||||
let s = store.resolve_key(get_str(args, "source")?).map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
|
|
@ -178,12 +167,6 @@ fn link_add(args: &serde_json::Value) -> Result<String> {
|
|||
Ok(format!("linked {} → {} (strength={:.2})", s, t, strength))
|
||||
}
|
||||
|
||||
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"]}))
|
||||
}
|
||||
|
||||
fn used(args: &serde_json::Value) -> Result<String> {
|
||||
let key = get_str(args, "key")?;
|
||||
let mut store = load_store()?;
|
||||
|
|
@ -195,12 +178,6 @@ fn used(args: &serde_json::Value) -> Result<String> {
|
|||
Ok(format!("marked {} as used", key))
|
||||
}
|
||||
|
||||
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"]}))
|
||||
}
|
||||
|
||||
fn weight_set(args: &serde_json::Value) -> Result<String> {
|
||||
let mut store = load_store()?;
|
||||
let key = store.resolve_key(get_str(args, "key")?).map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
|
|
@ -210,12 +187,6 @@ fn weight_set(args: &serde_json::Value) -> Result<String> {
|
|||
Ok(format!("weight {} {:.2} → {:.2}", key, old, new))
|
||||
}
|
||||
|
||||
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"]}))
|
||||
}
|
||||
|
||||
fn rename(args: &serde_json::Value) -> Result<String> {
|
||||
let old_key = get_str(args, "old_key")?;
|
||||
let new_key = get_str(args, "new_key")?;
|
||||
|
|
@ -226,12 +197,6 @@ fn rename(args: &serde_json::Value) -> Result<String> {
|
|||
Ok(format!("Renamed '{}' → '{}'", resolved, new_key))
|
||||
}
|
||||
|
||||
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"]}))
|
||||
}
|
||||
|
||||
fn supersede(args: &serde_json::Value) -> Result<String> {
|
||||
let old_key = get_str(args, "old_key")?;
|
||||
let new_key = get_str(args, "new_key")?;
|
||||
|
|
@ -249,14 +214,6 @@ fn supersede(args: &serde_json::Value) -> Result<String> {
|
|||
Ok(format!("superseded {} → {} ({})", old_key, new_key, reason))
|
||||
}
|
||||
|
||||
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\", \
|
||||
\"neighbors('identity') | select strength\", \"key ~ 'journal.*' | count\"",
|
||||
json!({"type":"object","properties":{"query":{"type":"string","description":"Query expression"}},"required":["query"]}))
|
||||
}
|
||||
|
||||
fn query(args: &serde_json::Value) -> Result<String> {
|
||||
let query_str = get_str(args, "query")?;
|
||||
let store = load_store()?;
|
||||
|
|
@ -265,16 +222,6 @@ fn query(args: &serde_json::Value) -> Result<String> {
|
|||
.map_err(|e| anyhow::anyhow!("{}", e))
|
||||
}
|
||||
|
||||
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.",
|
||||
json!({"type":"object","properties":{
|
||||
"key":{"type":"string","description":"Output name (e.g. 'relevant_memories')"},
|
||||
"value":{"type":"string","description":"Output value"}
|
||||
},"required":["key","value"]}))
|
||||
}
|
||||
|
||||
fn output(args: &serde_json::Value) -> Result<String> {
|
||||
let key = get_str(args, "key")?;
|
||||
if key.starts_with("pid-") || key.contains('/') || key.contains("..") {
|
||||
|
|
@ -291,14 +238,6 @@ fn output(args: &serde_json::Value) -> Result<String> {
|
|||
|
||||
// ── Journal tools ──────────────────────────────────────────────
|
||||
|
||||
fn journal_tail_def() -> ToolDef {
|
||||
ToolDef::new("journal_tail",
|
||||
"Read the last N journal entries (default 1).",
|
||||
json!({"type":"object","properties":{
|
||||
"count":{"type":"integer","description":"Number of entries (default 1)"}
|
||||
}}))
|
||||
}
|
||||
|
||||
fn journal_tail(args: &serde_json::Value) -> Result<String> {
|
||||
let count = args.get("count").and_then(|v| v.as_u64()).unwrap_or(1) as usize;
|
||||
let store = load_store()?;
|
||||
|
|
@ -317,16 +256,6 @@ fn journal_tail(args: &serde_json::Value) -> Result<String> {
|
|||
}
|
||||
}
|
||||
|
||||
fn journal_new_def() -> ToolDef {
|
||||
ToolDef::new("journal_new",
|
||||
"Start a new journal entry.",
|
||||
json!({"type":"object","properties":{
|
||||
"name":{"type":"string","description":"Short node name (becomes the key)"},
|
||||
"title":{"type":"string","description":"Descriptive title for the heading"},
|
||||
"body":{"type":"string","description":"Entry body (2-3 paragraphs)"}
|
||||
},"required":["name","title","body"]}))
|
||||
}
|
||||
|
||||
fn journal_new(args: &serde_json::Value) -> Result<String> {
|
||||
let name = get_str(args, "name")?;
|
||||
let title = get_str(args, "title")?;
|
||||
|
|
@ -363,14 +292,6 @@ fn journal_new(args: &serde_json::Value) -> Result<String> {
|
|||
Ok(format!("New entry '{}' ({} words)", title, word_count))
|
||||
}
|
||||
|
||||
fn journal_update_def() -> ToolDef {
|
||||
ToolDef::new("journal_update",
|
||||
"Append text to the most recent journal entry (same thread continuing).",
|
||||
json!({"type":"object","properties":{
|
||||
"body":{"type":"string","description":"Text to append to the last entry"}
|
||||
},"required":["body"]}))
|
||||
}
|
||||
|
||||
fn journal_update(args: &serde_json::Value) -> Result<String> {
|
||||
let body = get_str(args, "body")?;
|
||||
let mut store = load_store()?;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue