diff options
Diffstat (limited to 'makima/docs/PLAN-resume-history-system.md')
| -rw-r--r-- | makima/docs/PLAN-resume-history-system.md | 1316 |
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 |
