@@ -12,6 +12,28 @@ use crate::protocol::ToolResult;
1212/// Default threshold for auto-consolidation (can be overridden by config).
1313const AUTO_CONSOLIDATE_THRESHOLD : usize = 10 ;
1414
15+ /// Parse a JSON keywords array from tool arguments.
16+ fn parse_keywords ( args : & Value ) -> Vec < String > {
17+ args. get ( "keywords" )
18+ . and_then ( |v| v. as_array ( ) )
19+ . map ( |arr| {
20+ arr. iter ( )
21+ . filter_map ( |v| v. as_str ( ) . map ( String :: from) )
22+ . collect ( )
23+ } )
24+ . unwrap_or_default ( )
25+ }
26+
27+ /// Check if a memory topic matches a filter (exact or prefix match).
28+ fn topic_matches ( memory_topic : & str , filter : & str ) -> bool {
29+ memory_topic == filter || memory_topic. starts_with ( & format ! ( "{filter}:" ) )
30+ }
31+
32+ /// Check if memory keywords contain a substring match.
33+ fn keyword_matches ( keywords : & [ String ] , filter : & str ) -> bool {
34+ keywords. iter ( ) . any ( |k| k. contains ( filter) )
35+ }
36+
1537/// Try to auto-consolidate a topic if it exceeds the threshold.
1638/// Returns a human-readable message if consolidation happened, or empty string.
1739fn try_auto_consolidate ( store : & SqliteStore , topic : & str , threshold : usize ) -> String {
@@ -587,19 +609,17 @@ fn tool_store(
587609
588610 let mut memory = Memory :: new ( topic. into ( ) , content. into ( ) , importance) ;
589611
590- if let Some ( keywords) = args. get ( "keywords" ) . and_then ( |v| v. as_array ( ) ) {
591- memory. keywords = keywords
592- . iter ( )
593- . filter_map ( |v| v. as_str ( ) . map ( String :: from) )
594- . collect ( ) ;
612+ let kw = parse_keywords ( args) ;
613+ if !kw. is_empty ( ) {
614+ memory. keywords = kw;
595615 }
596616
597617 if let Some ( raw) = get_str ( args, "raw_excerpt" ) {
598618 memory. raw_excerpt = Some ( raw. into ( ) ) ;
599619 }
600620
601621 // Auto-embed if embedder is available
602- let embed_text = format ! ( "{topic} {content}" ) ;
622+ let embed_text = memory . embed_text ( ) ;
603623 let embed_vec = if let Some ( emb) = embedder {
604624 match emb. embed ( & embed_text) {
605625 Ok ( vec) => Some ( vec) ,
@@ -629,11 +649,9 @@ fn tool_store(
629649 if let Some ( raw) = get_str ( args, "raw_excerpt" ) {
630650 updated. raw_excerpt = Some ( raw. into ( ) ) ;
631651 }
632- if let Some ( keywords_arr) = args. get ( "keywords" ) . and_then ( |v| v. as_array ( ) ) {
633- updated. keywords = keywords_arr
634- . iter ( )
635- . filter_map ( |v| v. as_str ( ) . map ( String :: from) )
636- . collect ( ) ;
652+ let kw = parse_keywords ( args) ;
653+ if !kw. is_empty ( ) {
654+ updated. keywords = kw;
637655 }
638656 updated. importance = importance;
639657 updated. embedding = Some ( query_emb. clone ( ) ) ;
@@ -744,11 +762,10 @@ fn tool_recall(
744762 if let Ok ( results) = store. search_hybrid ( query, & query_emb, limit) {
745763 let mut scored_results = results;
746764 if let Some ( t) = topic {
747- scored_results
748- . retain ( |( m, _) | m. topic == t || m. topic . starts_with ( & format ! ( "{t}:" ) ) ) ;
765+ scored_results. retain ( |( m, _) | topic_matches ( & m. topic , t) ) ;
749766 }
750767 if let Some ( kw) = keyword {
751- scored_results. retain ( |( m, _) | m. keywords . iter ( ) . any ( |k| k . contains ( kw ) ) ) ;
768+ scored_results. retain ( |( m, _) | keyword_matches ( & m. keywords , kw ) ) ;
752769 }
753770
754771 // Batch update access counts
@@ -779,10 +796,10 @@ fn tool_recall(
779796 }
780797
781798 if let Some ( t) = topic {
782- results. retain ( |m| m. topic == t || m . topic . starts_with ( & format ! ( "{t}:" ) ) ) ;
799+ results. retain ( |m| topic_matches ( & m. topic , t ) ) ;
783800 }
784801 if let Some ( kw) = keyword {
785- results. retain ( |m| m. keywords . iter ( ) . any ( |k| k . contains ( kw ) ) ) ;
802+ results. retain ( |m| keyword_matches ( & m. keywords , kw ) ) ;
786803 }
787804
788805 // Batch update access counts
@@ -918,17 +935,14 @@ fn tool_update(store: &SqliteStore, embedder: Option<&dyn Embedder>, args: &Valu
918935 }
919936 }
920937
921- if let Some ( keywords_arr) = args. get ( "keywords" ) . and_then ( |v| v. as_array ( ) ) {
922- memory. keywords = keywords_arr
923- . iter ( )
924- . filter_map ( |v| v. as_str ( ) . map ( String :: from) )
925- . collect ( ) ;
938+ let kw = parse_keywords ( args) ;
939+ if !kw. is_empty ( ) {
940+ memory. keywords = kw;
926941 }
927942
928943 // Re-embed if embedder available
929944 if let Some ( emb) = embedder {
930- let text = format ! ( "{} {}" , memory. topic, content) ;
931- if let Ok ( vec) = emb. embed ( & text) {
945+ if let Ok ( vec) = emb. embed ( & memory. embed_text ( ) ) {
932946 memory. embedding = Some ( vec) ;
933947 }
934948 }
0 commit comments