remove dead memory tool files
81 unmodified lines828384858681 unmodified lines{"id":"int-72e1b1bb","kind":"field_change","created_at":"2026-06-30T21:59:42.144961313Z","actor":"dawn","issue_id":"klbr-u03","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Replaced hardcoded /home/mayer paths in antigravity bridge auth with dirs::home_dir()-based helpers, added ANTIGRAVITY_BRIDGE_CONFIG_DIR for token/cache files, and made the agy binary path configurable via ANTIGRAVITY_BRIDGE_BINARY_PATH or AGY_BINARY_PATH with ~/.local/bin/agy fallback. Verified cargo fmt --check and cargo test -p antigravity-bridge (9 passed)."}}{"id":"int-9f29f590","kind":"field_change","created_at":"2026-06-30T22:01:41.091400446Z","actor":"dawn","issue_id":"klbr-noi","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Replaced the missing ./default.nix package with config.nci.outputs.\"klbr-daemon\".packages.release. Verified nix eval .#packages.x86_64-linux.default.name returns klbr-daemon and nix flake show succeeds without the default.nix error."}}{"id":"int-686e2908","kind":"field_change","created_at":"2026-06-30T22:03:16.088748369Z","actor":"dawn","issue_id":"klbr-zue","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Replaced shell stdout/stderr byte slicing with char-boundary truncation helper and added UTF-8 regression tests. Verified cargo test -p klbr-core shell_truncation, full cargo test -p klbr-core (168 passed, 1 ignored), cargo fmt --check, git diff --check, and rg confirms the unsafe stdout/stderr slices are gone."}}{"id":"int-eabe816d","kind":"field_change","created_at":"2026-06-30T22:04:39.33696926Z","actor":"dawn","issue_id":"klbr-41z","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Changed daemon default websocket bind from 0.0.0.0:8765 to 127.0.0.1:8765 and replaced DumpMemories std::fs::write with tokio::fs::write(...).await. Verified rg for old patterns, cargo fmt --check, and cargo test -p klbr-daemon."}}{"id":"int-a150f81b","kind":"field_change","created_at":"2026-06-30T22:08:23.925241993Z","actor":"dawn","issue_id":"klbr-7e0","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Removed unregistered old memory tool modules and updated memory docs"}}52 unmodified lines5354555657575859606135 unmodified lines52 unmodified lines{"_type":"issue","id":"klbr-3ha","title":"core: refactor models.rs media stripping and SSE parser duplication","description":"strip_media_urls and replace_media_urls_with_placeholders share near-identical regex parsing logic. Also, complete() and stream() duplicate manual SSE chunk parsing. Clean these up.","status":"open","priority":2,"issue_type":"task","owner":"90008@klbr.net","created_at":"2026-06-30T21:39:42Z","created_by":"dawn","updated_at":"2026-06-30T21:39:42Z","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-647","title":"core: migrate Message.role from String to Role enum","description":"Message.role is currently represented as a raw String, which leads to fragile string comparisons ('assistant', 'tool', etc.) all over the codebase. Migrate to a proper Role enum.","status":"open","priority":2,"issue_type":"task","owner":"90008@klbr.net","created_at":"2026-06-30T21:39:36Z","created_by":"dawn","updated_at":"2026-06-30T21:39:36Z","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-so2","title":"core: decompose agent.rs run_turn() god method","description":"agent.rs run_turn() is 500+ lines, managing execution, tool loops, preemption, and discord integration. Break down tool execution loops and phase handlers into separate functions.","status":"open","priority":2,"issue_type":"task","owner":"90008@klbr.net","created_at":"2026-06-30T21:39:29Z","created_by":"dawn","updated_at":"2026-06-30T21:39:29Z","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-ow7","title":"core: refactor agent.rs run() loop failure-handling duplication","description":"agent.rs run() loop copy-pastes the exact same ~30-line backoff/failure-handling parsing block 4 times across different event stream branches. Extract to a helper function.","status":"open","priority":2,"issue_type":"task","owner":"90008@klbr.net","created_at":"2026-06-30T21:39:23Z","created_by":"dawn","updated_at":"2026-06-30T21:39:23Z","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-7e0","title":"core: clean up dead tool files in src/tools/","description":"There are 12 unused tool files in klbr-core/src/tools/ (remember.rs, recall.rs, tag_memory.rs, etc.) that are not declared as submodules or registered. They should be deleted.","status":"open","priority":2,"issue_type":"task","owner":"90008@klbr.net","created_at":"2026-06-30T21:39:16Z","created_by":"dawn","updated_at":"2026-06-30T21:39:16Z","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-7e0","title":"core: clean up dead tool files in src/tools/","description":"There are 12 unused tool files in klbr-core/src/tools/ (remember.rs, recall.rs, tag_memory.rs, etc.) that are not declared as submodules or registered. They should be deleted.","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-30T21:39:16Z","created_by":"dawn","updated_at":"2026-06-30T22:08:24Z","started_at":"2026-06-30T22:05:01Z","closed_at":"2026-06-30T22:08:24Z","close_reason":"Removed unregistered old memory tool modules and updated memory docs","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-q53","title":"workspace: consolidate dependency versions via workspace.dependencies","description":"Workspace is not utilizing cargo workspace dependencies, causing version drift between core, daemon, discord, and ipc. Consolidate them in the root Cargo.toml.","status":"open","priority":2,"issue_type":"task","owner":"90008@klbr.net","created_at":"2026-06-30T21:39:10Z","created_by":"dawn","updated_at":"2026-06-30T21:39:10Z","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-6m2","title":"web: refactor and split App.svelte monolith","description":"App.svelte is currently a 3000+ line file holding all UI, WS client, and styles. Needs to be split into modular components like Sidebar, ChatPane, Metrics, and XmlVisualizer.","status":"open","priority":2,"issue_type":"task","owner":"90008@klbr.net","created_at":"2026-06-30T21:39:03Z","created_by":"dawn","updated_at":"2026-06-30T21:39:03Z","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-ds9","title":"refactor and simplify core agent loop without losing behaviour","status":"open","priority":2,"issue_type":"task","owner":"90008@klbr.net","created_at":"2026-06-30T21:20:30Z","created_by":"dawn","updated_at":"2026-06-30T21:20:30Z","dependency_count":0,"dependent_count":0,"comment_count":0}{"_type":"issue","id":"klbr-jae","title":"Design aggregate evidence value representation","description":"LongMemEval aggregate misses show that a prompt fix would be benchmark-shaped and brittle. The structural gap is evidence representation: EvidenceFactRow currently stores only the first numeric value from a chunk, often capturing list ordinals, dates, or distractors instead of the values needed for counts/sums. Design and implement a general value representation for memory evidence that can keep multiple numeric mentions with nearby unit/currency/date/entity context, then only enable deterministic aggregate synthesis when the rendered facts are grounded and unambiguous.","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-30T21:01:47Z","created_by":"dawn","updated_at":"2026-06-30T21:11:59Z","started_at":"2026-06-30T21:02:38Z","closed_at":"2026-06-30T21:11:59Z","close_reason":"Implemented typed evidence value mentions for fact rows, rendered them in memory packets, and made aggregate synthesis use only finite same-kind/unit aggregateable values. Ordinals, dates, and mixed-unit rows now abstain instead of falling back to first-number arithmetic; no prompt fixes were added.","dependency_count":0,"dependent_count":0,"comment_count":0}35 unmodified lines202 unmodified linesExpand all20320420520620720720820921021175 unmodified lines202 unmodified linesExpand all
6. make the agent memory tool surface folgezettel-native. initial implementation landed as `mk_index`, `mk_search`, `mk_follow`, `mk_recall`, `mk_remember`, `mk_revise`, `mk_link`, and `mk_fleeting`; old exposed memory tool names are intentionally removed from the registry. tool modules are removed from the tree.
retrieval/assembly work is tracked in beads as `klbr-u0q`. the folgezettelagent surface is tracked as `klbr-7yo`.
75 unmodified lines48 unmodified lines49505152535354555655565758596061323 unmodified linesExpand all48 unmodified lines artifacts with structured event metadata, source-grounded timeline snippets, stated facts/decisions, attachment markers, and source refs.- reflection now exposes only the folgezettel memory tool surface: `mk_index`, `mk_search`, `mk_follow`, `mk_recall`, `mk_remember`, `mk_revise`, `mk_link`, and `mk_fleeting`. old public memory tools `mk_revise`, `mk_link`, and `mk_fleeting`. old public memory tool modules (`remember`, `recall`, `context_for`, `fetch_memories`, `memory_provenance`, `write_memory_note`, `edit_memory`, `list_memories`) are intentionally not registered. `memory_provenance`, `write_memory_note`, `edit_memory`, `list_memories`, `pin_memory`, `tag_memory`, and `unpin_memory`) have been removed from the tool tree instead of being left as unregistered dead code.- `mk_remember` writes sourced `zettel_note` artifacts, requires `source_refs`, links child notes to parents with `continues`, and records title/body/source policy frontmatter. `mk_revise` creates a sourced successor and supersedes the old ref. `mk_fleeting` writes unplaced323 unmodified linesExpand all123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114use std::future::Future;use std::pin::Pin;
use serde_json::json;
use crate::{memory::MemoryStore, models::ToolDef};
use super::{Tool, ToolContext};
pub fn tool() -> Tool { Tool::new(definition(), exec)}
fn definition() -> ToolDef { ToolDef::function( "context_for", "fetch all memories associated with a tag — a person, project, topic, etc. \ use this to load everything you know about someone or something before responding. \ no semantic ranking; returns newest first.", json!({ "type": "object", "properties": { "tags": { "type": "array", "items": { "type": "string" }, "description": "tags to fetch, e.g. [\"person:mayer\"] or [\"project:klbr\", \"preference\"]" }, "tag_mode": { "type": "string", "enum": ["and", "or"], "description": "\"and\" = all tags must match, \"or\" = any tag matches (default: \"or\")" }, "limit": { "type": "integer", "description": "max results (default 20)" } }, "required": ["tags"] }), )}
fn exec(args: serde_json::Value, ctx: ToolContext) -> Pin<Box<dyn Future<Output = String> + Send>> { Box::pin(execute(args, ctx))}
async fn execute(args: serde_json::Value, ctx: ToolContext) -> String { let tags: Vec<String> = match args["tags"].as_array() { Some(arr) => arr .iter() .filter_map(|v| v.as_str().map(String::from)) .collect(), None => return "error: missing required arg 'tags'".into(), }; let tag_and = args["tag_mode"].as_str() == Some("and"); let limit = args["limit"].as_u64().unwrap_or(20) as usize;
match ctx.memory.context_for(&tags, tag_and, limit) { Ok(results) if results.is_empty() => { format!("no memories found for tags: {}", tags.join(", ")) } Ok(results) => results .into_iter() .enumerate() .map(|(idx, e)| { let tag_str = if e.tags.is_empty() { String::new() } else { format!(" [{}]", e.tags.join(", ")) }; let content = if idx >= ctx.verbatim_count { if e.content.chars().count() <= 120 { format!("[snippet] {}", e.content) } else { let truncated: String = e.content.chars().take(120).collect(); format!("[snippet] {truncated}...") } } else { e.content.clone() }; format!( "[id:{}]{tag_str}{} {}", e.id, provenance_hint(&ctx.memory, e.id), content ) }) .collect::<Vec<_>>() .join("\n"), Err(err) => format!("error: {err}"), }}
fn provenance_hint(memory: &MemoryStore, memory_id: i64) -> String { let hints = memory .provenance_counts(memory_id) .unwrap_or_default() .into_iter() .filter(|(_, count)| *count > 0) .map(|(edge_type, count)| { let name = match edge_type { crate::mvp::MemoryEdgeType::DerivedFrom => "derived_from", crate::mvp::MemoryEdgeType::Supersedes => "supersedes", crate::mvp::MemoryEdgeType::Supports => "supports", }; format!("{name}:{count}") }) .collect::<Vec<_>>(); if hints.is_empty() { String::new() } else { format!(" [{}]", hints.join(",")) }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144use std::future::Future;use std::pin::Pin;
use serde_json::json;
use crate::models::ToolDef;
use super::{Tool, ToolContext};
pub fn tool() -> Tool { Tool::new(definition(), exec)}
fn definition() -> ToolDef { ToolDef::function( "edit_memory", "update an existing memory. use this to edit content, retag, pin, unpin, or edit the special \ soul memory. for normal memories, you can change content, tags, and pinned state. \ you can also archive, suppress, tombstone, restore, or supersede a normal memory. \ archived memories stop surfacing in recall but remain available through provenance; \ tombstoned memories are redacted and cannot be restored. \ for the special soul memory, set special=\"soul\" and provide content.", json!({ "type": "object", "properties": { "id": { "type": "integer", "description": "memory id for a normal memory" }, "special": { "type": "string", "enum": ["soul"], "description": "special memory target. use \"soul\" to edit your soul" }, "content": { "type": "string", "description": "replacement content for a normal memory or special=\"soul\"" }, "pinned": { "type": "boolean", "description": "set pinned/unpinned state for a normal memory" }, "tags": { "type": "array", "items": { "type": "string" }, "description": "replace the tags on a normal memory" }, "status": { "type": "string", "enum": ["active", "archived", "suppressed", "tombstoned"], "description": "set lifecycle status for a normal memory" }, "reason": { "type": "string", "description": "optional reason when status=\"tombstoned\"" }, "superseded_by": { "type": "integer", "description": "newer memory id that replaces this memory. creates a supersedes provenance edge and archives this memory" } } }), )}
fn exec(args: serde_json::Value, ctx: ToolContext) -> Pin<Box<dyn Future<Output = String> + Send>> { Box::pin(execute(args, ctx))}
async fn execute(args: serde_json::Value, ctx: ToolContext) -> String { let special = args["special"].as_str(); if special == Some("soul") { let Some(content) = args["content"].as_str() else { return "error: special=\"soul\" requires 'content'".into(); }; return match ctx.memory.set_soul_text(content) { Ok(_) => "updated soul memory".into(), Err(err) => format!("error: {err}"), }; }
let Some(id) = args["id"].as_i64() else { return "error: provide either 'id' or special=\"soul\"".into(); };
let mut changed = Vec::new();
if let Some(content) = args["content"].as_str() { match ctx.llm.embed(content).await { Ok(emb) => { if let Err(err) = ctx.memory.set_content(id, content, &emb) { return format!("error: {err}"); } } Err(err) => return format!("error: content embedding failed: {err}"), } changed.push("content".to_string()); }
if let Some(tags) = args["tags"].as_array() { let tags: Vec<String> = tags .iter() .filter_map(|value| value.as_str().map(String::from)) .collect(); if let Err(err) = ctx.memory.set_tags(id, &tags) { return format!("error: {err}"); } changed.push(format!("tags={}", tags.join(", "))); }
if let Some(pinned) = args["pinned"].as_bool() { if let Err(err) = ctx.memory.set_pinned(id, pinned) { return format!("error: {err}"); } changed.push(format!("pinned={pinned}")); }
if let Some(status) = args["status"].as_str() { let result = match status { "active" => ctx.memory.restore_memory(id), "archived" => ctx.memory.archive_memory(id), "suppressed" => ctx.memory.suppress_memory(id), "tombstoned" => ctx.memory.tombstone_memory(id, args["reason"].as_str()), _ => return "error: invalid status".into(), }; if let Err(err) = result { return format!("error: {err}"); } changed.push(format!("status={status}")); }
if let Some(new_id) = args["superseded_by"].as_i64() { if let Err(err) = ctx.memory.supersede_memory(id, new_id) { return format!("error: {err}"); } changed.push(format!("superseded_by={new_id}")); }
if changed.is_empty() { return "error: no changes requested".into(); }
format!("updated memory {id}: {}", changed.join("; "))}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102use std::future::Future;use std::pin::Pin;
use serde_json::json;
use crate::{memory::MemoryStore, models::ToolDef, mvp::MemoryStatus};
use super::{Tool, ToolContext};
pub fn tool() -> Tool { Tool::new(definition(), exec)}
fn definition() -> ToolDef { ToolDef::function( "fetch_memories", "fetch the full verbatim text of specific memories by their IDs. \ use this to read the complete details of memories that were returned as search snippets (marked with [snippet]).", json!({ "type": "object", "properties": { "ids": { "type": "array", "items": { "type": "integer" }, "description": "the list of memory IDs to fetch" } }, "required": ["ids"] }), )}
fn exec(args: serde_json::Value, ctx: ToolContext) -> Pin<Box<dyn Future<Output = String> + Send>> { Box::pin(execute(args, ctx))}
async fn execute(args: serde_json::Value, ctx: ToolContext) -> String { let ids: Vec<i64> = match args["ids"].as_array() { Some(arr) => arr.iter().filter_map(|v| v.as_i64()).collect(), None => return "error: missing required arg 'ids'".into(), };
if ids.is_empty() { return "error: empty 'ids' array".into(); }
let mut out = Vec::new(); for id in ids { match ctx.memory.get_memory(id) { Ok(Some(memory)) => { let tag_str = if memory.tags.is_empty() { String::new() } else { format!(" [{}]", memory.tags.join(", ")) }; let status_str = match memory.status { MemoryStatus::Active => "active", MemoryStatus::Archived => "archived", MemoryStatus::Suppressed => "suppressed", MemoryStatus::Tombstoned => "tombstoned", }; out.push(format!( "[id:{}] [status:{}]{}{} {}", memory.memory_id, status_str, tag_str, provenance_hint(&ctx.memory, memory.memory_id), memory.text )); } Ok(None) => { out.push(format!("error: memory {id} not found")); } Err(err) => { out.push(format!("error fetching memory {id}: {err}")); } } } out.join("\n")}
fn provenance_hint(memory: &MemoryStore, memory_id: i64) -> String { let hints = memory .provenance_counts(memory_id) .unwrap_or_default() .into_iter() .filter(|(_, count)| *count > 0) .map(|(edge_type, count)| { let name = match edge_type { crate::mvp::MemoryEdgeType::DerivedFrom => "derived_from", crate::mvp::MemoryEdgeType::Supersedes => "supersedes", crate::mvp::MemoryEdgeType::Supports => "supports", }; format!("{name}:{count}") }) .collect::<Vec<_>>(); if hints.is_empty() { String::new() } else { format!(" [{}]", hints.join(",")) }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188use std::future::Future;use std::pin::Pin;
use serde_json::json;
use crate::{memory::MemoryStore, models::ToolDef, mvp::MemoryStatus};
use super::{Tool, ToolContext};
pub fn tool() -> Tool { Tool::new(definition(), exec)}
fn definition() -> ToolDef { ToolDef::function( "list_memories", "list current pinned memories and recent unpinned memories with their ids. \ useful before a reflection pass to see what's stored. set include_inactive=true \ to inspect archived, suppressed, and tombstoned memories too. set include_soul=true \ to include the special system soul memory.", json!({ "type": "object", "properties": { "include_soul": { "type": "boolean", "description": "include the special system soul memory (default false)" }, "include_inactive": { "type": "boolean", "description": "include archived, suppressed, and tombstoned records (default false)" }, "limit": { "type": "integer", "description": "number of recent memories to list when include_inactive=true (default 20)" } }, "required": [] }), )}
fn exec(args: serde_json::Value, ctx: ToolContext) -> Pin<Box<dyn Future<Output = String> + Send>> { Box::pin(execute(args, ctx))}
async fn execute(args: serde_json::Value, ctx: ToolContext) -> String { let include_soul = args["include_soul"].as_bool().unwrap_or(false); let include_inactive = args["include_inactive"].as_bool().unwrap_or(false); let limit = args["limit"].as_u64().unwrap_or(20).clamp(1, 100) as usize;
let mut out = String::new(); if include_soul { let soul = ctx.memory.soul_text().unwrap_or_default(); out.push_str("## soul\n"); match soul { Some(content) => out.push_str(&format!("[special:soul] {content}\n")), None => out.push_str("(none)\n"), } out.push('\n'); }
if include_inactive { let memories = ctx .memory .list_recent_memories(limit, true) .unwrap_or_default(); out.push_str("## recent memories\n"); if memories.is_empty() { out.push_str("(none)\n"); } else { for memory in memories { out.push_str(&format!( "{}\n", format_memory_line( &ctx.memory, memory.id, &memory.content, &memory.tags, &memory.status, memory.pinned, memory.source_ref.as_deref(), ) )); } } return out; }
let pinned = ctx.memory.pinned_memory_entries().unwrap_or_default(); let unpinned = ctx.memory.recent_unpinned(10).unwrap_or_default();
out.push_str("## pinned\n"); if pinned.is_empty() { out.push_str("(none)\n"); } else { for memory in &pinned { out.push_str(&format!( "{}\n", format_memory_line( &ctx.memory, memory.id, &memory.content, &memory.tags, &MemoryStatus::Active, true, None, ) )); } } out.push_str("\n## recent unpinned\n"); if unpinned.is_empty() { out.push_str("(none)\n"); } else { for (id, content, tags) in &unpinned { out.push_str(&format!( "{}\n", format_memory_line( &ctx.memory, *id, content, tags, &MemoryStatus::Active, false, None, ) )); } } out}
fn format_memory_line( memory: &MemoryStore, id: i64, content: &str, tags: &[String], status: &MemoryStatus, pinned: bool, source_ref: Option<&str>,) -> String { let tag_str = if tags.is_empty() { String::new() } else { format!(" [{}]", tags.join(", ")) }; let pinned_str = if pinned { " [pinned]" } else { "" }; let source_str = source_ref .filter(|value| !value.is_empty()) .map(|value| format!(" [source:{value}]")) .unwrap_or_default(); format!( "[id:{id}] [{}]{pinned_str}{}{tag_str}{source_str} {content}", memory_status(status), edge_hints(memory, id), )}
fn edge_hints(memory: &MemoryStore, id: i64) -> String { let hints = memory .provenance_counts(id) .unwrap_or_default() .into_iter() .filter(|(_, count)| *count > 0) .map(|(edge_type, count)| { let name = match edge_type { crate::mvp::MemoryEdgeType::DerivedFrom => "derived_from", crate::mvp::MemoryEdgeType::Supersedes => "supersedes", crate::mvp::MemoryEdgeType::Supports => "supports", }; format!("{name}:{count}") }) .collect::<Vec<_>>(); if hints.is_empty() { String::new() } else { format!(" [{}]", hints.join(",")) }}
fn memory_status(status: &MemoryStatus) -> &'static str { match status { MemoryStatus::Active => "active", MemoryStatus::Archived => "archived", MemoryStatus::Suppressed => "suppressed", MemoryStatus::Tombstoned => "tombstoned", }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100use std::future::Future;use std::pin::Pin;
use serde_json::json;
use crate::models::ToolDef;
use super::{Tool, ToolContext};
pub fn tool() -> Tool { Tool::new(definition(), exec)}
fn definition() -> ToolDef { ToolDef::function( "memory_provenance", "inspect provenance sources for a memory id. use this when a recalled memory is a \ summary, derived fact, or superseding fact and you need to verify where it came from. \ archived source memories can appear here even though normal recall hides them.", json!({ "type": "object", "properties": { "id": { "type": "integer", "description": "memory id to inspect" }, "depth": { "type": "integer", "description": "max provenance traversal depth (default 1, max 3)" } }, "required": ["id"] }), )}
fn exec(args: serde_json::Value, ctx: ToolContext) -> Pin<Box<dyn Future<Output = String> + Send>> { Box::pin(execute(args, ctx))}
async fn execute(args: serde_json::Value, ctx: ToolContext) -> String { let Some(id) = args["id"].as_i64() else { return "error: missing required arg 'id'".into(); }; let depth = args["depth"].as_u64().unwrap_or(1).clamp(1, 3) as usize;
let memory = match ctx.memory.get_memory(id) { Ok(Some(memory)) => memory, Ok(None) => return format!("memory {id} not found"), Err(err) => return format!("error: {err}"), };
let sources = match ctx.memory.provenance_sources(id, depth) { Ok(sources) => sources, Err(err) => return format!("error: {err}"), };
let mut out = String::new(); out.push_str("## memory\n"); out.push_str(&format!( "[id:{}] [status:{}]{} {}\n", memory.memory_id, memory_status(&memory.status), format_tags(&memory.tags), memory.text ));
out.push_str("\n## provenance sources\n"); if sources.is_empty() { out.push_str("(none)\n"); } else { for source in sources { out.push_str(&format!( "[id:{}] [status:{}]{} {}\n", source.memory_id, memory_status(&source.status), format_tags(&source.tags), source.text )); } } out}
fn format_tags(tags: &[String]) -> String { if tags.is_empty() { String::new() } else { format!(" [tags:{}]", tags.join(", ")) }}
fn memory_status(status: &crate::mvp::MemoryStatus) -> &'static str { match status { crate::mvp::MemoryStatus::Active => "active", crate::mvp::MemoryStatus::Archived => "archived", crate::mvp::MemoryStatus::Suppressed => "suppressed", crate::mvp::MemoryStatus::Tombstoned => "tombstoned", }}123456789101112131415161718192021222324252627282930313233343536373839404142434445use std::future::Future;use std::pin::Pin;
use serde_json::json;
use crate::llm::ToolDef;
use super::{Tool, ToolContext};
pub fn tool() -> Tool { Tool::new(definition(), exec)}
fn definition() -> ToolDef { ToolDef::function( "pin_memory", "pin an existing memory so it appears at every startup. use during reflection to \ promote unpinned memories that turned out to be long-term important.", json!({ "type": "object", "properties": { "id": { "type": "integer", "description": "memory id (shown in list_memories or recall results)" } }, "required": ["id"] }), )}
fn exec(args: serde_json::Value, ctx: ToolContext) -> Pin<Box<dyn Future<Output = String> + Send>> { Box::pin(execute(args, ctx))}
async fn execute(args: serde_json::Value, ctx: ToolContext) -> String { let id = match args["id"].as_i64() { Some(i) => i, None => return "error: missing required arg 'id'".into(), }; match ctx.memory.set_pinned(id, true) { Ok(_) => format!("pinned memory {id}"), Err(e) => format!("error: {e}"), }}