feat: add --auto-memory flag to prompt LLM to save discoveries
Adds a new --auto-memory CLI flag that automatically sends a reminder to the LLM after each turn where tools were called, prompting it to call the remember tool if it discovered any key code locations. Changes: - Add auto_memory field and set_auto_memory() method to Agent - Add tool_calls_this_turn tracking in execute_tool_in_dir() - Add send_auto_memory_reminder() that sends reminder after tool use - Add --auto-memory CLI flag and wire it up in console/machine modes - Call send_auto_memory_reminder() in single-shot and interactive modes - Add visible status messages for auto-memory actions Fixes bug where tool calls were not being tracked when execute_tool_in_dir was called directly with working_dir=None.
This commit is contained in:
@@ -387,6 +387,10 @@ pub struct Cli {
|
|||||||
/// Skip session resumption and force a new session (for agent mode)
|
/// Skip session resumption and force a new session (for agent mode)
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub new_session: bool,
|
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<()> {
|
pub async fn run() -> Result<()> {
|
||||||
@@ -615,7 +619,7 @@ pub async fn run() -> Result<()> {
|
|||||||
|
|
||||||
let ui_writer = MachineUiWriter::new();
|
let ui_writer = MachineUiWriter::new();
|
||||||
|
|
||||||
let agent = if cli.autonomous {
|
let mut agent = if cli.autonomous {
|
||||||
Agent::new_autonomous_with_readme_and_quiet(
|
Agent::new_autonomous_with_readme_and_quiet(
|
||||||
config.clone(),
|
config.clone(),
|
||||||
ui_writer,
|
ui_writer,
|
||||||
@@ -633,6 +637,11 @@ pub async fn run() -> Result<()> {
|
|||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Apply auto-memory flag if enabled
|
||||||
|
if cli.auto_memory {
|
||||||
|
agent.set_auto_memory(true);
|
||||||
|
}
|
||||||
|
|
||||||
run_with_machine_mode(agent, cli, project).await?;
|
run_with_machine_mode(agent, cli, project).await?;
|
||||||
} else {
|
} else {
|
||||||
// Normal mode - use ConsoleUiWriter
|
// Normal mode - use ConsoleUiWriter
|
||||||
@@ -653,7 +662,7 @@ pub async fn run() -> Result<()> {
|
|||||||
|
|
||||||
let ui_writer = ConsoleUiWriter::new();
|
let ui_writer = ConsoleUiWriter::new();
|
||||||
|
|
||||||
let agent = if cli.autonomous {
|
let mut agent = if cli.autonomous {
|
||||||
Agent::new_autonomous_with_readme_and_quiet(
|
Agent::new_autonomous_with_readme_and_quiet(
|
||||||
config.clone(),
|
config.clone(),
|
||||||
ui_writer,
|
ui_writer,
|
||||||
@@ -671,6 +680,11 @@ pub async fn run() -> Result<()> {
|
|||||||
.await?
|
.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?;
|
run_with_console_mode(agent, cli, project, combined_content).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1326,6 +1340,11 @@ async fn run_with_console_mode(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
output.print_smart(&result.response);
|
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
|
// Save session continuation for resume capability
|
||||||
agent.save_session_continuation(Some(result.response.clone()));
|
agent.save_session_continuation(Some(result.response.clone()));
|
||||||
} else {
|
} else {
|
||||||
@@ -1376,6 +1395,11 @@ async fn run_with_machine_mode(
|
|||||||
println!("AGENT_RESPONSE:");
|
println!("AGENT_RESPONSE:");
|
||||||
println!("{}", result.response);
|
println!("{}", result.response);
|
||||||
println!("END_AGENT_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
|
// Save session continuation for resume capability
|
||||||
agent.save_session_continuation(Some(result.response.clone()));
|
agent.save_session_continuation(Some(result.response.clone()));
|
||||||
} else {
|
} else {
|
||||||
@@ -1739,6 +1763,11 @@ async fn run_interactive<W: UiWriter>(
|
|||||||
|
|
||||||
// Process the multiline input
|
// Process the multiline input
|
||||||
execute_task(&mut agent, &input, show_prompt, show_code, &output).await;
|
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 {
|
} else {
|
||||||
// Single line input
|
// Single line input
|
||||||
let input = line.trim().to_string();
|
let input = line.trim().to_string();
|
||||||
@@ -1907,6 +1936,11 @@ async fn run_interactive<W: UiWriter>(
|
|||||||
|
|
||||||
// Process the single line input
|
// Process the single line input
|
||||||
execute_task(&mut agent, &input, show_prompt, show_code, &output).await;
|
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) => {
|
Err(ReadlineError::Interrupted) => {
|
||||||
@@ -2187,6 +2221,12 @@ async fn run_interactive_machine(
|
|||||||
// Execute task
|
// Execute task
|
||||||
println!("TASK_START");
|
println!("TASK_START");
|
||||||
execute_task_machine(&mut agent, &input, show_prompt, show_code).await;
|
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");
|
println!("TASK_END");
|
||||||
}
|
}
|
||||||
Err(ReadlineError::Interrupted) => continue,
|
Err(ReadlineError::Interrupted) => continue,
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ pub struct Agent<W: UiWriter> {
|
|||||||
>,
|
>,
|
||||||
webdriver_process: std::sync::Arc<tokio::sync::RwLock<Option<tokio::process::Child>>>,
|
webdriver_process: std::sync::Arc<tokio::sync::RwLock<Option<tokio::process::Child>>>,
|
||||||
tool_call_count: usize,
|
tool_call_count: usize,
|
||||||
|
/// Tool calls made in the current turn (reset after each turn)
|
||||||
|
tool_calls_this_turn: Vec<String>,
|
||||||
requirements_sha: Option<String>,
|
requirements_sha: Option<String>,
|
||||||
/// Working directory for tool execution (set by --codebase-fast-start)
|
/// Working directory for tool execution (set by --codebase-fast-start)
|
||||||
working_dir: Option<String>,
|
working_dir: Option<String>,
|
||||||
@@ -122,6 +124,8 @@ pub struct Agent<W: UiWriter> {
|
|||||||
is_agent_mode: bool,
|
is_agent_mode: bool,
|
||||||
/// Name of the agent if running in agent mode (e.g., "fowler", "pike")
|
/// Name of the agent if running in agent mode (e.g., "fowler", "pike")
|
||||||
agent_name: Option<String>,
|
agent_name: Option<String>,
|
||||||
|
/// Whether auto-memory reminders are enabled (--auto-memory flag)
|
||||||
|
auto_memory: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: UiWriter> Agent<W> {
|
impl<W: UiWriter> Agent<W> {
|
||||||
@@ -284,6 +288,7 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
webdriver_session: std::sync::Arc::new(tokio::sync::RwLock::new(None)),
|
webdriver_session: std::sync::Arc::new(tokio::sync::RwLock::new(None)),
|
||||||
webdriver_process: 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_call_count: 0,
|
||||||
|
tool_calls_this_turn: Vec::new(),
|
||||||
requirements_sha: None,
|
requirements_sha: None,
|
||||||
working_dir: None,
|
working_dir: None,
|
||||||
background_process_manager: std::sync::Arc::new(
|
background_process_manager: std::sync::Arc::new(
|
||||||
@@ -293,6 +298,7 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
pending_images: Vec::new(),
|
pending_images: Vec::new(),
|
||||||
is_agent_mode: false,
|
is_agent_mode: false,
|
||||||
agent_name: None,
|
agent_name: None,
|
||||||
|
auto_memory: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1441,6 +1447,83 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
debug!("Agent mode enabled for agent: {}", agent_name);
|
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<bool> {
|
||||||
|
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).
|
/// Initialize session ID manually (primarily for testing).
|
||||||
/// This allows tests to verify session ID generation without calling execute_task,
|
/// This allows tests to verify session ID generation without calling execute_task,
|
||||||
/// which would require an LLM provider.
|
/// which would require an LLM provider.
|
||||||
@@ -2753,8 +2836,7 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_tool(&mut self, tool_call: &ToolCall) -> Result<String> {
|
pub async fn execute_tool(&mut self, tool_call: &ToolCall) -> Result<String> {
|
||||||
// Increment tool call count
|
// Tool tracking is handled by execute_tool_in_dir
|
||||||
self.tool_call_count += 1;
|
|
||||||
self.execute_tool_in_dir(tool_call, None).await
|
self.execute_tool_in_dir(tool_call, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2764,10 +2846,9 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
tool_call: &ToolCall,
|
tool_call: &ToolCall,
|
||||||
working_dir: Option<&str>,
|
working_dir: Option<&str>,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
// Only increment tool call count if not already incremented by execute_tool
|
// Always track tool calls for auto-memory feature
|
||||||
if working_dir.is_some() {
|
self.tool_call_count += 1;
|
||||||
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 result = self.execute_tool_inner_in_dir(tool_call, working_dir).await;
|
||||||
let log_str = match &result {
|
let log_str = match &result {
|
||||||
|
|||||||
Reference in New Issue
Block a user