From ea96d9ba3d2028dc64b8f01ba6ba320b3bc10103 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 18 Feb 2026 09:13:11 -0700 Subject: [PATCH] fix: Forgot to implement support for failing a task and keep all dependents blocked --- src/config/prompts.rs | 1 + src/function/supervisor.rs | 52 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/config/prompts.rs b/src/config/prompts.rs index 939daa3..50b3ef8 100644 --- a/src/config/prompts.rs +++ b/src/config/prompts.rs @@ -32,6 +32,7 @@ pub(in crate::config) const DEFAULT_SPAWN_INSTRUCTIONS: &str = indoc! {" | `agent__task_create` | Create a task in the dependency-aware task queue. | | `agent__task_list` | List all tasks and their status/dependencies. | | `agent__task_complete` | Mark a task done; returns any newly unblocked tasks. Auto-dispatches agents for tasks with a designated agent. | + | `agent__task_fail` | Mark a task as failed. Dependents remain blocked. | ### Core Pattern: Spawn -> Continue -> Collect diff --git a/src/function/supervisor.rs b/src/function/supervisor.rs index 67c60b9..5d9e99d 100644 --- a/src/function/supervisor.rs +++ b/src/function/supervisor.rs @@ -201,6 +201,24 @@ pub fn supervisor_function_declarations() -> Vec { }, agent: false, }, + FunctionDeclaration { + name: format!("{SUPERVISOR_FUNCTION_PREFIX}task_fail"), + description: "Mark a task as failed. Dependents will remain blocked.".to_string(), + parameters: JsonSchema { + type_value: Some("object".to_string()), + properties: Some(IndexMap::from([( + "task_id".to_string(), + JsonSchema { + type_value: Some("string".to_string()), + description: Some("The task ID to mark as failed".into()), + ..Default::default() + }, + )])), + required: Some(vec!["task_id".to_string()]), + ..Default::default() + }, + agent: false, + }, ] } @@ -266,6 +284,7 @@ pub async fn handle_supervisor_tool( "task_create" => handle_task_create(config, args), "task_list" => handle_task_list(config), "task_complete" => handle_task_complete(config, args).await, + "task_fail" => handle_task_fail(config, args), _ => bail!("Unknown supervisor action: {action}"), } } @@ -819,6 +838,39 @@ async fn handle_task_complete(config: &GlobalConfig, args: &Value) -> Result Result { + let task_id = args + .get("task_id") + .and_then(Value::as_str) + .ok_or_else(|| anyhow!("'task_id' is required"))?; + + let cfg = config.read(); + let supervisor = cfg + .supervisor + .as_ref() + .ok_or_else(|| anyhow!("No supervisor active"))?; + let mut sup = supervisor.write(); + + let task = sup.task_queue().get(task_id); + if task.is_none() { + return Ok(json!({ + "status": "error", + "message": format!("Task '{task_id}' not found"), + })); + } + + let blocked_dependents: Vec = task.unwrap().blocks.iter().cloned().collect(); + + sup.task_queue_mut().fail(task_id); + + Ok(json!({ + "status": "ok", + "task_id": task_id, + "blocked_dependents": blocked_dependents, + "message": format!("Task '{task_id}' marked as failed. {} dependent task(s) will remain blocked.", blocked_dependents.len()), + })) +} + const SUMMARIZATION_PROMPT: &str = r#"You are a precise summarization assistant. Your job is to condense a sub-agent's output into a compact summary that preserves all actionable information. Rules: