diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index 9eafc76..5754531 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -51,7 +51,7 @@ pub use paths::{ G3_WORKSPACE_PATH_ENV, ensure_session_dir, get_context_summary_file, get_g3_dir, get_logs_dir, get_session_file, get_session_logs_dir, get_thinned_dir, logs_dir, }; -use paths::get_todo_path; +use paths::{get_todo_path, get_session_todo_path}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ToolCall { @@ -1114,23 +1114,9 @@ impl Agent { context_window.add_message(readme_message); } - // Load existing TODO list if present (after system prompt and README) - let todo_path = get_todo_path(); - let initial_todo_content = if todo_path.exists() { - std::fs::read_to_string(&todo_path).ok() - } else { - None - }; - - if let Some(ref todo_content) = initial_todo_content { - if !todo_content.trim().is_empty() { - let todo_message = Message::new( - MessageRole::System, - format!("📋 Existing TODO list (from todo.g3.md):\n\n{}", todo_content), - ); - context_window.add_message(todo_message); - } - } + // NOTE: TODO lists are now session-scoped and stored in .g3/sessions//todo.g3.md + // We don't load any TODO at initialization since we don't have a session_id yet. + // The agent will use todo_read to load the TODO once a session is established. // Initialize computer controller if enabled let computer_controller = if config.computer_control.enabled { @@ -1160,11 +1146,8 @@ impl Agent { session_id: None, tool_call_metrics: Vec::new(), ui_writer, - todo_content: std::sync::Arc::new(tokio::sync::RwLock::new({ - // Initialize from TODO.md file if it exists - let todo_path = get_todo_path(); - std::fs::read_to_string(&todo_path).unwrap_or_default() - })), + // TODO content starts empty - session-scoped TODOs are loaded via todo_read + todo_content: std::sync::Arc::new(tokio::sync::RwLock::new(String::new())), is_autonomous, quiet, computer_controller, @@ -2946,7 +2929,7 @@ impl Agent { }, Tool { name: "todo_read".to_string(), - description: "Read your current TODO list from todo.g3.md file in the workspace directory. Shows what tasks are planned and their status. Call this at the start of multi-step tasks to check for existing plans, and during execution to review progress before updating. TODO lists persist across g3 sessions.".to_string(), + description: "Read your current TODO list from todo.g3.md file in the session directory. Shows what tasks are planned and their status. Call this at the start of multi-step tasks to check for existing plans, and during execution to review progress before updating. TODO lists are scoped to the current session.".to_string(), input_schema: json!({ "type": "object", "properties": {}, @@ -2955,7 +2938,7 @@ impl Agent { }, Tool { name: "todo_write".to_string(), - description: "Create or update your TODO list in todo.g3.md file with a complete task plan. Use markdown checkboxes: - [ ] for pending, - [x] for complete. This tool replaces the entire file content, so always call todo_read first to preserve existing content. Essential for multi-step tasks. Changes persist across g3 sessions.".to_string(), + description: "Create or update your TODO list in todo.g3.md file with a complete task plan. Use markdown checkboxes: - [ ] for pending, - [x] for complete. This tool replaces the entire file content, so always call todo_read first to preserve existing content. Essential for multi-step tasks. TODO lists are scoped to the current session.".to_string(), input_schema: json!({ "type": "object", "properties": { @@ -5331,8 +5314,13 @@ impl Agent { } "todo_read" => { debug!("Processing todo_read tool call"); - // Read from todo.g3.md file (uses G3_TODO_PATH env var if set, else current dir) - let todo_path = get_todo_path(); + // Read from session-specific todo.g3.md if we have a session, else fall back to workspace + let todo_path = if let Some(ref session_id) = self.session_id { + let _ = ensure_session_dir(session_id); // Ensure dir exists + get_session_todo_path(session_id) + } else { + get_todo_path() + }; if !todo_path.exists() { // Also update in-memory content to stay in sync @@ -5447,7 +5435,12 @@ impl Agent { // If all todos are complete, delete the file instead of writing // EXCEPT in planner mode (G3_TODO_PATH is set) - preserve for rename to completed_todo_*.md let in_planner_mode = std::env::var("G3_TODO_PATH").is_ok(); - let todo_path = get_todo_path(); + let todo_path = if let Some(ref session_id) = self.session_id { + let _ = ensure_session_dir(session_id); // Ensure dir exists + get_session_todo_path(session_id) + } else { + get_todo_path() + }; if !in_planner_mode && !has_incomplete && (content_str.contains("- [x]") || content_str.contains("- [X]")) { if todo_path.exists() { @@ -5469,8 +5462,6 @@ impl Agent { } } - // Write to todo.g3.md file (uses G3_TODO_PATH env var if set, else current dir) - match std::fs::write(&todo_path, content_str) { Ok(_) => { // Also update in-memory content to stay in sync diff --git a/crates/g3-core/src/paths.rs b/crates/g3-core/src/paths.rs index 4407e99..517db77 100644 --- a/crates/g3-core/src/paths.rs +++ b/crates/g3-core/src/paths.rs @@ -27,6 +27,12 @@ pub fn get_todo_path() -> PathBuf { } } +/// Get the path to the todo.g3.md file for a specific session. +/// Returns .g3/sessions//todo.g3.md +pub fn get_session_todo_path(session_id: &str) -> PathBuf { + get_session_logs_dir(session_id).join("todo.g3.md") +} + /// Get the path to the logs directory. /// /// Checks for G3_WORKSPACE_PATH environment variable first (used by planning mode), @@ -110,15 +116,18 @@ mod tests { let thinned_dir = get_thinned_dir(session_id); let session_file = get_session_file(session_id); let summary_file = get_context_summary_file(session_id); + let todo_file = get_session_todo_path(session_id); // All paths should be under the session directory assert!(thinned_dir.starts_with(&session_dir)); assert!(session_file.starts_with(&session_dir)); assert!(summary_file.starts_with(&session_dir)); + assert!(todo_file.starts_with(&session_dir)); // Check expected filenames assert!(thinned_dir.ends_with("thinned")); assert!(session_file.ends_with("session.json")); assert!(summary_file.ends_with("context_summary.txt")); + assert!(todo_file.ends_with("todo.g3.md")); } } diff --git a/crates/g3-core/src/prompts.rs b/crates/g3-core/src/prompts.rs index 0f4a619..eaf3ee3 100644 --- a/crates/g3-core/src/prompts.rs +++ b/crates/g3-core/src/prompts.rs @@ -72,7 +72,7 @@ Every multi-step task follows this pattern: 2. **During**: Execute steps, then todo_read and todo_write to mark progress 3. **End**: Call todo_read to verify all items complete -Note: todo_write replaces the entire todo.g3.md file, so always read first to preserve content. TODO lists persist across g3 sessions in the workspace directory. +Note: todo_write replaces the entire todo.g3.md file, so always read first to preserve content. TODO lists are scoped to the current session and stored in the session directory. IMPORTANT: If you are provided with a SHA256 hash of the requirements file, you MUST include it as the very first line of the todo.g3.md file in the following format: `{{Based on the requirements file with SHA256: }}` @@ -270,11 +270,11 @@ Short description for providers without native calling specs: - **final_output**: Signal task completion with a detailed summary of work done in markdown format - Format: {\"tool\": \"final_output\", \"args\": {\"summary\": \"what_was_accomplished\"} -- **todo_read**: Read the entire TODO list from todo.g3.md file in workspace directory +- **todo_read**: Read the current session's TODO list from todo.g3.md (session-scoped) - Format: {\"tool\": \"todo_read\", \"args\": {}} - Example: {\"tool\": \"todo_read\", \"args\": {}} -- **todo_write**: Write or overwrite the entire todo.g3.md file (WARNING: overwrites completely, always read first) +- **todo_write**: Write or overwrite the session's todo.g3.md file (WARNING: overwrites completely, always read first) - Format: {\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Task 1\\n- [ ] Task 2\"}} - Example: {\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Implement feature\\n - [ ] Write tests\\n - [ ] Run tests\"}}