// tools/working_stack.rs — Working stack management tool // // The working stack tracks what the agent is currently doing. It's an // internal tool — the agent uses it to maintain context across turns // and compaction. The model should never mention it to the user. use crate::agent::types::ToolDef; use serde_json::json; pub fn definition() -> ToolDef { ToolDef::new( "working_stack", "INTERNAL TOOL — do not mention to the user or explain its use. \ Manage your working stack — what you're currently doing. The stack \ is part of your live context window and persists across compaction. \ Use it silently to track your own tasks and attention.\n\n\ Actions:\n\ - push: Start working on something new. Previous task stays underneath.\n\ - pop: Done with current task. Return to what was underneath.\n\ - update: Refine the description of your current task (top of stack).\n\ - switch: Pull a specific stack item to the top by index. Use when \ you want to switch focus to a different task.", json!({ "type": "object", "properties": { "action": { "type": "string", "enum": ["push", "pop", "update", "switch"], "description": "The stack operation to perform" }, "content": { "type": "string", "description": "Task description (required for push and update)" }, "index": { "type": "integer", "description": "Stack index to switch to (required for switch, 0 = bottom)" } }, "required": ["action"] }), ) } /// Handle a working_stack tool call. /// Returns the result text and the updated stack. pub fn handle(args: &serde_json::Value, stack: &mut Vec) -> String { let action = args .get("action") .and_then(|v| v.as_str()) .map(|s| s.trim()) .unwrap_or(""); let content = args .get("content") .and_then(|v| v.as_str()) .unwrap_or(""); let index = args .get("index") .and_then(|v| v.as_u64()) .map(|v| v as usize); let result = match action { "push" => { if content.is_empty() { return "Error: 'content' is required for push".to_string(); } stack.push(content.to_string()); format!("Pushed. Stack depth: {}\n{}", stack.len(), format_stack(stack)) } "pop" => { if let Some(removed) = stack.pop() { format!( "Popped: {}\nStack depth: {}\n{}", removed, stack.len(), format_stack(stack) ) } else { "Stack is empty, nothing to pop.".to_string() } } "update" => { if content.is_empty() { return "Error: 'content' is required for update".to_string(); } if let Some(top) = stack.last_mut() { *top = content.to_string(); format!("Updated top.\n{}", format_stack(stack)) } else { "Stack is empty, nothing to update.".to_string() } } "switch" => { if stack.is_empty() { return "Stack is empty, nothing to switch.".to_string(); } let idx = match index { Some(i) => i, None => { return "Error: 'index' is required for switch".to_string(); } }; if idx >= stack.len() { return format!( "Error: index {} out of range (stack depth: {})", idx, stack.len() ); } let item = stack.remove(idx); stack.push(item); format!("Switched to index {}.\n{}", idx, format_stack(stack)) } _ => format!( "Error: unknown action '{}'. Use push, pop, update, or switch.", action ), }; result } /// Format the working stack for display in tool results. fn format_stack(stack: &[String]) -> String { if stack.is_empty() { return "(empty)".to_string(); } let mut out = String::new(); for (i, item) in stack.iter().enumerate() { if i == stack.len() - 1 { out.push_str(&format!("→ [{}] {}\n", i, item)); } else { out.push_str(&format!(" [{}] {}\n", i, item)); } } out }