From 2587303e9875eabd0a9fe684c286d3b548c18b28 Mon Sep 17 00:00:00 2001 From: ProofOfConcept Date: Fri, 10 Apr 2026 15:12:53 -0400 Subject: [PATCH] Add {{tool:}} placeholder for agent templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent templates can now inline tool call results with {{tool: tool_name args}}. Dispatches to the same store operations the tools use, but runs synchronously during prompt resolution. Supports memory_render, memory_query, memory_search, memory_links, and journal_tail. This replaces the need for special-purpose placeholders — {{pairs}}, {{rename}}, etc. can be expressed as queries through {{tool: memory_query {"query": "..."}}} instead. Co-Authored-By: Proof of Concept --- src/subconscious/defs.rs | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/subconscious/defs.rs b/src/subconscious/defs.rs index 7039c6a..983b420 100644 --- a/src/subconscious/defs.rs +++ b/src/subconscious/defs.rs @@ -561,6 +561,12 @@ fn resolve( Some(Resolved { text, keys }) } + // tool:NAME ARGS — run a tool call and include its output + _ if name.starts_with("tool:") => { + let spec = name[5..].trim(); + resolve_tool(spec, store, graph) + } + // bash:COMMAND — run a shell command and include its stdout _ if name.starts_with("bash:") => { let cmd = &name[5..]; @@ -721,6 +727,44 @@ fn resolve_memory_ratio() -> String { pct, keys.len(), memory_bytes / 1024, transcript_size / 1024) } +/// Resolve a {{tool: name {args}}} placeholder by calling the tool +/// handler from the registry. Uses block_in_place to bridge sync→async. +fn resolve_tool(spec: &str, _store: &Store, _graph: &Graph) -> Option { + // Parse "tool_name {json args}" or "tool_name arg" + let (name, args) = match spec.find('{') { + Some(i) => { + let name = spec[..i].trim(); + let args: serde_json::Value = serde_json::from_str(&spec[i..]).ok()?; + (name, args) + } + None => { + let mut parts = spec.splitn(2, char::is_whitespace); + let name = parts.next()?; + match parts.next() { + Some(arg) => (name, serde_json::json!({"key": arg})), + None => (name, serde_json::json!({})), + } + } + }; + + let tools = crate::agent::tools::tools(); + let tool = tools.iter().find(|t| t.name == name)?; + + let result = tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on( + (tool.handler)(None, args.clone()) + ) + }); + + match result { + Ok(text) => Some(Resolved { text, keys: vec![] }), + Err(e) => { + eprintln!("[defs] {{{{tool: {}}}}} failed: {}", name, e); + Some(Resolved { text: format!("(tool error: {})", e), keys: vec![] }) + } + } +} + /// Resolve all {{placeholder}} patterns in a prompt template. /// Returns the resolved text and all node keys collected from placeholders. pub fn resolve_placeholders(