consciousness/src/agent/tools/working_stack.rs
ProofOfConcept 998b71e52c flatten: move poc-memory contents to workspace root
No more subcrate nesting — src/, agents/, schema/, defaults/, build.rs
all live at the workspace root. poc-daemon remains as the only workspace
member. Crate name (poc-memory) and all imports unchanged.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
2026-03-25 00:54:12 -04:00

137 lines
4.7 KiB
Rust

// 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>) -> 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
}