2026-07-12 11:28:39 +07:00
//! Tool for saving a new memory entry to persistent project memory.
2026-07-11 20:44:15 +07:00
use serde_json ::{ json , Value };
use anyhow ::{ Result , anyhow };
use super ::super ::Tool ;
use super ::super ::ToolCtx ;
use crate ::model ::memory ::Memory ;
2026-07-12 11:28:39 +07:00
/// Tool that writes a new `Memory` entry (name/description/content/kind) to disk.
2026-07-11 20:44:15 +07:00
pub struct Remember ;
impl Tool for Remember {
fn name ( & self ) -> & 'static str {
"remember"
}
fn description ( & self ) -> & 'static str {
"Save a piece of information to persistent project memory. Memory entries are injected into future conversations via the system prompt, so use this to record conventions, preferences, and important context."
}
fn parameters ( & self ) -> Value {
json! ({
"type" : "object" ,
"properties" : {
"name" : {
"type" : "string" ,
"description" : "Short unique name for the memory (kebab-case, e.g. 'testing-conventions')"
},
"description" : {
"type" : "string" ,
"description" : "One-line summary shown in the memory index"
},
"content" : {
"type" : "string" ,
"description" : "The memory content body"
},
"kind" : {
"type" : "string" ,
"description" : "Type of memory: 'project', 'reference', 'lesson', or 'feedback'" ,
"enum" : [ "project" , "reference" , "lesson" , "feedback" ]
}
},
"required" : [ "name" , "description" , "content" , "kind" ]
})
}
2026-07-12 11:28:39 +07:00
/// Build a `Memory` from the given args and persist it to `ctx.memory_dir`.
///
/// Flow: extract name/description/content/kind → validate name via `Memory::slugify`
/// → construct `Memory` with `lifecycle: "new"` and current timestamps →
/// `memory.write`.
///
/// Why: name must slugify to a valid filename (alphanumeric + hyphens, 1-80 chars)
/// since it's used directly as the on-disk file identifier.
///
/// Return: confirmation string on success; error if name is invalid or the write fails.
2026-07-11 20:44:15 +07:00
fn run ( & self , ctx : & ToolCtx , args : & Value ) -> Result < String > {
let name = args . get ( "name" )
. and_then ( | v | v . as_str ())
. ok_or_else ( || anyhow! ( "missing required argument: name" )) ? ;
let description = args . get ( "description" )
. and_then ( | v | v . as_str ())
. ok_or_else ( || anyhow! ( "missing required argument: description" )) ? ;
let content = args . get ( "content" )
. and_then ( | v | v . as_str ())
. ok_or_else ( || anyhow! ( "missing required argument: content" )) ? ;
let kind = args . get ( "kind" )
. and_then ( | v | v . as_str ())
. ok_or_else ( || anyhow! ( "missing required argument: kind" )) ? ;
if Memory ::slugify ( name ). is_none () {
anyhow ::bail! ( "invalid memory name: must produce a valid slug (alphanumeric + hyphens, 1-80 chars)" );
}
let now = chrono ::Utc ::now (). timestamp_millis ();
let memory = Memory {
name : name . to_string (),
description : description . to_string (),
content : content . to_string (),
kind : kind . to_string (),
created_at : now ,
updated_at : now ,
outcome : None ,
lifecycle : "new" . to_string (),
2026-07-11 21:06:22 +07:00
scope : None ,
before_snippet : None ,
after_snippet : None ,
provenances : vec ! [],
2026-07-11 20:44:15 +07:00
};
memory . write ( & ctx . memory_dir )
. map_err ( | e | anyhow! ( "failed to write memory '{}': {}" , name , e )) ? ;
Ok ( format! ( "saved memory ' {} ' ( {} )" , name , kind ))
}
}