# 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/20250118000000_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/20250118000000_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