diff options
| author | soryu <soryu@soryu.co> | 2026-02-16 01:24:19 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-16 01:31:31 +0000 |
| commit | 0676468e3e69ff36f1e509d775f191dd41f6080b (patch) | |
| tree | 624fb2e69827299ae9e571240e53d226124f55dc /makima/src | |
| parent | a9da99085bc0b1f94e13cb27639915fd1398ccbe (diff) | |
| download | soryu-0676468e3e69ff36f1e509d775f191dd41f6080b.tar.gz soryu-0676468e3e69ff36f1e509d775f191dd41f6080b.zip | |
Ensure directives replan on goal change
Diffstat (limited to 'makima/src')
| -rw-r--r-- | makima/src/bin/makima.rs | 2 | ||||
| -rw-r--r-- | makima/src/daemon/api/directive.rs | 7 | ||||
| -rw-r--r-- | makima/src/daemon/cli/directive.rs | 4 | ||||
| -rw-r--r-- | makima/src/db/repository.rs | 38 | ||||
| -rw-r--r-- | makima/src/orchestration/directive.rs | 11 |
5 files changed, 57 insertions, 5 deletions
diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs index c2c9beb..406f6e1 100644 --- a/makima/src/bin/makima.rs +++ b/makima/src/bin/makima.rs @@ -821,7 +821,7 @@ async fn run_directive( DirectiveCommand::Update(args) => { let client = ApiClient::new(args.common.api_url, args.common.api_key)?; let result = client - .directive_update(args.common.directive_id, args.pr_url, args.pr_branch) + .directive_update(args.common.directive_id, args.pr_url, args.pr_branch, args.status) .await?; println!("{}", serde_json::to_string(&result.0)?); } diff --git a/makima/src/daemon/api/directive.rs b/makima/src/daemon/api/directive.rs index a0cdab0..1088eb7 100644 --- a/makima/src/daemon/api/directive.rs +++ b/makima/src/daemon/api/directive.rs @@ -135,14 +135,15 @@ impl ApiClient { self.put(&format!("/api/v1/directives/{}/goal", directive_id), &req).await } - /// Update directive metadata (PR URL, PR branch, etc.) + /// Update directive metadata (PR URL, PR branch, status, etc.) pub async fn directive_update( &self, directive_id: Uuid, pr_url: Option<String>, pr_branch: Option<String>, + status: Option<String>, ) -> Result<JsonValue, ApiError> { - let req = UpdateDirectiveMetadataRequest { pr_url, pr_branch }; + let req = UpdateDirectiveMetadataRequest { pr_url, pr_branch, status }; self.put(&format!("/api/v1/directives/{}", directive_id), &req).await } @@ -155,4 +156,6 @@ pub struct UpdateDirectiveMetadataRequest { pub pr_url: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub pr_branch: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option<String>, } diff --git a/makima/src/daemon/cli/directive.rs b/makima/src/daemon/cli/directive.rs index 7c50c42..8a6a9f2 100644 --- a/makima/src/daemon/cli/directive.rs +++ b/makima/src/daemon/cli/directive.rs @@ -124,5 +124,9 @@ pub struct UpdateArgs { /// PR branch name to store on the directive #[arg(long)] pub pr_branch: Option<String>, + + /// Status to set on the directive (e.g., completed, paused) + #[arg(long)] + pub status: Option<String>, } diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index 4aa09dc..b5888c9 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -5051,6 +5051,7 @@ pub async fn update_directive_for_owner( let title = req.title.as_deref().unwrap_or(¤t.title); let goal = req.goal.as_deref().unwrap_or(¤t.goal); + let goal_changed = goal != current.goal; let status = req.status.as_deref().unwrap_or(¤t.status); let repository_url = req.repository_url.as_deref().or(current.repository_url.as_deref()); let local_path = req.local_path.as_deref().or(current.local_path.as_deref()); @@ -5066,6 +5067,7 @@ pub async fn update_directive_for_owner( SET title = $3, goal = $4, status = $5, repository_url = $6, local_path = $7, base_branch = $8, orchestrator_task_id = $9, pr_url = $10, pr_branch = $11, reconcile_mode = $12, + goal_updated_at = CASE WHEN $13 THEN NOW() ELSE goal_updated_at END, version = version + 1, updated_at = NOW() WHERE id = $1 AND owner_id = $2 RETURNING * @@ -5083,6 +5085,7 @@ pub async fn update_directive_for_owner( .bind(pr_url) .bind(pr_branch) .bind(reconcile_mode) + .bind(goal_changed) .fetch_optional(pool) .await .map_err(RepositoryError::Database)?; @@ -5574,7 +5577,9 @@ pub async fn check_directive_idle( Ok(result.rows_affected() > 0) } -/// Update a directive's goal and bump goal_updated_at. Reactivates if idle. +/// Update a directive's goal and bump goal_updated_at. +/// Reactivates idle/paused directives and clears any stale orchestrator task +/// so that replanning triggers on the next tick. pub async fn update_directive_goal( pool: &PgPool, owner_id: Uuid, @@ -5586,7 +5591,8 @@ pub async fn update_directive_goal( UPDATE directives SET goal = $3, goal_updated_at = NOW(), - status = CASE WHEN status = 'idle' THEN 'active' ELSE status END, + status = CASE WHEN status IN ('idle', 'paused') THEN 'active' ELSE status END, + orchestrator_task_id = NULL, updated_at = NOW(), version = version + 1 WHERE id = $1 AND owner_id = $2 @@ -5913,6 +5919,34 @@ pub async fn clear_orchestrator_task( Ok(()) } +/// Cancel old planning tasks for a directive. +/// Marks any non-terminal planning/re-planning tasks as interrupted, +/// excluding the given new task. Identifies planning tasks by name prefix +/// ("Plan: " or "Re-plan: ") to avoid cancelling completion/verification tasks. +pub async fn cancel_old_planning_tasks( + pool: &PgPool, + directive_id: Uuid, + exclude_task_id: Uuid, +) -> Result<u64, sqlx::Error> { + let result = sqlx::query( + r#" + UPDATE tasks + SET status = 'interrupted', + completed_at = NOW(), + updated_at = NOW() + WHERE directive_id = $1 + AND id != $2 + AND (name LIKE 'Plan: %' OR name LIKE 'Re-plan: %') + AND status NOT IN ('completed', 'failed', 'merged', 'done', 'interrupted') + "#, + ) + .bind(directive_id) + .bind(exclude_task_id) + .execute(pool) + .await?; + Ok(result.rows_affected()) +} + /// Link a task to a step without changing step status. pub async fn link_task_to_step( pool: &PgPool, diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs index 92aacde..9113fd4 100644 --- a/makima/src/orchestration/directive.rs +++ b/makima/src/orchestration/directive.rs @@ -382,6 +382,17 @@ impl DirectiveOrchestrator { repository::assign_orchestrator_task(&self.pool, directive_id, task.id).await?; + // Cancel any old planning tasks for this directive (superseded by the new one) + let cancelled = + repository::cancel_old_planning_tasks(&self.pool, directive_id, task.id).await?; + if cancelled > 0 { + tracing::info!( + directive_id = %directive_id, + cancelled_count = cancelled, + "Cancelled old planning tasks superseded by new plan" + ); + } + // Try to dispatch to a daemon self.try_dispatch_task(task.id, owner_id, &task.name, &task.plan, task.version).await; |
