summaryrefslogtreecommitdiff
path: root/makima/src/db
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-15 11:57:43 +0000
committersoryu <soryu@soryu.co>2026-01-15 17:12:04 +0000
commit3efdab36ca61a6795454668881d5b925abe22bd3 (patch)
tree0fd96e527f45a3da31dfc073b07cd55ba284e550 /makima/src/db
parent63b2e347b2ecadc6a48062e10e0a7e19b6102631 (diff)
downloadsoryu-3efdab36ca61a6795454668881d5b925abe22bd3.tar.gz
soryu-3efdab36ca61a6795454668881d5b925abe22bd3.zip
Fixup: Add cleanup and isolation features to makima
Add comprehensive CLI documentation - Create makima/docs/CLI.md with complete command reference for: - makima server: HTTP/WebSocket server options - makima daemon: Worker daemon configuration - makima supervisor: Contract orchestration commands - makima contract: Task-contract interaction commands - Include configuration file examples and environment variables - Add usage workflows for common scenarios - Update makima/README.md with CLI overview and link to docs Add GitHub Actions release workflow for v0.1.0 Creates automated release workflow that: - Triggers on v* tag pushes - Builds binaries for Linux x86_64, macOS x86_64, and macOS ARM64 - Uses Rust nightly toolchain (required for edition 2024) - Packages binaries as .tar.gz archives - Creates GitHub release with installation instructions fix(ci): update macOS runner for x86_64 builds Replace deprecated macos-13 runner with macos-15-intel for x86_64-apple-darwin target. The macos-13 runner has been retired by GitHub Actions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Add dismissing notifications and fix CLI task ID arg Add worktree cleanup when contracts complete or are deleted (#21) - Add CleanupWorktree daemon command variant - Handle CleanupWorktree in daemon task manager - Add cleanup_contract_worktrees helper function - Trigger cleanup when contract status becomes 'completed' - Trigger cleanup before contract deletion Add Autonomous Loop Mode for persistent task completion (#20) Implements the "Autonomous Loop Mode" feature inspired by Ralph for Claude Code. This enables tasks to automatically restart and continue working until they explicitly signal completion via a COMPLETION_GATE block. Key features: - Exit confirmation via COMPLETION_GATE: Tasks must output a <COMPLETION_GATE> block with `ready: true` to signal completion. Without this, the task auto-restarts using `claude --continue` to resume the conversation. - Circuit breaker: Prevents infinite loops by detecting: * Maximum iteration limit (default: 10) * No progress for N consecutive iterations (default: 3) * Same error repeated N times (default: 5) - spawn_continue: New ProcessManager method to spawn Claude with the `--continue` flag, resuming from the previous session state. Toggle: Enable via `autonomous_loop` flag on contracts. When set, all tasks spawned for that contract will run in autonomous loop mode. Files changed: - completion_gate.rs: COMPLETION_GATE parser and CircuitBreaker logic - claude.rs: spawn_continue() for --continue mode spawning - manager.rs: Autonomous loop iteration logic in run_task() - protocol.rs: autonomousLoop field in DaemonCommand::SpawnTask - models.rs/repository.rs: autonomous_loop column on contracts/tasks - Migration: Adds autonomous_loop columns to contracts and tasks tables Add get-task and output commands to supervisor CLI (#24) Add two new supervisor subcommands: - `makima supervisor task <task_id>` - Get individual task details - `makima supervisor output <task_id>` - Get task output/claude log This allows supervisors to fetch task details and claude output directly from the CLI instead of using curl to call the task API. Add optional bubblewrap sandboxing for Claude processes (#23) Add --bubblewrap flag and process.bubblewrap config section to enable running Claude Code in a bubblewrap sandbox for process isolation. When enabled, claude processes run with filesystem restrictions: - Root filesystem mounted read-only - Working directory (worktree) mounted read-write - Fresh /dev, /proc, /tmp - Network access preserved for API calls Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'makima/src/db')
-rw-r--r--makima/src/db/models.rs13
-rw-r--r--makima/src/db/repository.rs42
2 files changed, 50 insertions, 5 deletions
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs
index 8ab3a10..40d4109 100644
--- a/makima/src/db/models.rs
+++ b/makima/src/db/models.rs
@@ -1194,6 +1194,11 @@ pub struct Contract {
/// The long-running supervisor task that orchestrates this contract
#[serde(skip_serializing_if = "Option::is_none")]
pub supervisor_task_id: Option<Uuid>,
+ /// Whether tasks for this contract should run in autonomous loop mode.
+ /// When enabled, tasks will automatically restart with --continue if they exit
+ /// without a COMPLETION_GATE indicating ready: true.
+ #[serde(default)]
+ pub autonomous_loop: bool,
pub version: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
@@ -1314,6 +1319,11 @@ pub struct CreateContractRequest {
/// - specification: defaults to "research"
#[serde(default)]
pub initial_phase: Option<String>,
+ /// Enable autonomous loop mode for tasks in this contract.
+ /// When enabled, tasks automatically restart with --continue if they exit
+ /// without a COMPLETION_GATE indicating ready: true.
+ #[serde(default)]
+ pub autonomous_loop: Option<bool>,
}
/// Request payload for updating a contract
@@ -1327,6 +1337,9 @@ pub struct UpdateContractRequest {
/// Supervisor task ID for contract orchestration
#[serde(skip_serializing_if = "Option::is_none")]
pub supervisor_task_id: Option<Uuid>,
+ /// Enable or disable autonomous loop mode for tasks in this contract.
+ #[serde(default)]
+ pub autonomous_loop: Option<bool>,
/// Version for optimistic locking
pub version: Option<i32>,
}
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs
index 0e85be1..92b2048 100644
--- a/makima/src/db/repository.rs
+++ b/makima/src/db/repository.rs
@@ -2052,10 +2052,12 @@ pub async fn create_contract_for_owner(
)));
}
+ let autonomous_loop = req.autonomous_loop.unwrap_or(false);
+
sqlx::query_as::<_, Contract>(
r#"
- INSERT INTO contracts (owner_id, name, description, contract_type, phase)
- VALUES ($1, $2, $3, $4, $5)
+ INSERT INTO contracts (owner_id, name, description, contract_type, phase, autonomous_loop)
+ VALUES ($1, $2, $3, $4, $5, $6)
RETURNING *
"#,
)
@@ -2064,6 +2066,7 @@ pub async fn create_contract_for_owner(
.bind(&req.description)
.bind(contract_type)
.bind(phase)
+ .bind(autonomous_loop)
.fetch_one(pool)
.await
}
@@ -2162,14 +2165,15 @@ pub async fn update_contract_for_owner(
let phase = req.phase.unwrap_or(existing.phase);
let status = req.status.unwrap_or(existing.status);
let supervisor_task_id = req.supervisor_task_id.or(existing.supervisor_task_id);
+ let autonomous_loop = req.autonomous_loop.unwrap_or(existing.autonomous_loop);
let result = if req.version.is_some() {
sqlx::query_as::<_, Contract>(
r#"
UPDATE contracts
SET name = $3, description = $4, phase = $5, status = $6,
- supervisor_task_id = $7, version = version + 1, updated_at = NOW()
- WHERE id = $1 AND owner_id = $2 AND version = $8
+ supervisor_task_id = $7, autonomous_loop = $8, version = version + 1, updated_at = NOW()
+ WHERE id = $1 AND owner_id = $2 AND version = $9
RETURNING *
"#,
)
@@ -2180,6 +2184,7 @@ pub async fn update_contract_for_owner(
.bind(&phase)
.bind(&status)
.bind(supervisor_task_id)
+ .bind(autonomous_loop)
.bind(req.version.unwrap())
.fetch_optional(pool)
.await?
@@ -2188,7 +2193,7 @@ pub async fn update_contract_for_owner(
r#"
UPDATE contracts
SET name = $3, description = $4, phase = $5, status = $6,
- supervisor_task_id = $7, version = version + 1, updated_at = NOW()
+ supervisor_task_id = $7, autonomous_loop = $8, version = version + 1, updated_at = NOW()
WHERE id = $1 AND owner_id = $2
RETURNING *
"#,
@@ -2200,6 +2205,7 @@ pub async fn update_contract_for_owner(
.bind(&phase)
.bind(&status)
.bind(supervisor_task_id)
+ .bind(autonomous_loop)
.fetch_optional(pool)
.await?
};
@@ -2591,6 +2597,32 @@ pub async fn list_tasks_in_contract(
.await
}
+/// Minimal task info for worktree cleanup operations.
+#[derive(Debug, Clone, sqlx::FromRow)]
+pub struct TaskWorktreeInfo {
+ pub id: Uuid,
+ pub daemon_id: Option<Uuid>,
+ pub overlay_path: Option<String>,
+}
+
+/// List tasks in a contract with their daemon/worktree info.
+/// Used for cleaning up worktrees when a contract is completed or deleted.
+pub async fn list_contract_tasks_with_worktree_info(
+ pool: &PgPool,
+ contract_id: Uuid,
+) -> Result<Vec<TaskWorktreeInfo>, sqlx::Error> {
+ sqlx::query_as::<_, TaskWorktreeInfo>(
+ r#"
+ SELECT id, daemon_id, overlay_path
+ FROM tasks
+ WHERE contract_id = $1 AND (daemon_id IS NOT NULL OR overlay_path IS NOT NULL)
+ "#,
+ )
+ .bind(contract_id)
+ .fetch_all(pool)
+ .await
+}
+
// =============================================================================
// Contract Events
// =============================================================================