tools: add output(), journal_tail/new/update tools
- output(key, value): write named results to agent state dir,
readable via {{input:key}} placeholder
- journal_tail(count): read last N journal entries
- journal_new(title, body): start new ## timestamped entry
- journal_update(body): append to last entry
Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
77d1d39f3f
commit
4b32716d3e
1 changed files with 92 additions and 0 deletions
|
|
@ -43,6 +43,29 @@ pub fn definitions() -> Vec<ToolDef> {
|
|||
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"]})),
|
||||
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"]})),
|
||||
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)"}
|
||||
}})),
|
||||
ToolDef::new("journal_new",
|
||||
"Start a new journal entry with a ## heading and body.",
|
||||
json!({"type":"object","properties":{
|
||||
"title":{"type":"string","description":"Entry title (becomes ## YYYY-MM-DDTHH:MM — title)"},
|
||||
"body":{"type":"string","description":"Entry body (2-3 paragraphs)"}
|
||||
},"required":["title","body"]})),
|
||||
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"]})),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -114,6 +137,75 @@ pub fn dispatch(name: &str, args: &serde_json::Value, provenance: Option<&str>)
|
|||
crate::query_parser::query_to_string(&store, &graph, query)
|
||||
.map_err(|e| anyhow::anyhow!("{}", e))
|
||||
}
|
||||
"output" => {
|
||||
let key = get_str(args, "key")?;
|
||||
let value = get_str(args, "value")?;
|
||||
let dir = std::env::var("POC_AGENT_OUTPUT_DIR")
|
||||
.map_err(|_| anyhow::anyhow!("no output directory set"))?;
|
||||
let path = std::path::Path::new(&dir).join(key);
|
||||
std::fs::write(&path, value)
|
||||
.with_context(|| format!("writing output {}", path.display()))?;
|
||||
Ok(format!("{}: {}", key, value))
|
||||
}
|
||||
"journal_tail" => {
|
||||
let count = args.get("count").and_then(|v| v.as_u64()).unwrap_or(1) as usize;
|
||||
let store = Store::load().map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
let content = store.nodes.get("journal")
|
||||
.map(|n| n.content.as_str())
|
||||
.unwrap_or("");
|
||||
let mut entries: Vec<&str> = Vec::new();
|
||||
let mut remaining = content;
|
||||
while let Some(pos) = remaining.rfind("\n## ") {
|
||||
entries.push(&remaining[pos + 1..]);
|
||||
remaining = &remaining[..pos];
|
||||
if entries.len() >= count { break; }
|
||||
}
|
||||
if entries.len() < count && remaining.starts_with("## ") {
|
||||
entries.push(remaining);
|
||||
}
|
||||
entries.reverse();
|
||||
if entries.is_empty() {
|
||||
Ok("(no journal entries)".into())
|
||||
} else {
|
||||
Ok(entries.join("\n\n"))
|
||||
}
|
||||
}
|
||||
"journal_new" => {
|
||||
let title = get_str(args, "title")?;
|
||||
let body = get_str(args, "body")?;
|
||||
let ts = chrono::Local::now().format("%Y-%m-%dT%H:%M");
|
||||
let entry = format!("## {} — {}\n\n{}", ts, title, body);
|
||||
let mut store = Store::load().map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
let existing = store.nodes.get("journal")
|
||||
.map(|n| n.content.clone())
|
||||
.unwrap_or_default();
|
||||
let new_content = if existing.is_empty() {
|
||||
entry.clone()
|
||||
} else {
|
||||
format!("{}\n\n{}", existing.trim_end(), entry)
|
||||
};
|
||||
store.upsert_provenance("journal", &new_content, prov)
|
||||
.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
store.save().map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
let word_count = body.split_whitespace().count();
|
||||
Ok(format!("New entry '{}' ({} words)", title, word_count))
|
||||
}
|
||||
"journal_update" => {
|
||||
let body = get_str(args, "body")?;
|
||||
let mut store = Store::load().map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
let existing = store.nodes.get("journal")
|
||||
.map(|n| n.content.clone())
|
||||
.unwrap_or_default();
|
||||
if existing.is_empty() {
|
||||
anyhow::bail!("no journal entry to update — use journal_new first");
|
||||
}
|
||||
let new_content = format!("{}\n\n{}", existing.trim_end(), body);
|
||||
store.upsert_provenance("journal", &new_content, prov)
|
||||
.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
store.save().map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
let word_count = body.split_whitespace().count();
|
||||
Ok(format!("Updated last entry (+{} words)", word_count))
|
||||
}
|
||||
_ => anyhow::bail!("Unknown memory tool: {}", name),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue