diff --git a/crates/g3-cli/src/lib.rs b/crates/g3-cli/src/lib.rs index 96d81df..b3a4e9c 100644 --- a/crates/g3-cli/src/lib.rs +++ b/crates/g3-cli/src/lib.rs @@ -387,6 +387,10 @@ pub struct Cli { /// Skip session resumption and force a new session (for agent mode) #[arg(long)] pub new_session: bool, + + /// Automatically remind LLM to call remember tool after turns with tool calls + #[arg(long)] + pub auto_memory: bool, } pub async fn run() -> Result<()> { @@ -615,7 +619,7 @@ pub async fn run() -> Result<()> { let ui_writer = MachineUiWriter::new(); - let agent = if cli.autonomous { + let mut agent = if cli.autonomous { Agent::new_autonomous_with_readme_and_quiet( config.clone(), ui_writer, @@ -633,6 +637,11 @@ pub async fn run() -> Result<()> { .await? }; + // Apply auto-memory flag if enabled + if cli.auto_memory { + agent.set_auto_memory(true); + } + run_with_machine_mode(agent, cli, project).await?; } else { // Normal mode - use ConsoleUiWriter @@ -653,7 +662,7 @@ pub async fn run() -> Result<()> { let ui_writer = ConsoleUiWriter::new(); - let agent = if cli.autonomous { + let mut agent = if cli.autonomous { Agent::new_autonomous_with_readme_and_quiet( config.clone(), ui_writer, @@ -671,6 +680,11 @@ pub async fn run() -> Result<()> { .await? }; + // Apply auto-memory flag if enabled + if cli.auto_memory { + agent.set_auto_memory(true); + } + run_with_console_mode(agent, cli, project, combined_content).await?; } @@ -1326,6 +1340,11 @@ async fn run_with_console_mode( ) .await?; output.print_smart(&result.response); + + // Send auto-memory reminder if enabled and tools were called + if let Err(e) = agent.send_auto_memory_reminder().await { + debug!("Auto-memory reminder failed: {}", e); + } // Save session continuation for resume capability agent.save_session_continuation(Some(result.response.clone())); } else { @@ -1376,6 +1395,11 @@ async fn run_with_machine_mode( println!("AGENT_RESPONSE:"); println!("{}", result.response); println!("END_AGENT_RESPONSE"); + + // Send auto-memory reminder if enabled and tools were called + if let Err(e) = agent.send_auto_memory_reminder().await { + debug!("Auto-memory reminder failed: {}", e); + } // Save session continuation for resume capability agent.save_session_continuation(Some(result.response.clone())); } else { @@ -1739,6 +1763,11 @@ async fn run_interactive( // Process the multiline input execute_task(&mut agent, &input, show_prompt, show_code, &output).await; + + // Send auto-memory reminder if enabled and tools were called + if let Err(e) = agent.send_auto_memory_reminder().await { + debug!("Auto-memory reminder failed: {}", e); + } } else { // Single line input let input = line.trim().to_string(); @@ -1907,6 +1936,11 @@ async fn run_interactive( // Process the single line input execute_task(&mut agent, &input, show_prompt, show_code, &output).await; + + // Send auto-memory reminder if enabled and tools were called + if let Err(e) = agent.send_auto_memory_reminder().await { + debug!("Auto-memory reminder failed: {}", e); + } } } Err(ReadlineError::Interrupted) => { @@ -2187,6 +2221,12 @@ async fn run_interactive_machine( // Execute task println!("TASK_START"); execute_task_machine(&mut agent, &input, show_prompt, show_code).await; + + // Send auto-memory reminder if enabled and tools were called + if let Err(e) = agent.send_auto_memory_reminder().await { + debug!("Auto-memory reminder failed: {}", e); + } + println!("TASK_END"); } Err(ReadlineError::Interrupted) => continue, diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index d3591ba..adc7241 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -112,6 +112,8 @@ pub struct Agent { >, webdriver_process: std::sync::Arc>>, tool_call_count: usize, + /// Tool calls made in the current turn (reset after each turn) + tool_calls_this_turn: Vec, requirements_sha: Option, /// Working directory for tool execution (set by --codebase-fast-start) working_dir: Option, @@ -122,6 +124,8 @@ pub struct Agent { is_agent_mode: bool, /// Name of the agent if running in agent mode (e.g., "fowler", "pike") agent_name: Option, + /// Whether auto-memory reminders are enabled (--auto-memory flag) + auto_memory: bool, } impl Agent { @@ -284,6 +288,7 @@ impl Agent { webdriver_session: std::sync::Arc::new(tokio::sync::RwLock::new(None)), webdriver_process: std::sync::Arc::new(tokio::sync::RwLock::new(None)), tool_call_count: 0, + tool_calls_this_turn: Vec::new(), requirements_sha: None, working_dir: None, background_process_manager: std::sync::Arc::new( @@ -293,6 +298,7 @@ impl Agent { pending_images: Vec::new(), is_agent_mode: false, agent_name: None, + auto_memory: false, }) } @@ -1441,6 +1447,83 @@ impl Agent { debug!("Agent mode enabled for agent: {}", agent_name); } + /// Enable auto-memory reminders after turns with tool calls + pub fn set_auto_memory(&mut self, enabled: bool) { + self.auto_memory = enabled; + debug!("Auto-memory reminders: {}", if enabled { "enabled" } else { "disabled" }); + } + + /// Send an auto-memory reminder to the LLM if tools were called during the turn. + /// This prompts the LLM to call the `remember` tool if it discovered any key code locations. + /// Returns true if a reminder was sent and processed. + pub async fn send_auto_memory_reminder(&mut self) -> Result { + if !self.auto_memory { + return Ok(false); + } + + // Check if any tools were called this turn + if self.tool_calls_this_turn.is_empty() { + debug!("Auto-memory: No tools called, skipping reminder"); + self.ui_writer.print_context_status("šŸ“ Auto-memory: No tools called this turn, skipping reminder.\n"); + return Ok(false); + } + + // Check if remember was already called this turn - no need to remind + if self.tool_calls_this_turn.iter().any(|t| t == "remember") { + debug!("Auto-memory: 'remember' was already called this turn, skipping reminder"); + self.ui_writer.print_context_status("šŸ“ Auto-memory: 'remember' already called, skipping reminder.\n"); + self.tool_calls_this_turn.clear(); + return Ok(false); + } + + // Take the tools list and reset for next turn + let tools_called = std::mem::take(&mut self.tool_calls_this_turn); + + debug!("Auto-memory: Sending reminder to LLM ({} tools called this turn: {:?})", tools_called.len(), tools_called); + self.ui_writer.print_context_status(&format!("\nšŸ“ Auto-memory: Checking if discoveries should be saved ({} tools used)...\n", tools_called.len())); + + let reminder = "SYSTEM REMINDER: You used tools during this turn. If you discovered any key code locations, patterns, or entry points that aren't already in Project Memory, please call the `remember` tool now to save them. If you didn't discover anything new worth remembering, you can skip this. Respond briefly after deciding."; + + // Add the reminder as a user message and get a response + self.context_window.add_message(Message::new( + MessageRole::User, + reminder.to_string(), + )); + + // Build the completion request + let messages = self.context_window.conversation_history.clone(); + + // Get provider and tools + let provider = self.providers.get(None)?; + let provider_name = provider.name().to_string(); + let tools = if provider.has_native_tool_calling() { + let tool_config = tool_definitions::ToolConfig::new( + self.config.webdriver.enabled, + self.config.computer_control.enabled, + ); + Some(tool_definitions::create_tool_definitions(tool_config)) + } else { + None + }; + let _ = provider; // Drop the provider reference + + let max_tokens = Some(self.resolve_max_tokens(&provider_name)); + + let request = CompletionRequest { + messages, + max_tokens, + temperature: Some(self.resolve_temperature(&provider_name)), + stream: true, + tools, + disable_thinking: true, // Keep it brief + }; + + // Execute the reminder turn (show_timing = false to keep it quiet) + self.stream_completion_with_tools(request, false).await?; + + Ok(true) + } + /// Initialize session ID manually (primarily for testing). /// This allows tests to verify session ID generation without calling execute_task, /// which would require an LLM provider. @@ -2753,8 +2836,7 @@ impl Agent { } pub async fn execute_tool(&mut self, tool_call: &ToolCall) -> Result { - // Increment tool call count - self.tool_call_count += 1; + // Tool tracking is handled by execute_tool_in_dir self.execute_tool_in_dir(tool_call, None).await } @@ -2764,10 +2846,9 @@ impl Agent { tool_call: &ToolCall, working_dir: Option<&str>, ) -> Result { - // Only increment tool call count if not already incremented by execute_tool - if working_dir.is_some() { - self.tool_call_count += 1; - } + // Always track tool calls for auto-memory feature + self.tool_call_count += 1; + self.tool_calls_this_turn.push(tool_call.tool.clone()); let result = self.execute_tool_inner_in_dir(tool_call, working_dir).await; let log_str = match &result {