Add {{tool:}} placeholder for agent templates

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 <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-04-10 15:12:53 -04:00 committed by Kent Overstreet
parent be6ac762f6
commit 2587303e98

View file

@ -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<Resolved> {
// 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(