diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 6d45334..d9d0aea 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -467,11 +467,12 @@ inject_todo_instructions: true # Include the default todo instructions into pr ### How It Works -1. When `inject_todo_instructions` is enabled, agents receive instructions on using four built-in tools: +1. When `inject_todo_instructions` is enabled, agents receive instructions on using five built-in tools: - `todo__init`: Initialize a todo list with a goal - `todo__add`: Add a task to the list - `todo__done`: Mark a task complete - `todo__list`: View current todo state + - `todo__clear`: Clear the entire todo list and reset the goal These instructions are a reasonable default that detail how to use Loki's To-Do System. If you wish, you can disable the injection of the default instructions and specify your own instructions for how diff --git a/docs/REPL.md b/docs/REPL.md index 809a02d..2726883 100644 --- a/docs/REPL.md +++ b/docs/REPL.md @@ -120,13 +120,14 @@ For more information on sessions and how to use them in Loki, refer to the [sess Loki lets you build OpenAI GPT-style agents. The following commands let you interact with and manage your agents in Loki: -| Command | Description | -|----------------------|------------------------------------------------------------| -| `.agent` | Use an agent | -| `.starter` | Display and use conversation starters for the active agent | -| `.edit agent-config` | Open the agent configuration in your preferred text editor | -| `.info agent` | Display information about the active agent | -| `.exit agent` | Leave the active agent | +| Command | Description | +|----------------------|-----------------------------------------------------------------------------------------------| +| `.agent` | Use an agent | +| `.starter` | Display and use conversation starters for the active agent | +| `.clear todo` | Clear the todo list and stop auto-continuation (requires `auto_continue: true` on the agent) | +| `.edit agent-config` | Open the agent configuration in your preferred text editor | +| `.info agent` | Display information about the active agent | +| `.exit agent` | Leave the active agent | ![agent](./images/agents/sql.gif) diff --git a/docs/TODO-SYSTEM.md b/docs/TODO-SYSTEM.md index da3179b..18a06eb 100644 --- a/docs/TODO-SYSTEM.md +++ b/docs/TODO-SYSTEM.md @@ -117,6 +117,22 @@ Display the current todo list with status of each item. **Returns:** The full todo list with goal, progress, and item statuses +### `todo__clear` +Clear the entire todo list and reset the goal. Use when the current task has been canceled or invalidated. + +**Parameters:** None + +**Returns:** Confirmation that the todo list was cleared + +### REPL Command: `.clear todo` +You can also clear the todo list manually from the REPL by typing `.clear todo`. This is useful when: +- You gave a custom response that changes or cancels the current task +- The agent is stuck in auto-continuation with stale todos +- You want to start fresh without leaving and re-entering the agent + +**Note:** This command is only available when an agent with `auto_continue: true` is active. If the todo +system isn't enabled for the current agent, the command will display an error message. + ## Auto-Continuation When `auto_continue` is enabled, Loki automatically sends a continuation prompt if: diff --git a/src/config/agent.rs b/src/config/agent.rs index 69da450..87490ba 100644 --- a/src/config/agent.rs +++ b/src/config/agent.rs @@ -476,6 +476,11 @@ impl Agent { self.todo_list.mark_done(id) } + pub fn clear_todo_list(&mut self) { + self.todo_list.clear(); + self.reset_continuation(); + } + pub fn continuation_prompt(&self) -> String { self.config.continuation_prompt.clone().unwrap_or_else(|| { formatdoc! {" diff --git a/src/config/prompts.rs b/src/config/prompts.rs index 765fbd5..0a3553c 100644 --- a/src/config/prompts.rs +++ b/src/config/prompts.rs @@ -7,10 +7,12 @@ pub(in crate::config) const DEFAULT_TODO_INSTRUCTIONS: &str = indoc! {" - `todo__add`: Add individual tasks. Add all planned steps before starting work. - `todo__done`: Mark a task done by id. Call this immediately after completing each step. - `todo__list`: Show the current todo list. + - `todo__clear`: Clear the entire todo list and reset the goal. Use when the user cancels or changes direction. RULES: - Always create a todo list before starting work. - Mark each task done as soon as you finish it; do not batch. + - If the user cancels the current task or changes direction, call `todo__clear` immediately. - If you stop with incomplete tasks, the system will automatically prompt you to continue." }; diff --git a/src/config/todo.rs b/src/config/todo.rs index 7b070fe..8aa7ca5 100644 --- a/src/config/todo.rs +++ b/src/config/todo.rs @@ -67,6 +67,11 @@ impl TodoList { self.todos.is_empty() } + pub fn clear(&mut self) { + self.goal.clear(); + self.todos.clear(); + } + pub fn render_for_model(&self) -> String { let mut lines = Vec::new(); if !self.goal.is_empty() { @@ -149,6 +154,21 @@ mod tests { assert!(rendered.contains("○ 2. Map")); } + #[test] + fn test_clear() { + let mut list = TodoList::new("Some goal"); + list.add("Task 1"); + list.add("Task 2"); + list.mark_done(1); + assert!(!list.is_empty()); + + list.clear(); + assert!(list.is_empty()); + assert!(list.goal.is_empty()); + assert_eq!(list.todos.len(), 0); + assert!(!list.has_incomplete()); + } + #[test] fn test_serialization_roundtrip() { let mut list = TodoList::new("Roundtrip"); diff --git a/src/function/todo.rs b/src/function/todo.rs index da73f5e..d37fa5c 100644 --- a/src/function/todo.rs +++ b/src/function/todo.rs @@ -76,6 +76,16 @@ pub fn todo_function_declarations() -> Vec { }, agent: false, }, + FunctionDeclaration { + name: format!("{TODO_FUNCTION_PREFIX}clear"), + description: "Clear the entire todo list and reset the goal. Use when the current task has been canceled or invalidated.".to_string(), + parameters: JsonSchema { + type_value: Some("object".to_string()), + properties: Some(IndexMap::new()), + ..Default::default() + }, + agent: false, + }, ] } @@ -156,6 +166,17 @@ pub fn handle_todo_tool(config: &GlobalConfig, cmd_name: &str, args: &Value) -> None => bail!("No active agent"), } } + "clear" => { + let mut cfg = config.write(); + let agent = cfg.agent.as_mut(); + match agent { + Some(agent) => { + agent.clear_todo_list(); + Ok(json!({"status": "ok", "message": "Todo list cleared"})) + } + None => bail!("No active agent"), + } + } _ => bail!("Unknown todo action: {action}"), } } diff --git a/src/repl/mod.rs b/src/repl/mod.rs index 8c0652f..a4922aa 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -33,7 +33,7 @@ use std::{env, mem, process}; const MENU_NAME: &str = "completion_menu"; -static REPL_COMMANDS: LazyLock<[ReplCommand; 38]> = LazyLock::new(|| { +static REPL_COMMANDS: LazyLock<[ReplCommand; 39]> = LazyLock::new(|| { [ ReplCommand::new(".help", "Show this help guide", AssertState::pass()), ReplCommand::new(".info", "Show system info", AssertState::pass()), @@ -137,6 +137,11 @@ static REPL_COMMANDS: LazyLock<[ReplCommand; 38]> = LazyLock::new(|| { "Leave agent", AssertState::True(StateFlags::AGENT), ), + ReplCommand::new( + ".clear todo", + "Clear the todo list and stop auto-continuation", + AssertState::True(StateFlags::AGENT), + ), ReplCommand::new( ".rag", "Initialize or access RAG", @@ -804,6 +809,25 @@ pub async fn run_repl_command( Some("messages") => { bail!("Use '.empty session' instead"); } + Some("todo") => { + let mut cfg = config.write(); + match cfg.agent.as_mut() { + Some(agent) => { + if !agent.auto_continue_enabled() { + bail!( + "The todo system is not enabled for this agent. Set 'auto_continue: true' in the agent's config.yaml to enable it." + ); + } + if agent.todo_list().is_empty() { + println!("Todo list is already empty."); + } else { + agent.clear_todo_list(); + println!("Todo list cleared."); + } + } + None => bail!("No active agent"), + } + } _ => unknown_command()?, }, ".vault" => match split_first_arg(args) {