summaryrefslogtreecommitdiff
path: root/makima/src
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-16 01:24:19 +0000
committersoryu <soryu@soryu.co>2026-02-16 01:31:31 +0000
commit0676468e3e69ff36f1e509d775f191dd41f6080b (patch)
tree624fb2e69827299ae9e571240e53d226124f55dc /makima/src
parenta9da99085bc0b1f94e13cb27639915fd1398ccbe (diff)
downloadsoryu-0676468e3e69ff36f1e509d775f191dd41f6080b.tar.gz
soryu-0676468e3e69ff36f1e509d775f191dd41f6080b.zip
Ensure directives replan on goal change
Diffstat (limited to 'makima/src')
-rw-r--r--makima/src/bin/makima.rs2
-rw-r--r--makima/src/daemon/api/directive.rs7
-rw-r--r--makima/src/daemon/cli/directive.rs4
-rw-r--r--makima/src/db/repository.rs38
-rw-r--r--makima/src/orchestration/directive.rs11
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(&current.title);
let goal = req.goal.as_deref().unwrap_or(&current.goal);
+ let goal_changed = goal != current.goal;
let status = req.status.as_deref().unwrap_or(&current.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;