diff options
| author | soryu <soryu@soryu.co> | 2026-01-23 20:00:34 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-23 20:00:34 +0000 |
| commit | 12cb721dbbe571bd3b2766546b2105ef034e6cf3 (patch) | |
| tree | 07a12739c23f1389a66090b8fc84dfffb1d99f82 | |
| parent | 9e286c146e29e714b3b209b4d948d75cce179b05 (diff) | |
| download | soryu-12cb721dbbe571bd3b2766546b2105ef034e6cf3.tar.gz soryu-12cb721dbbe571bd3b2766546b2105ef034e6cf3.zip | |
[WIP] Heartbeat checkpoint - 2026-01-23 20:00:34 UTC
| -rw-r--r-- | makima/src/daemon/api/supervisor.rs | 4 | ||||
| -rw-r--r-- | makima/src/daemon/cli/daemon.rs | 5 | ||||
| -rw-r--r-- | makima/src/daemon/config.rs | 17 | ||||
| -rw-r--r-- | makima/src/daemon/task/manager.rs | 25 | ||||
| -rw-r--r-- | makima/src/daemon/ws/protocol.rs | 4 |
5 files changed, 51 insertions, 4 deletions
diff --git a/makima/src/daemon/api/supervisor.rs b/makima/src/daemon/api/supervisor.rs index 74c27e0..64af2e8 100644 --- a/makima/src/daemon/api/supervisor.rs +++ b/makima/src/daemon/api/supervisor.rs @@ -17,6 +17,10 @@ pub struct SpawnTaskRequest { pub parent_task_id: Option<Uuid>, #[serde(skip_serializing_if = "Option::is_none")] pub checkpoint_sha: Option<String>, + /// Override max_iterations for this task (optional). + /// If not set, uses the daemon's configured max_iterations. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_iterations: Option<u32>, } #[derive(Serialize)] diff --git a/makima/src/daemon/cli/daemon.rs b/makima/src/daemon/cli/daemon.rs index c779d64..0dd11dc 100644 --- a/makima/src/daemon/cli/daemon.rs +++ b/makima/src/daemon/cli/daemon.rs @@ -38,4 +38,9 @@ pub struct DaemonArgs { /// Requires bwrap to be installed on the system. #[arg(long, env = "MAKIMA_DAEMON_BUBBLEWRAP")] pub bubblewrap: bool, + + /// Maximum iterations for autonomous task loops. + /// Set to 0 for unlimited (not recommended). Default: 10. + #[arg(long, env = "MAKIMA_MAX_ITERATIONS")] + pub max_iterations: Option<u32>, } diff --git a/makima/src/daemon/config.rs b/makima/src/daemon/config.rs index 476b57e..79c9341 100644 --- a/makima/src/daemon/config.rs +++ b/makima/src/daemon/config.rs @@ -218,6 +218,11 @@ pub struct ProcessConfig { /// Set to 0 to disable. Default: 300 (5 minutes). #[serde(default = "default_heartbeat_commit_interval", alias = "heartbeatcommitintervalsecs")] pub heartbeat_commit_interval_secs: u64, + + /// Maximum iterations for autonomous task loops. + /// Set to 0 for unlimited (not recommended). Default: 10. + #[serde(default = "default_max_iterations", alias = "maxiterations")] + pub max_iterations: u32, } fn default_claude_command() -> String { @@ -228,6 +233,10 @@ fn default_heartbeat_commit_interval() -> u64 { 300 // 5 minutes } +fn default_max_iterations() -> u32 { + 10 +} + fn default_max_tasks() -> u32 { 4 } @@ -245,6 +254,7 @@ impl Default for ProcessConfig { env_vars: HashMap::new(), bubblewrap: BubblewrapConfig::default(), heartbeat_commit_interval_secs: default_heartbeat_commit_interval(), + max_iterations: default_max_iterations(), } } } @@ -530,6 +540,11 @@ impl DaemonConfig { config.process.bubblewrap.enabled = true; } + // Apply max_iterations if CLI flag is set + if let Some(max_iterations) = args.max_iterations { + config.process.max_iterations = max_iterations; + } + // Validate required fields after all sources are merged config.validate()?; @@ -564,6 +579,8 @@ impl DaemonConfig { default_timeout_secs: 0, env_vars: HashMap::new(), bubblewrap: BubblewrapConfig::default(), + heartbeat_commit_interval_secs: 300, + max_iterations: 10, }, local_db: LocalDbConfig { path: PathBuf::from("/tmp/makima-daemon-test/state.db"), diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs index 555cd2a..179c07f 100644 --- a/makima/src/daemon/task/manager.rs +++ b/makima/src/daemon/task/manager.rs @@ -947,6 +947,8 @@ pub struct ManagedTask { pub contract_id: Option<Uuid>, /// Whether to run in autonomous loop mode. pub autonomous_loop: bool, + /// Override max_iterations for this task (None = use config default). + pub max_iterations: Option<u32>, /// Time task was created. pub created_at: Instant, /// Time task started running. @@ -985,6 +987,9 @@ pub struct TaskConfig { /// Interval in seconds between heartbeat commits (WIP checkpoints). /// Set to 0 to disable. Default: 300 (5 minutes). pub heartbeat_commit_interval_secs: u64, + /// Maximum iterations for autonomous task loops. + /// Set to 0 for unlimited (not recommended). Default: 10. + pub max_iterations: u32, } impl Default for TaskConfig { @@ -1002,6 +1007,7 @@ impl Default for TaskConfig { api_url: "https://api.makima.jp".to_string(), api_key: String::new(), heartbeat_commit_interval_secs: 300, // 5 minutes + max_iterations: 10, } } } @@ -1176,6 +1182,7 @@ impl TaskManager { autonomous_loop, resume_session, conversation_history, + max_iterations, } => { tracing::info!( task_id = %task_id, @@ -1194,6 +1201,7 @@ impl TaskManager { continue_from_task_id = ?continue_from_task_id, copy_files = ?copy_files, contract_id = ?contract_id, + max_iterations = ?max_iterations, plan_len = plan.len(), "Spawning new task" ); @@ -1202,7 +1210,7 @@ impl TaskManager { parent_task_id, depth, is_orchestrator, is_supervisor, target_repo_path, completion_action, continue_from_task_id, copy_files, contract_id, autonomous_loop, resume_session, - conversation_history, + conversation_history, max_iterations, ).await?; } DaemonCommand::PauseTask { task_id } => { @@ -1501,6 +1509,7 @@ impl TaskManager { autonomous_loop: bool, resume_session: bool, conversation_history: Option<serde_json::Value>, + max_iterations: Option<u32>, ) -> TaskResult<()> { tracing::info!(task_id = %task_id, is_orchestrator = is_orchestrator, is_supervisor = is_supervisor, depth = depth, "=== SPAWN_TASK START ==="); @@ -1554,6 +1563,7 @@ impl TaskManager { copy_files: copy_files.clone(), contract_id, autonomous_loop, + max_iterations, created_at: Instant::now(), started_at: None, completed_at: None, @@ -1578,7 +1588,7 @@ impl TaskManager { task_id, task_name, plan, repo_url, base_branch, target_branch, is_orchestrator, is_supervisor, target_repo_path, completion_action, continue_from_task_id, copy_files, contract_id, autonomous_loop, resume_session, - conversation_history, + conversation_history, max_iterations, ).await { tracing::error!(task_id = %task_id, error = %e, "Task execution failed"); inner.mark_failed(task_id, &e.to_string()).await; @@ -1604,6 +1614,7 @@ impl TaskManager { git_user_name: self.git_user_name.clone(), api_url: self.config.api_url.clone(), heartbeat_commit_interval_secs: self.config.heartbeat_commit_interval_secs, + max_iterations: self.config.max_iterations, } } @@ -2900,6 +2911,8 @@ struct TaskManagerInner { git_user_name: Arc<RwLock<Option<String>>>, api_url: String, heartbeat_commit_interval_secs: u64, + /// Maximum iterations for autonomous task loops (from config). + max_iterations: u32, } impl TaskManagerInner { @@ -2923,8 +2936,9 @@ impl TaskManagerInner { autonomous_loop: bool, resume_session: bool, conversation_history: Option<serde_json::Value>, + max_iterations: Option<u32>, ) -> Result<(), DaemonError> { - tracing::info!(task_id = %task_id, is_orchestrator = is_orchestrator, is_supervisor = is_supervisor, resume_session = resume_session, "=== RUN_TASK START ==="); + tracing::info!(task_id = %task_id, is_orchestrator = is_orchestrator, is_supervisor = is_supervisor, resume_session = resume_session, max_iterations = ?max_iterations, "=== RUN_TASK START ==="); // If resuming session, try to find existing worktree first let existing_worktree = if resume_session { @@ -3567,7 +3581,10 @@ impl TaskManagerInner { // For autonomous loop mode: track accumulated output for COMPLETION_GATE detection let mut accumulated_output = String::new(); - let mut circuit_breaker = CircuitBreaker::new(); + // Use task-specific max_iterations if provided, otherwise use config default + let effective_max_iterations = max_iterations.unwrap_or(self.max_iterations); + let mut circuit_breaker = CircuitBreaker::with_thresholds(3, 5, effective_max_iterations); + tracing::info!(task_id = %task_id, effective_max_iterations = effective_max_iterations, "Circuit breaker initialized"); let mut iteration_count = 0u32; let mut final_exit_code: i64 = -1; // Track the final exit code across iterations diff --git a/makima/src/daemon/ws/protocol.rs b/makima/src/daemon/ws/protocol.rs index d0bcc19..d9a91e9 100644 --- a/makima/src/daemon/ws/protocol.rs +++ b/makima/src/daemon/ws/protocol.rs @@ -387,6 +387,10 @@ pub enum DaemonCommand { /// Used to inject previous conversation context into the prompt. #[serde(rename = "conversationHistory", default)] conversation_history: Option<serde_json::Value>, + /// Override max_iterations for this task (optional). + /// If not set, uses the daemon's configured max_iterations. + #[serde(rename = "maxIterations", default)] + max_iterations: Option<u32>, }, /// Pause a running task. |
