summaryrefslogtreecommitdiff
path: root/makima/src/server/handlers/chains.rs
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/server/handlers/chains.rs')
-rw-r--r--makima/src/server/handlers/chains.rs150
1 files changed, 141 insertions, 9 deletions
diff --git a/makima/src/server/handlers/chains.rs b/makima/src/server/handlers/chains.rs
index 5d26e6a..ae19ca0 100644
--- a/makima/src/server/handlers/chains.rs
+++ b/makima/src/server/handlers/chains.rs
@@ -15,8 +15,8 @@ use uuid::Uuid;
use crate::db::models::{
AddContractDefinitionRequest, ChainContractDefinition, ChainContractDetail,
ChainDefinitionGraphResponse, ChainEditorData, ChainEvent, ChainGraphResponse, ChainSummary,
- ChainWithContracts, CreateChainRequest, StartChainResponse, UpdateChainRequest,
- UpdateContractDefinitionRequest,
+ ChainWithContracts, CreateChainRequest, CreateTaskRequest, StartChainRequest,
+ StartChainResponse, UpdateChainRequest, UpdateContractDefinitionRequest,
};
use crate::db::repository::{self, RepositoryError};
use crate::server::auth::Authenticated;
@@ -1048,6 +1048,7 @@ pub async fn get_chain_definition_graph(
params(
("id" = Uuid, Path, description = "Chain ID")
),
+ request_body(content = Option<StartChainRequest>, description = "Optional start options"),
responses(
(status = 200, description = "Chain started", body = StartChainResponse),
(status = 400, description = "Chain cannot be started", body = ApiError),
@@ -1066,6 +1067,7 @@ pub async fn start_chain(
State(state): State<SharedState>,
Authenticated(auth): Authenticated,
Path(chain_id): Path<Uuid>,
+ body: Option<Json<StartChainRequest>>,
) -> impl IntoResponse {
let Some(ref pool) = state.db_pool else {
return (
@@ -1075,6 +1077,11 @@ pub async fn start_chain(
.into_response();
};
+ let req = body.map(|b| b.0).unwrap_or(StartChainRequest {
+ with_supervisor: false,
+ repository_url: None,
+ });
+
// Verify ownership and get chain
let chain = match repository::get_chain_for_owner(pool, chain_id, auth.owner_id).await {
Ok(Some(c)) => c,
@@ -1132,8 +1139,7 @@ pub async fn start_chain(
.into_response();
}
- // TODO: Implement chain supervisor spawning
- // For now, just update the chain status to active
+ // Update chain status to active
match repository::update_chain_status(pool, chain_id, "active").await {
Ok(_) => {}
Err(e) => {
@@ -1146,13 +1152,139 @@ pub async fn start_chain(
}
}
- // Return response indicating chain has started
- // supervisor_task_id is None until we implement the supervisor daemon
+ // Create supervisor task if requested
+ let mut supervisor_task_id: Option<Uuid> = None;
+ if req.with_supervisor {
+ let supervisor_name = format!("Chain Supervisor: {}", chain.name);
+ let supervisor_plan = format!(
+ r#"You are the supervisor for chain "{}".
+
+## Environment Variables
+- MAKIMA_CHAIN_ID={}
+- MAKIMA_API_URL (configured)
+- MAKIMA_API_KEY (configured)
+
+## Your Responsibilities
+1. Monitor chain progress by periodically checking chain status
+2. Validate that contracts are completing successfully
+3. Identify and report any issues or blockers
+4. Track the overall chain progress through the DAG
+
+## Available Commands
+Use these makima CLI commands to monitor the chain:
+
+```bash
+# Check chain status
+makima chain status {}
+
+# List contracts in the chain
+makima chain contracts {}
+
+# View the chain DAG with current status
+makima chain graph {} --with-status
+```
+
+## Monitoring Loop
+1. Check chain status every few minutes
+2. If a contract fails, investigate the issue
+3. Report progress to the user when milestones are reached
+4. Mark the chain as complete when all contracts finish
+
+## Current Chain Info
+- Chain ID: {}
+- Chain Name: {}
+- Total definitions: {}
+
+Begin monitoring the chain. Check the initial status and report what you find."#,
+ chain.name,
+ chain_id,
+ chain_id,
+ chain_id,
+ chain_id,
+ chain_id,
+ chain.name,
+ definitions.len()
+ );
+
+ let supervisor_req = CreateTaskRequest {
+ name: supervisor_name,
+ description: Some(format!("Supervisor task for chain: {}", chain.name)),
+ plan: supervisor_plan,
+ repository_url: req.repository_url.clone(),
+ base_branch: None,
+ target_branch: None,
+ parent_task_id: None,
+ contract_id: None, // Chain supervisor is not tied to a specific contract
+ target_repo_path: None,
+ completion_action: None,
+ continue_from_task_id: None,
+ copy_files: None,
+ is_supervisor: true,
+ checkpoint_sha: None,
+ priority: 0,
+ merge_mode: None,
+ branched_from_task_id: None,
+ conversation_history: None,
+ supervisor_worktree_task_id: None,
+ };
+
+ match repository::create_task_for_owner(pool, auth.owner_id, supervisor_req).await {
+ Ok(supervisor_task) => {
+ tracing::info!(
+ chain_id = %chain_id,
+ supervisor_task_id = %supervisor_task.id,
+ "Created supervisor task for chain"
+ );
+
+ // Update chain with supervisor_task_id
+ if let Err(e) =
+ repository::set_chain_supervisor_task(pool, chain_id, Some(supervisor_task.id))
+ .await
+ {
+ tracing::warn!(
+ chain_id = %chain_id,
+ error = %e,
+ "Failed to link supervisor task to chain"
+ );
+ }
+
+ supervisor_task_id = Some(supervisor_task.id);
+ }
+ Err(e) => {
+ tracing::warn!(
+ chain_id = %chain_id,
+ error = %e,
+ "Failed to create supervisor task for chain"
+ );
+ }
+ }
+ }
+
+ // Progress the chain - this creates root contracts (definitions with no dependencies)
+ let progression = match repository::progress_chain(pool, chain_id, auth.owner_id).await {
+ Ok(p) => p,
+ Err(e) => {
+ tracing::error!("Failed to progress chain: {}", e);
+ // Chain is active but no contracts created - return partial success
+ return Json(StartChainResponse {
+ chain_id,
+ supervisor_task_id,
+ contracts_created: vec![],
+ status: "active".to_string(),
+ })
+ .into_response();
+ }
+ };
+
Json(StartChainResponse {
chain_id,
- supervisor_task_id: None,
- contracts_created: vec![],
- status: "started".to_string(),
+ supervisor_task_id,
+ contracts_created: progression.contracts_created,
+ status: if progression.chain_completed {
+ "completed".to_string()
+ } else {
+ "active".to_string()
+ },
})
.into_response()
}