summaryrefslogtreecommitdiff
path: root/makima/docs/PLAN-resume-history-system.md
diff options
context:
space:
mode:
Diffstat (limited to 'makima/docs/PLAN-resume-history-system.md')
-rw-r--r--makima/docs/PLAN-resume-history-system.md1316
1 files changed, 1316 insertions, 0 deletions
diff --git a/makima/docs/PLAN-resume-history-system.md b/makima/docs/PLAN-resume-history-system.md
new file mode 100644
index 0000000..9e81c93
--- /dev/null
+++ b/makima/docs/PLAN-resume-history-system.md
@@ -0,0 +1,1316 @@
+# Resume and History System - Implementation Plan
+
+## Overview
+
+This document provides a detailed, actionable implementation plan for the Resume and History System. The system enables users to view historical conversation data, resume interrupted work, and rewind/restore to previous states in the Makima platform.
+
+**Key Reference Documents:**
+- Specification: Resume and History System Specification
+- Requirements: Requirements Document
+- User Stories: User Stories Document
+
+---
+
+## Phase 1: Database Schema
+
+**Objective:** Create the foundational database structures for storing conversation snapshots, history events, and supporting task forking.
+
+### Task 1.1: Create Database Migrations
+
+**Files to Create:**
+- `makima/migrations/20250117000000_history_tables.sql`
+
+**Schema Changes:**
+
+```sql
+-- 1. Conversation Snapshots table
+-- Stores conversation state at specific points for rewind capability
+CREATE TABLE IF NOT EXISTS conversation_snapshots (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
+ checkpoint_id UUID REFERENCES task_checkpoints(id) ON DELETE SET NULL,
+ snapshot_type VARCHAR(50) NOT NULL, -- 'auto', 'manual', 'checkpoint'
+ message_count INTEGER NOT NULL,
+ conversation_state JSONB NOT NULL, -- Full conversation at this point
+ metadata JSONB, -- Additional context (token count, cost, etc.)
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_conversation_snapshots_task ON conversation_snapshots(task_id);
+CREATE INDEX idx_conversation_snapshots_checkpoint ON conversation_snapshots(checkpoint_id);
+CREATE INDEX idx_conversation_snapshots_created ON conversation_snapshots(created_at DESC);
+
+-- 2. History Events table
+-- Unified event stream for timeline views
+CREATE TABLE IF NOT EXISTS history_events (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ contract_id UUID REFERENCES contracts(id) ON DELETE CASCADE,
+ task_id UUID REFERENCES tasks(id) ON DELETE CASCADE,
+ event_type VARCHAR(50) NOT NULL, -- 'task', 'chat', 'checkpoint', 'phase', 'file'
+ event_subtype VARCHAR(50), -- Specific event: 'created', 'completed', 'message', etc.
+ phase VARCHAR(50), -- Contract phase when event occurred
+ event_data JSONB NOT NULL, -- Event-specific data
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_history_events_contract ON history_events(contract_id, created_at DESC);
+CREATE INDEX idx_history_events_task ON history_events(task_id, created_at DESC);
+CREATE INDEX idx_history_events_owner ON history_events(owner_id, created_at DESC);
+CREATE INDEX idx_history_events_type ON history_events(event_type, created_at DESC);
+
+-- 3. Alter task_checkpoints - add conversation snapshot reference
+ALTER TABLE task_checkpoints
+ ADD COLUMN conversation_snapshot_id UUID REFERENCES conversation_snapshots(id) ON DELETE SET NULL;
+
+-- 4. Alter tasks - add forking fields
+ALTER TABLE tasks
+ ADD COLUMN forked_from_task_id UUID REFERENCES tasks(id) ON DELETE SET NULL,
+ ADD COLUMN forked_at_checkpoint_id UUID REFERENCES task_checkpoints(id) ON DELETE SET NULL;
+
+CREATE INDEX idx_tasks_forked_from ON tasks(forked_from_task_id) WHERE forked_from_task_id IS NOT NULL;
+
+-- Comments for documentation
+COMMENT ON TABLE conversation_snapshots IS 'Stores conversation state at specific points for rewind/resume capability';
+COMMENT ON TABLE history_events IS 'Unified event stream for timeline views across contracts and tasks';
+COMMENT ON COLUMN conversation_snapshots.snapshot_type IS 'Type: auto (periodic), manual (user-triggered), checkpoint (at git checkpoint)';
+COMMENT ON COLUMN history_events.event_type IS 'Category: task, chat, checkpoint, phase, file';
+```
+
+**Complexity:** Medium
+**Dependencies:** None
+**Estimated Time:** 2-3 hours
+
+---
+
+## Phase 2: Repository Layer
+
+**Objective:** Implement database access functions for conversation snapshots, history events, and enhanced checkpoint operations.
+
+### Task 2.1: Core Models
+
+**Files to Modify:**
+- `makima/src/db/models.rs`
+
+**New Types to Add:**
+
+```rust
+// ConversationSnapshot model
+#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ConversationSnapshot {
+ pub id: Uuid,
+ pub task_id: Uuid,
+ pub checkpoint_id: Option<Uuid>,
+ pub snapshot_type: String, // 'auto', 'manual', 'checkpoint'
+ pub message_count: i32,
+ #[sqlx(json)]
+ pub conversation_state: serde_json::Value,
+ #[sqlx(json)]
+ pub metadata: Option<serde_json::Value>,
+ pub created_at: DateTime<Utc>,
+}
+
+// HistoryEvent model
+#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct HistoryEvent {
+ pub id: Uuid,
+ pub owner_id: Uuid,
+ pub contract_id: Option<Uuid>,
+ pub task_id: Option<Uuid>,
+ pub event_type: String,
+ pub event_subtype: Option<String>,
+ pub phase: Option<String>,
+ #[sqlx(json)]
+ pub event_data: serde_json::Value,
+ pub created_at: DateTime<Utc>,
+}
+
+// Unified ConversationMessage for API responses
+#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ConversationMessage {
+ pub id: String,
+ pub role: String, // 'user', 'assistant', 'system', 'tool'
+ pub content: String,
+ pub timestamp: DateTime<Utc>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub tool_calls: Option<Vec<ToolCallInfo>>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub tool_name: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub tool_input: Option<serde_json::Value>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub tool_result: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub is_error: Option<bool>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub token_count: Option<i32>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub cost_usd: Option<f64>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ToolCallInfo {
+ pub id: String,
+ pub name: String,
+ pub input: serde_json::Value,
+}
+
+// Query filters for history
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct HistoryQueryFilters {
+ pub phase: Option<String>,
+ pub event_types: Option<Vec<String>>,
+ pub from: Option<DateTime<Utc>>,
+ pub to: Option<DateTime<Utc>>,
+ pub limit: Option<i32>,
+ pub cursor: Option<String>,
+}
+
+// Resume request types
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ResumeSupervisorRequest {
+ pub target_daemon_id: Option<Uuid>,
+ pub resume_mode: String, // 'continue', 'restart_phase', 'from_checkpoint'
+ pub checkpoint_id: Option<Uuid>,
+ pub additional_context: Option<String>,
+}
+
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ResumeFromCheckpointRequest {
+ pub task_name: Option<String>,
+ pub plan: String,
+ pub include_conversation: Option<bool>,
+ pub target_daemon_id: Option<Uuid>,
+}
+
+// Rewind request types
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct RewindTaskRequest {
+ pub checkpoint_id: Option<Uuid>,
+ pub checkpoint_sha: Option<String>,
+ pub preserve_mode: String, // 'discard', 'create_branch', 'stash'
+ pub branch_name: Option<String>,
+}
+
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct RewindConversationRequest {
+ pub to_message_id: Option<String>,
+ pub to_timestamp: Option<DateTime<Utc>>,
+ pub by_message_count: Option<i32>,
+ pub rewind_code: Option<bool>,
+}
+
+// Fork request type
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct ForkTaskRequest {
+ pub fork_from_type: String, // 'checkpoint', 'timestamp', 'message_id'
+ pub fork_from_value: String,
+ pub new_task_name: String,
+ pub new_task_plan: String,
+ pub include_conversation: Option<bool>,
+ pub create_branch: Option<bool>,
+ pub branch_name: Option<String>,
+}
+```
+
+**Complexity:** Medium
+**Dependencies:** Phase 1 complete
+**Estimated Time:** 2-3 hours
+
+### Task 2.2: Repository Functions
+
+**Files to Modify:**
+- `makima/src/db/repository.rs`
+
+**Functions to Add:**
+
+```rust
+// ============================================================================
+// Conversation Snapshots
+// ============================================================================
+
+/// Create a new conversation snapshot
+pub async fn create_conversation_snapshot(
+ pool: &PgPool,
+ task_id: Uuid,
+ checkpoint_id: Option<Uuid>,
+ snapshot_type: &str,
+ message_count: i32,
+ conversation_state: serde_json::Value,
+ metadata: Option<serde_json::Value>,
+) -> Result<ConversationSnapshot, sqlx::Error>
+
+/// Get a conversation snapshot by ID
+pub async fn get_conversation_snapshot(
+ pool: &PgPool,
+ id: Uuid,
+) -> Result<Option<ConversationSnapshot>, sqlx::Error>
+
+/// Get conversation snapshot at a specific checkpoint
+pub async fn get_conversation_at_checkpoint(
+ pool: &PgPool,
+ checkpoint_id: Uuid,
+) -> Result<Option<ConversationSnapshot>, sqlx::Error>
+
+/// List conversation snapshots for a task
+pub async fn list_conversation_snapshots(
+ pool: &PgPool,
+ task_id: Uuid,
+ limit: Option<i32>,
+) -> Result<Vec<ConversationSnapshot>, sqlx::Error>
+
+/// Delete conversation snapshots older than retention period
+pub async fn cleanup_old_snapshots(
+ pool: &PgPool,
+ retention_days: i32,
+) -> Result<u64, sqlx::Error>
+
+// ============================================================================
+// History Events
+// ============================================================================
+
+/// Record a new history event
+pub async fn record_history_event(
+ pool: &PgPool,
+ owner_id: Uuid,
+ contract_id: Option<Uuid>,
+ task_id: Option<Uuid>,
+ event_type: &str,
+ event_subtype: Option<&str>,
+ phase: Option<&str>,
+ event_data: serde_json::Value,
+) -> Result<HistoryEvent, sqlx::Error>
+
+/// Get contract history timeline
+pub async fn get_contract_history(
+ pool: &PgPool,
+ contract_id: Uuid,
+ owner_id: Uuid,
+ filters: &HistoryQueryFilters,
+) -> Result<(Vec<HistoryEvent>, i64), sqlx::Error>
+
+/// Get task history
+pub async fn get_task_history(
+ pool: &PgPool,
+ task_id: Uuid,
+ owner_id: Uuid,
+ filters: &HistoryQueryFilters,
+) -> Result<(Vec<HistoryEvent>, i64), sqlx::Error>
+
+/// Get unified timeline for an owner
+pub async fn get_timeline(
+ pool: &PgPool,
+ owner_id: Uuid,
+ filters: &HistoryQueryFilters,
+) -> Result<(Vec<HistoryEvent>, i64), sqlx::Error>
+
+// ============================================================================
+// Task Conversation Retrieval
+// ============================================================================
+
+/// Get task conversation messages (reconstructed from task_events)
+pub async fn get_task_conversation(
+ pool: &PgPool,
+ task_id: Uuid,
+ include_tool_calls: bool,
+ include_tool_results: bool,
+ limit: Option<i32>,
+) -> Result<Vec<ConversationMessage>, sqlx::Error>
+
+/// Get supervisor conversation (from supervisor_states)
+pub async fn get_supervisor_conversation(
+ pool: &PgPool,
+ contract_id: Uuid,
+) -> Result<Option<(SupervisorState, Vec<TaskSummary>)>, sqlx::Error>
+
+// ============================================================================
+// Checkpoint Operations
+// ============================================================================
+
+/// Create checkpoint with conversation snapshot
+pub async fn create_checkpoint_with_snapshot(
+ pool: &PgPool,
+ task_id: Uuid,
+ checkpoint_number: i32,
+ commit_sha: &str,
+ branch_name: &str,
+ message: &str,
+ files_changed: Option<serde_json::Value>,
+ lines_added: Option<i32>,
+ lines_removed: Option<i32>,
+ conversation_state: serde_json::Value,
+) -> Result<(TaskCheckpoint, ConversationSnapshot), sqlx::Error>
+
+/// Get checkpoint diff information (requires daemon interaction for actual diff)
+pub async fn get_checkpoint_info(
+ pool: &PgPool,
+ checkpoint_id: Uuid,
+) -> Result<Option<(TaskCheckpoint, Option<TaskCheckpoint>)>, sqlx::Error>
+
+// ============================================================================
+// Fork Operations
+// ============================================================================
+
+/// Create forked task
+pub async fn create_forked_task(
+ pool: &PgPool,
+ owner_id: Uuid,
+ source_task_id: Uuid,
+ checkpoint_id: Option<Uuid>,
+ req: &CreateTaskRequest,
+) -> Result<Task, sqlx::Error>
+```
+
+**Complexity:** Complex
+**Dependencies:** Task 2.1 complete
+**Estimated Time:** 4-5 hours
+
+---
+
+## Phase 3: API Endpoints
+
+**Objective:** Implement REST API endpoints for history viewing, resume operations, and rewind/fork functionality.
+
+### Task 3.1: History Endpoints
+
+**Files to Create/Modify:**
+- `makima/src/server/handlers/history.rs` (new file)
+- `makima/src/server/handlers/mod.rs` (add module)
+- `makima/src/server/mod.rs` (add routes)
+
+**Endpoints to Implement:**
+
+| Method | Path | Handler Function |
+|--------|------|------------------|
+| GET | `/api/v1/contracts/{id}/history` | `get_contract_history` |
+| GET | `/api/v1/contracts/{id}/supervisor/conversation` | `get_supervisor_conversation` |
+| GET | `/api/v1/mesh/tasks/{id}/conversation` | `get_task_conversation` |
+| GET | `/api/v1/mesh/tasks/{id}/checkpoints/{cid}/diff` | `get_checkpoint_diff` |
+| GET | `/api/v1/timeline` | `get_timeline` |
+
+**Implementation Details:**
+
+```rust
+// makima/src/server/handlers/history.rs
+
+/// GET /api/v1/contracts/{id}/history
+/// Returns contract history timeline with filtering and pagination
+#[utoipa::path(
+ get,
+ path = "/api/v1/contracts/{id}/history",
+ params(
+ ("id" = Uuid, Path, description = "Contract ID"),
+ HistoryQueryFilters
+ ),
+ responses(
+ (status = 200, body = ContractHistoryResponse),
+ (status = 404, description = "Contract not found"),
+ ),
+ tag = "history"
+)]
+pub async fn get_contract_history(
+ State(state): State<AppState>,
+ Path(contract_id): Path<Uuid>,
+ Query(filters): Query<HistoryQueryFilters>,
+ auth: AuthenticatedUser,
+) -> Result<Json<ContractHistoryResponse>, ApiError>
+
+/// GET /api/v1/contracts/{id}/supervisor/conversation
+/// Returns full supervisor conversation with spawned task references
+#[utoipa::path(
+ get,
+ path = "/api/v1/contracts/{id}/supervisor/conversation",
+ responses(
+ (status = 200, body = SupervisorConversationResponse),
+ (status = 404, description = "Supervisor not found"),
+ ),
+ tag = "history"
+)]
+pub async fn get_supervisor_conversation(
+ State(state): State<AppState>,
+ Path(contract_id): Path<Uuid>,
+ auth: AuthenticatedUser,
+) -> Result<Json<SupervisorConversationResponse>, ApiError>
+
+/// GET /api/v1/mesh/tasks/{id}/conversation
+/// Returns task conversation history
+#[utoipa::path(
+ get,
+ path = "/api/v1/mesh/tasks/{id}/conversation",
+ params(
+ ("id" = Uuid, Path, description = "Task ID"),
+ ("include_tool_calls" = Option<bool>, Query),
+ ("include_tool_results" = Option<bool>, Query),
+ ("limit" = Option<i32>, Query),
+ ),
+ responses(
+ (status = 200, body = TaskConversationResponse),
+ (status = 404, description = "Task not found"),
+ ),
+ tag = "history"
+)]
+pub async fn get_task_conversation(
+ State(state): State<AppState>,
+ Path(task_id): Path<Uuid>,
+ Query(params): Query<TaskConversationParams>,
+ auth: AuthenticatedUser,
+) -> Result<Json<TaskConversationResponse>, ApiError>
+
+/// GET /api/v1/mesh/tasks/{id}/checkpoints/{cid}/diff
+/// Returns checkpoint diff (delegates to daemon for git diff)
+pub async fn get_checkpoint_diff(
+ State(state): State<AppState>,
+ Path((task_id, checkpoint_id)): Path<(Uuid, Uuid)>,
+ auth: AuthenticatedUser,
+) -> Result<Json<CheckpointDiffResponse>, ApiError>
+
+/// GET /api/v1/timeline
+/// Returns unified timeline for authenticated user
+pub async fn get_timeline(
+ State(state): State<AppState>,
+ Query(filters): Query<TimelineQueryFilters>,
+ auth: AuthenticatedUser,
+) -> Result<Json<TimelineResponse>, ApiError>
+```
+
+**Complexity:** Medium
+**Dependencies:** Phase 2 complete
+**Estimated Time:** 4-5 hours
+
+### Task 3.2: Resume Endpoints
+
+**Files to Modify:**
+- `makima/src/server/handlers/mesh_supervisor.rs`
+- `makima/src/server/handlers/mesh.rs`
+
+**Endpoints to Implement:**
+
+| Method | Path | Handler Function |
+|--------|------|------------------|
+| POST | `/api/v1/contracts/{id}/supervisor/resume` | `resume_supervisor` |
+| POST | `/api/v1/mesh/tasks/{id}/checkpoints/{cid}/resume` | `resume_from_checkpoint` |
+| POST | `/api/v1/mesh/tasks/{id}/continue` | `continue_task` (enhanced) |
+
+**Implementation Details:**
+
+```rust
+// makima/src/server/handlers/mesh_supervisor.rs
+
+/// POST /api/v1/contracts/{id}/supervisor/resume
+/// Resume interrupted supervisor with specified mode
+#[utoipa::path(
+ post,
+ path = "/api/v1/contracts/{id}/supervisor/resume",
+ request_body = ResumeSupervisorRequest,
+ responses(
+ (status = 200, body = ResumeSupervisorResponse),
+ (status = 404, description = "Contract/supervisor not found"),
+ (status = 409, description = "Supervisor already running"),
+ ),
+ tag = "supervisor"
+)]
+pub async fn resume_supervisor(
+ State(state): State<AppState>,
+ Path(contract_id): Path<Uuid>,
+ auth: AuthenticatedUser,
+ Json(req): Json<ResumeSupervisorRequest>,
+) -> Result<Json<ResumeSupervisorResponse>, ApiError>
+
+// makima/src/server/handlers/mesh.rs
+
+/// POST /api/v1/mesh/tasks/{id}/checkpoints/{cid}/resume
+/// Create new task starting from specific checkpoint
+#[utoipa::path(
+ post,
+ path = "/api/v1/mesh/tasks/{id}/checkpoints/{cid}/resume",
+ request_body = ResumeFromCheckpointRequest,
+ responses(
+ (status = 201, body = ResumeFromCheckpointResponse),
+ (status = 404, description = "Task/checkpoint not found"),
+ ),
+ tag = "mesh"
+)]
+pub async fn resume_from_checkpoint(
+ State(state): State<AppState>,
+ Path((task_id, checkpoint_id)): Path<(Uuid, Uuid)>,
+ auth: AuthenticatedUser,
+ Json(req): Json<ResumeFromCheckpointRequest>,
+) -> Result<Json<ResumeFromCheckpointResponse>, ApiError>
+
+/// POST /api/v1/mesh/tasks/{id}/continue (enhanced)
+/// Enhanced with resume mode and context options
+pub async fn continue_task(
+ State(state): State<AppState>,
+ Path(task_id): Path<Uuid>,
+ auth: AuthenticatedUser,
+ Json(req): Json<ContinueTaskRequest>, // Enhanced request type
+) -> Result<Json<Task>, ApiError>
+```
+
+**Complexity:** Complex
+**Dependencies:** Task 3.1 complete
+**Estimated Time:** 5-6 hours
+
+### Task 3.3: Rewind and Fork Endpoints
+
+**Files to Modify:**
+- `makima/src/server/handlers/mesh.rs`
+- `makima/src/server/handlers/mesh_supervisor.rs`
+
+**Endpoints to Implement:**
+
+| Method | Path | Handler Function |
+|--------|------|------------------|
+| POST | `/api/v1/mesh/tasks/{id}/rewind` | `rewind_task` |
+| POST | `/api/v1/contracts/{id}/supervisor/conversation/rewind` | `rewind_conversation` |
+| POST | `/api/v1/mesh/tasks/{id}/fork` | `fork_task` |
+| POST | `/api/v1/mesh/tasks/{id}/checkpoints/{cid}/branch` | `branch_from_checkpoint` |
+
+**Implementation Details:**
+
+```rust
+// makima/src/server/handlers/mesh.rs
+
+/// POST /api/v1/mesh/tasks/{id}/rewind
+/// Rewind task code to specified checkpoint
+#[utoipa::path(
+ post,
+ path = "/api/v1/mesh/tasks/{id}/rewind",
+ request_body = RewindTaskRequest,
+ responses(
+ (status = 200, body = RewindTaskResponse),
+ (status = 404, description = "Task/checkpoint not found"),
+ (status = 409, description = "Task is running"),
+ ),
+ tag = "mesh"
+)]
+pub async fn rewind_task(
+ State(state): State<AppState>,
+ Path(task_id): Path<Uuid>,
+ auth: AuthenticatedUser,
+ Json(req): Json<RewindTaskRequest>,
+) -> Result<Json<RewindTaskResponse>, ApiError>
+
+/// POST /api/v1/mesh/tasks/{id}/fork
+/// Fork task from historical point
+#[utoipa::path(
+ post,
+ path = "/api/v1/mesh/tasks/{id}/fork",
+ request_body = ForkTaskRequest,
+ responses(
+ (status = 201, body = ForkTaskResponse),
+ (status = 404, description = "Task not found"),
+ ),
+ tag = "mesh"
+)]
+pub async fn fork_task(
+ State(state): State<AppState>,
+ Path(task_id): Path<Uuid>,
+ auth: AuthenticatedUser,
+ Json(req): Json<ForkTaskRequest>,
+) -> Result<Json<ForkTaskResponse>, ApiError>
+
+/// POST /api/v1/mesh/tasks/{id}/checkpoints/{cid}/branch
+/// Create git branch from checkpoint without starting task
+pub async fn branch_from_checkpoint(
+ State(state): State<AppState>,
+ Path((task_id, checkpoint_id)): Path<(Uuid, Uuid)>,
+ auth: AuthenticatedUser,
+ Json(req): Json<CreateBranchFromCheckpointRequest>,
+) -> Result<Json<BranchCreatedResponse>, ApiError>
+
+// makima/src/server/handlers/mesh_supervisor.rs
+
+/// POST /api/v1/contracts/{id}/supervisor/conversation/rewind
+/// Rewind supervisor conversation to specified point
+pub async fn rewind_conversation(
+ State(state): State<AppState>,
+ Path(contract_id): Path<Uuid>,
+ auth: AuthenticatedUser,
+ Json(req): Json<RewindConversationRequest>,
+) -> Result<Json<RewindConversationResponse>, ApiError>
+```
+
+**Complexity:** Complex
+**Dependencies:** Task 3.2 complete
+**Estimated Time:** 5-6 hours
+
+---
+
+## Phase 4: CLI Commands
+
+**Objective:** Implement command-line interface for history viewing, resume, and rewind operations.
+
+### Task 4.1: History Commands
+
+**Files to Modify:**
+- `makima/src/daemon/cli/mod.rs`
+- `makima/src/daemon/cli/contract.rs`
+- `makima/src/daemon/cli/supervisor.rs`
+
+**New Commands:**
+
+```rust
+// makima/src/daemon/cli/mod.rs
+
+/// Contract subcommands - add history commands
+#[derive(Subcommand, Debug)]
+pub enum ContractCommand {
+ // ... existing commands ...
+
+ /// View contract history timeline
+ History(contract::HistoryArgs),
+}
+
+/// Supervisor subcommands - add history and resume commands
+#[derive(Subcommand, Debug)]
+pub enum SupervisorCommand {
+ // ... existing commands ...
+
+ /// View task conversation history
+ TaskHistory(supervisor::TaskHistoryArgs),
+
+ /// List checkpoints with details
+ CheckpointList(supervisor::CheckpointListArgs),
+
+ /// View checkpoint diff
+ CheckpointDiff(supervisor::CheckpointDiffArgs),
+
+ /// Resume supervisor after interruption
+ Resume(supervisor::ResumeArgs),
+}
+```
+
+**Implementation - Contract History:**
+
+```rust
+// makima/src/daemon/cli/contract.rs
+
+#[derive(Args, Debug)]
+pub struct HistoryArgs {
+ /// Filter by phase (research, specify, plan, execute, review)
+ #[arg(long)]
+ pub phase: Option<String>,
+
+ /// Filter from date (ISO 8601 format)
+ #[arg(long)]
+ pub from: Option<String>,
+
+ /// Filter to date (ISO 8601 format)
+ #[arg(long)]
+ pub to: Option<String>,
+
+ /// Maximum entries to return
+ #[arg(long, default_value = "50")]
+ pub limit: i32,
+
+ /// Output format (table, json)
+ #[arg(long, default_value = "table")]
+ pub format: String,
+}
+
+pub async fn handle_history(args: &HistoryArgs) -> Result<()> {
+ // 1. Get contract context from environment
+ // 2. Call /api/v1/contracts/{id}/history with filters
+ // 3. Format and display results
+}
+```
+
+**Implementation - Task History:**
+
+```rust
+// makima/src/daemon/cli/supervisor.rs
+
+#[derive(Args, Debug)]
+pub struct TaskHistoryArgs {
+ /// Task ID to view history for
+ pub task_id: Uuid,
+
+ /// Include tool calls in output
+ #[arg(long, default_value = "true")]
+ pub tool_calls: bool,
+
+ /// Maximum messages to return
+ #[arg(long)]
+ pub limit: Option<i32>,
+
+ /// Output format (table, json, chat)
+ #[arg(long, default_value = "chat")]
+ pub format: String,
+}
+
+pub async fn handle_task_history(args: &TaskHistoryArgs) -> Result<()> {
+ // 1. Call /api/v1/mesh/tasks/{id}/conversation
+ // 2. Format as chat-style output or JSON
+}
+
+#[derive(Args, Debug)]
+pub struct CheckpointListArgs {
+ /// Task ID to list checkpoints for
+ pub task_id: Uuid,
+
+ /// Include diff summary
+ #[arg(long)]
+ pub with_diff: bool,
+}
+
+#[derive(Args, Debug)]
+pub struct CheckpointDiffArgs {
+ /// Task ID
+ pub task_id: Uuid,
+
+ /// Checkpoint number
+ pub checkpoint_number: i32,
+}
+```
+
+**Complexity:** Medium
+**Dependencies:** Phase 3 complete
+**Estimated Time:** 3-4 hours
+
+### Task 4.2: Resume and Rewind Commands
+
+**Files to Modify:**
+- `makima/src/daemon/cli/supervisor.rs`
+- `makima/src/daemon/cli/contract.rs`
+
+**New Commands:**
+
+```rust
+// makima/src/daemon/cli/supervisor.rs
+
+#[derive(Args, Debug)]
+pub struct ResumeArgs {
+ /// Resume mode: continue, restart_phase, from_checkpoint
+ #[arg(long, default_value = "continue")]
+ pub mode: String,
+
+ /// Checkpoint ID (required for from_checkpoint mode)
+ #[arg(long)]
+ pub checkpoint: Option<Uuid>,
+
+ /// Additional context to inject
+ #[arg(long)]
+ pub context: Option<String>,
+}
+
+pub async fn handle_resume(args: &ResumeArgs) -> Result<()> {
+ // 1. Get contract context
+ // 2. Call /api/v1/contracts/{id}/supervisor/resume
+ // 3. Display result and new supervisor task info
+}
+
+#[derive(Args, Debug)]
+pub struct TaskResumeArgs {
+ /// Task ID to resume
+ pub task_id: Uuid,
+
+ /// Resume mode: with_context, clean_restart, from_checkpoint
+ #[arg(long, default_value = "with_context")]
+ pub mode: String,
+
+ /// Checkpoint SHA (for from_checkpoint mode)
+ #[arg(long)]
+ pub checkpoint: Option<String>,
+}
+
+#[derive(Args, Debug)]
+pub struct TaskResumeFromArgs {
+ /// Source task ID
+ pub task_id: Uuid,
+
+ /// Checkpoint number to resume from
+ #[arg(long)]
+ pub checkpoint: i32,
+
+ /// Plan for the new task
+ #[arg(long)]
+ pub plan: String,
+
+ /// Name for the new task
+ #[arg(long)]
+ pub name: Option<String>,
+}
+
+#[derive(Args, Debug)]
+pub struct TaskRewindArgs {
+ /// Task ID to rewind
+ pub task_id: Uuid,
+
+ /// Checkpoint number to rewind to
+ #[arg(long)]
+ pub checkpoint: i32,
+
+ /// Preserve mode: discard, create_branch, stash
+ #[arg(long, default_value = "create_branch")]
+ pub preserve: String,
+
+ /// Branch name (for create_branch mode)
+ #[arg(long)]
+ pub branch_name: Option<String>,
+}
+
+#[derive(Args, Debug)]
+pub struct TaskForkArgs {
+ /// Source task ID
+ pub task_id: Uuid,
+
+ /// Checkpoint number to fork from
+ #[arg(long)]
+ pub checkpoint: i32,
+
+ /// Name for the new task
+ #[arg(long)]
+ pub name: String,
+
+ /// Plan for the new task
+ #[arg(long)]
+ pub plan: String,
+
+ /// Include conversation history
+ #[arg(long, default_value = "true")]
+ pub include_conversation: bool,
+}
+
+#[derive(Args, Debug)]
+pub struct ConversationRewindArgs {
+ /// Number of messages to rewind
+ #[arg(long)]
+ pub by_messages: Option<i32>,
+
+ /// Message ID to rewind to
+ #[arg(long)]
+ pub to_message: Option<String>,
+
+ /// Also rewind code to matching checkpoint
+ #[arg(long)]
+ pub rewind_code: bool,
+}
+```
+
+**Update CLI Commands enum:**
+
+```rust
+// makima/src/daemon/cli/mod.rs
+
+#[derive(Subcommand, Debug)]
+pub enum SupervisorCommand {
+ // ... existing commands ...
+
+ /// Resume supervisor after interruption
+ Resume(supervisor::ResumeArgs),
+
+ /// Resume task with context
+ TaskResume(supervisor::TaskResumeArgs),
+
+ /// Resume from specific checkpoint
+ TaskResumeFrom(supervisor::TaskResumeFromArgs),
+
+ /// Rewind task code to checkpoint
+ TaskRewind(supervisor::TaskRewindArgs),
+
+ /// Fork task from historical point
+ TaskFork(supervisor::TaskForkArgs),
+
+ /// Rewind supervisor conversation
+ RewindConversation(supervisor::ConversationRewindArgs),
+}
+```
+
+**Complexity:** Medium
+**Dependencies:** Task 4.1 complete
+**Estimated Time:** 4-5 hours
+
+---
+
+## Phase 5: Daemon Integration
+
+**Objective:** Extend daemon protocol and handlers to support rewind operations and conversation snapshots.
+
+### Task 5.1: Protocol Extensions
+
+**Files to Modify:**
+- `makima/src/daemon/ws/protocol.rs`
+- `makima/src/server/protocol.rs` (if separate)
+
+**New Commands to Add:**
+
+```rust
+// makima/src/daemon/ws/protocol.rs
+
+/// Command from server to daemon
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(tag = "type", rename_all = "camelCase")]
+pub enum DaemonCommand {
+ // ... existing commands ...
+
+ /// Rewind task worktree to a specific checkpoint
+ RewindToCheckpoint {
+ #[serde(rename = "taskId")]
+ task_id: Uuid,
+ #[serde(rename = "checkpointSha")]
+ checkpoint_sha: String,
+ /// How to preserve current state: 'discard', 'create_branch', 'stash'
+ #[serde(rename = "preserveMode")]
+ preserve_mode: String,
+ /// Branch name for create_branch mode
+ #[serde(rename = "branchName")]
+ branch_name: Option<String>,
+ },
+
+ /// Create a conversation snapshot for a task
+ CreateConversationSnapshot {
+ #[serde(rename = "taskId")]
+ task_id: Uuid,
+ },
+
+ /// Get git diff between two commits
+ GetCheckpointDiff {
+ #[serde(rename = "taskId")]
+ task_id: Uuid,
+ /// SHA of the checkpoint to diff
+ #[serde(rename = "checkpointSha")]
+ checkpoint_sha: String,
+ /// SHA of the previous checkpoint (for comparison)
+ #[serde(rename = "previousSha")]
+ previous_sha: Option<String>,
+ },
+}
+
+/// Message from daemon to server
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(tag = "type", rename_all = "camelCase")]
+pub enum DaemonMessage {
+ // ... existing messages ...
+
+ /// Response to RewindToCheckpoint command
+ RewindResult {
+ #[serde(rename = "taskId")]
+ task_id: Uuid,
+ success: bool,
+ message: String,
+ /// Reference to preserved state (branch name or stash ref)
+ #[serde(rename = "preservedAs")]
+ preserved_as: Option<PreservedState>,
+ },
+
+ /// Response to CreateConversationSnapshot command
+ ConversationSnapshotCreated {
+ #[serde(rename = "taskId")]
+ task_id: Uuid,
+ #[serde(rename = "snapshotId")]
+ snapshot_id: Uuid,
+ #[serde(rename = "messageCount")]
+ message_count: i32,
+ },
+
+ /// Response to GetCheckpointDiff command
+ CheckpointDiffResult {
+ #[serde(rename = "taskId")]
+ task_id: Uuid,
+ success: bool,
+ diff: Option<GitDiffInfo>,
+ error: Option<String>,
+ },
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PreservedState {
+ /// Type: 'branch' or 'stash'
+ #[serde(rename = "type")]
+ pub state_type: String,
+ /// Branch name or stash reference
+ pub reference: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct GitDiffInfo {
+ pub files: Vec<FileDiffInfo>,
+ pub stats: DiffStats,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct FileDiffInfo {
+ pub path: String,
+ /// Action: 'added', 'modified', 'deleted'
+ pub action: String,
+ pub additions: i32,
+ pub deletions: i32,
+ /// Diff hunks (optional, may be truncated for large files)
+ pub hunks: Option<Vec<DiffHunk>>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DiffHunk {
+ pub header: String,
+ pub lines: Vec<String>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DiffStats {
+ pub files_changed: i32,
+ pub insertions: i32,
+ pub deletions: i32,
+}
+```
+
+**Complexity:** Medium
+**Dependencies:** None (can be done in parallel with Phase 3-4)
+**Estimated Time:** 2-3 hours
+
+### Task 5.2: Daemon Handlers
+
+**Files to Modify:**
+- `makima/src/daemon/task/manager.rs`
+- `makima/src/daemon/worktree/manager.rs`
+
+**Handlers to Implement:**
+
+```rust
+// makima/src/daemon/task/manager.rs
+
+impl TaskManager {
+ /// Handle rewind to checkpoint command
+ pub async fn handle_rewind_to_checkpoint(
+ &mut self,
+ task_id: Uuid,
+ checkpoint_sha: &str,
+ preserve_mode: &str,
+ branch_name: Option<&str>,
+ ) -> Result<RewindResult, DaemonError> {
+ // 1. Validate task exists and is not running
+ // 2. Get worktree path for task
+ // 3. Based on preserve_mode:
+ // - 'discard': git reset --hard {sha}
+ // - 'create_branch': git branch {name} && git reset --hard {sha}
+ // - 'stash': git stash && git reset --hard {sha}
+ // 4. Return result with preserved reference
+ }
+
+ /// Handle conversation snapshot creation
+ pub async fn handle_create_conversation_snapshot(
+ &mut self,
+ task_id: Uuid,
+ ) -> Result<SnapshotResult, DaemonError> {
+ // 1. Get task state including conversation
+ // 2. Send to server for storage
+ // 3. Return snapshot ID
+ }
+
+ /// Handle checkpoint diff request
+ pub async fn handle_get_checkpoint_diff(
+ &mut self,
+ task_id: Uuid,
+ checkpoint_sha: &str,
+ previous_sha: Option<&str>,
+ ) -> Result<GitDiffInfo, DaemonError> {
+ // 1. Get worktree path for task
+ // 2. Run git diff command
+ // 3. Parse and return diff info
+ }
+}
+```
+
+**Files to Modify:**
+- `makima/src/daemon/worktree/manager.rs`
+
+**Helper Functions:**
+
+```rust
+// makima/src/daemon/worktree/manager.rs
+
+impl WorktreeManager {
+ /// Reset worktree to specific commit
+ pub async fn reset_to_commit(
+ &self,
+ worktree_path: &Path,
+ commit_sha: &str,
+ ) -> Result<(), WorktreeError>
+
+ /// Create branch from current state
+ pub async fn create_branch(
+ &self,
+ worktree_path: &Path,
+ branch_name: &str,
+ ) -> Result<(), WorktreeError>
+
+ /// Stash current changes
+ pub async fn stash_changes(
+ &self,
+ worktree_path: &Path,
+ ) -> Result<String, WorktreeError> // Returns stash ref
+
+ /// Get diff between two commits
+ pub async fn get_diff(
+ &self,
+ worktree_path: &Path,
+ from_sha: &str,
+ to_sha: &str,
+ ) -> Result<String, WorktreeError>
+
+ /// Parse git diff output into structured format
+ pub fn parse_diff(diff_output: &str) -> Result<GitDiffInfo, ParseError>
+}
+```
+
+**Complexity:** Complex
+**Dependencies:** Task 5.1 complete
+**Estimated Time:** 4-5 hours
+
+### Task 5.3: Checkpoint Enhancement
+
+**Files to Modify:**
+- `makima/src/daemon/task/manager.rs`
+- `makima/src/server/handlers/mesh_daemon.rs`
+
+**Changes:**
+
+1. **Update checkpoint creation to include conversation state:**
+
+```rust
+// When creating a checkpoint, also capture current conversation
+pub async fn create_checkpoint_with_conversation(
+ &mut self,
+ task_id: Uuid,
+ message: &str,
+) -> Result<(TaskCheckpoint, ConversationSnapshot), DaemonError> {
+ // 1. Create git commit (existing logic)
+ // 2. Capture current conversation state
+ // 3. Create checkpoint record with snapshot reference
+}
+```
+
+2. **Update task spawning to handle fork/resume scenarios:**
+
+```rust
+// When spawning task with forked_from_task_id or checkpoint_sha:
+// - If checkpoint_sha: create worktree at that specific commit
+// - If include_conversation: inject conversation history into plan
+```
+
+**Complexity:** Medium
+**Dependencies:** Task 5.2 complete
+**Estimated Time:** 3-4 hours
+
+---
+
+## Implementation Summary
+
+### Phase Dependencies
+
+```
+Phase 1 (Database)
+ └─> Phase 2 (Repository)
+ └─> Phase 3 (API Endpoints)
+ └─> Phase 4 (CLI Commands)
+
+Phase 5 (Daemon) can be done in parallel with Phases 3-4
+```
+
+### Estimated Total Time
+
+| Phase | Tasks | Estimated Hours |
+|-------|-------|-----------------|
+| Phase 1: Database Schema | 1 task | 2-3 hours |
+| Phase 2: Repository Layer | 2 tasks | 6-8 hours |
+| Phase 3: API Endpoints | 3 tasks | 14-17 hours |
+| Phase 4: CLI Commands | 2 tasks | 7-9 hours |
+| Phase 5: Daemon Integration | 3 tasks | 9-12 hours |
+| **Total** | **11 tasks** | **38-49 hours** |
+
+### Task Breakdown by Complexity
+
+| Complexity | Count | Tasks |
+|------------|-------|-------|
+| Simple | 1 | Phase 1 Migration |
+| Medium | 6 | Models, History Endpoints, CLI History, CLI Resume, Protocol, Checkpoint Enhancement |
+| Complex | 4 | Repository Functions, Resume Endpoints, Rewind/Fork Endpoints, Daemon Handlers |
+
+### Files to Create
+
+1. `makima/migrations/20250117000000_history_tables.sql`
+2. `makima/src/server/handlers/history.rs`
+
+### Files to Modify
+
+**Database Layer:**
+- `makima/src/db/models.rs`
+- `makima/src/db/repository.rs`
+
+**API Layer:**
+- `makima/src/server/handlers/mod.rs`
+- `makima/src/server/handlers/mesh.rs`
+- `makima/src/server/handlers/mesh_supervisor.rs`
+- `makima/src/server/mod.rs`
+
+**CLI Layer:**
+- `makima/src/daemon/cli/mod.rs`
+- `makima/src/daemon/cli/contract.rs`
+- `makima/src/daemon/cli/supervisor.rs`
+
+**Daemon Layer:**
+- `makima/src/daemon/ws/protocol.rs`
+- `makima/src/daemon/task/manager.rs`
+- `makima/src/daemon/worktree/manager.rs`
+- `makima/src/server/handlers/mesh_daemon.rs`
+
+---
+
+## Testing Strategy
+
+### Unit Tests
+- Repository functions: CRUD operations for snapshots and events
+- Model serialization/deserialization
+- Diff parsing functions
+
+### Integration Tests
+- API endpoint tests with mock database
+- CLI command execution tests
+- Daemon command handling tests
+
+### End-to-End Tests
+- Complete workflow: Create task -> Checkpoint -> Rewind -> Resume
+- Fork workflow: Create task -> Checkpoint -> Fork -> Verify state
+- History viewing across contract lifecycle
+
+### Manual Testing Checklist
+- [ ] Create conversation snapshot at checkpoint
+- [ ] View contract history timeline with filters
+- [ ] View task conversation history
+- [ ] Resume interrupted supervisor (all modes)
+- [ ] Resume task from specific checkpoint
+- [ ] Rewind code with branch preservation
+- [ ] Rewind conversation to message
+- [ ] Fork task from checkpoint
+- [ ] CLI commands output correctly formatted
+
+---
+
+## Security Considerations
+
+1. **Owner verification**: All operations verify owner_id authorization
+2. **Tool key authentication**: Supervisor operations require valid tool keys
+3. **Data isolation**: Users cannot access other users' history
+4. **Audit logging**: Log all rewind/fork operations for traceability
+
+## Performance Considerations
+
+1. **Pagination**: All list endpoints support cursor-based pagination
+2. **Index usage**: Queries use appropriate indexes (contract_id, task_id, created_at)
+3. **Snapshot cleanup**: Implement retention policy for old snapshots
+4. **Response limits**: Cap response sizes for conversation history