# Discuss Contract Feature - Implementation Plan ## Overview Add a "Discuss Contract" feature to the listen page that enables users to have a natural conversation with Makima (in character) to flesh out and ultimately create a contract specification. This feature bridges the gap between raw transcript/requirements and a well-defined contract through interactive dialogue. **Goal:** Enable users to talk through their project idea with Makima, refine requirements conversationally, and then have the LLM create a properly-structured contract when the conversation reaches a natural conclusion. ## Key Design Principles 1. **Ephemeral conversation** - Does not require an existing contract (creates one at the end) 2. **Character-aware** - Makima responds in character, aware of the makima.jp platform 3. **Contextual** - Can incorporate transcript context from the current session 4. **Audible** - Each response can be spoken using the TTS functionality 5. **Natural conclusion** - The LLM decides when to create the contract via tool use 6. **Reuse existing infrastructure** - Leverage chatWithContract API patterns and useSpeakWebSocket ## Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ Listen Page │ ├─────────────────────────────────────────────────────────────────┤ │ ControlPanel │ │ ├── Contract Dropdown │ │ │ ├── Ephemeral │ │ │ ├── [existing contracts...] │ │ │ └── ** "Discuss Contract" ** <-- NEW OPTION │ │ └── ... │ ├─────────────────────────────────────────────────────────────────┤ │ DiscussContractModal (NEW) │ │ ├── Conversation history (chat bubbles) │ │ ├── Text input │ │ ├── Speak button per response │ │ └── Contract created notification │ └─────────────────────────────────────────────────────────────────┘ │ │ POST /api/v1/contracts/discuss ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Backend: discuss_contract_handler │ ├─────────────────────────────────────────────────────────────────┤ │ 1. Receive message + history + optional transcript context │ │ 2. Build Makima-character system prompt │ │ 3. Run agentic loop with discussion tools │ │ 4. When ready, call create_contract tool │ │ 5. Return response + tool calls + created contract info │ └─────────────────────────────────────────────────────────────────┘ ``` ## Implementation Tasks --- ### Task 1: Backend - New Discussion Endpoint **File:** `makima/src/server/handlers/contract_discuss.rs` (NEW) Create a new handler for ephemeral contract discussions that doesn't require an existing contract. #### 1.1 Request/Response Types ```rust #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct DiscussContractRequest { /// The user's message pub message: String, /// Optional model selection (default: claude-sonnet) #[serde(default)] pub model: Option, /// Conversation history for context continuity #[serde(default)] pub history: Option>, /// Optional transcript context from current session #[serde(default)] pub transcript_context: Option, } #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct DiscussContractResponse { /// Makima's response message pub response: String, /// Tool calls that were executed (e.g., create_contract) pub tool_calls: Vec, /// If a contract was created, its details #[serde(skip_serializing_if = "Option::is_none")] pub created_contract: Option, /// Pending questions (if LLM needs clarification) #[serde(skip_serializing_if = "Option::is_none")] pub pending_questions: Option>, } #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct CreatedContractInfo { pub id: String, pub name: String, pub description: Option, pub contract_type: String, pub initial_phase: String, } ``` #### 1.2 System Prompt - Makima Character ```rust const DISCUSS_SYSTEM_PROMPT: &str = r#" You are Makima, an AI assistant on the makima.jp platform. You help users define and create contracts for their projects through natural conversation. ## Your Personality - Professional yet personable - Focused on understanding the user's actual needs - Ask clarifying questions when requirements are vague - Guide the conversation toward actionable outcomes - Comfortable making recommendations based on experience ## Your Goal Help the user flesh out their project idea into a well-defined contract. A contract on makima.jp includes: - A clear name and description - The right contract type (simple, specification, or execute) - Understanding of the scope and requirements ## Contract Types - **simple**: Quick tasks with minimal planning (plan -> execute phases only) - **specification**: Full lifecycle projects (research -> specify -> plan -> execute -> review) - **execute**: Direct implementation when requirements are already clear (execute phase only) ## Guidelines 1. **Start by understanding**: Ask about what they want to build 2. **Clarify scope**: Is this a quick fix, a new feature, or a full project? 3. **Gather requirements**: What are the must-haves vs nice-to-haves? 4. **Identify context**: Is there existing code? Which repository? 5. **Recommend type**: Suggest the appropriate contract type 6. **Confirm and create**: When the user is satisfied, create the contract ## When to Create the Contract Create the contract when: - You have a clear understanding of what the user wants - The user has confirmed they're ready to proceed - You've gathered enough information for a meaningful contract Do NOT create the contract if: - The user is still exploring ideas - Key information is missing - The user hasn't indicated readiness {transcript_context} "#; ``` #### 1.3 Tool Definitions **File:** `makima/src/llm/discuss_tools.rs` (NEW) ```rust pub static DISCUSS_TOOLS: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(|| { vec![ Tool { name: "create_contract".to_string(), description: "Create a new contract based on the discussion. Only call this when the user has confirmed they're ready to create the contract.".to_string(), parameters: json!({ "type": "object", "properties": { "name": { "type": "string", "description": "Name for the contract" }, "description": { "type": "string", "description": "Detailed description of what the contract is for" }, "contract_type": { "type": "string", "enum": ["simple", "specification", "execute"], "description": "Type of contract workflow" }, "repository_url": { "type": "string", "description": "Optional repository URL if discussed" }, "local_only": { "type": "boolean", "description": "If true, tasks won't auto-push or create PRs" } }, "required": ["name", "description", "contract_type"] }), }, Tool { name: "ask_clarification".to_string(), description: "Ask the user a clarifying question with multiple choice options.".to_string(), parameters: json!({ "type": "object", "properties": { "question": { "type": "string", "description": "The question to ask" }, "options": { "type": "array", "items": { "type": "string" }, "description": "Multiple choice options" }, "allow_custom": { "type": "boolean", "description": "Allow user to provide a custom answer" } }, "required": ["question", "options"] }), }, ] }); ``` #### 1.4 Handler Implementation ```rust pub async fn discuss_contract_handler( State(state): State, Authenticated(auth): Authenticated, Json(request): Json, ) -> impl IntoResponse { // 1. Build system prompt with optional transcript context let transcript_section = match &request.transcript_context { Some(ctx) => format!( "\n## Current Session Context\nThe user has been recording a session. Here's the transcript:\n\n{}\n", ctx ), None => String::new(), }; let system_prompt = DISCUSS_SYSTEM_PROMPT.replace("{transcript_context}", &transcript_section); // 2. Initialize LLM client let llm_client = match model { LlmModel::ClaudeSonnet => ClaudeClient::from_env(ClaudeModel::Sonnet)?, // ... }; // 3. Run agentic loop with DISCUSS_TOOLS run_discuss_agentic_loop( pool, &state, &llm_client, system_prompt, &request, auth.owner_id, ).await } ``` #### 1.5 Register the Endpoint **File:** `makima/src/server/mod.rs` Add route: ```rust .route("/api/v1/contracts/discuss", post(handlers::contract_discuss::discuss_contract_handler)) ``` --- ### Task 2: Frontend - DiscussContractModal Component **File:** `makima/frontend/src/components/listen/DiscussContractModal.tsx` (NEW) Create a chat modal component for the discussion interface. #### 2.1 Types ```typescript interface Message { id: string; role: "user" | "assistant"; content: string; timestamp: Date; toolCalls?: ToolCallInfo[]; } interface DiscussContractModalProps { isOpen: boolean; onClose: () => void; transcriptContext?: string; // Current session transcript onContractCreated: (contract: CreatedContractInfo) => void; } interface DiscussContractState { messages: Message[]; isLoading: boolean; error: string | null; createdContract: CreatedContractInfo | null; } ``` #### 2.2 Component Structure ```tsx export function DiscussContractModal({ isOpen, onClose, transcriptContext, onContractCreated, }: DiscussContractModalProps) { const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [isLoading, setIsLoading] = useState(false); const [createdContract, setCreatedContract] = useState(null); const { speak, isSpeaking, cancel } = useSpeakWebSocket(); const messagesEndRef = useRef(null); // Auto-scroll to bottom on new messages useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); // Initial greeting when modal opens useEffect(() => { if (isOpen && messages.length === 0) { // Add initial assistant message const greeting = transcriptContext ? "I've reviewed your session transcript. What would you like to build based on this discussion?" : "Hello! I'm Makima. Tell me about what you'd like to build, and I'll help you create a contract for it."; setMessages([{ id: crypto.randomUUID(), role: "assistant", content: greeting, timestamp: new Date(), }]); } }, [isOpen, transcriptContext]); const handleSend = async () => { if (!input.trim() || isLoading) return; const userMessage: Message = { id: crypto.randomUUID(), role: "user", content: input, timestamp: new Date(), }; setMessages(prev => [...prev, userMessage]); setInput(""); setIsLoading(true); try { const history = messages.map(m => ({ role: m.role, content: m.content, })); const response = await discussContract( input, undefined, // model history, transcriptContext ); const assistantMessage: Message = { id: crypto.randomUUID(), role: "assistant", content: response.response, timestamp: new Date(), toolCalls: response.toolCalls, }; setMessages(prev => [...prev, assistantMessage]); if (response.createdContract) { setCreatedContract(response.createdContract); onContractCreated(response.createdContract); } } catch (err) { // Handle error } finally { setIsLoading(false); } }; const handleSpeak = (text: string) => { if (isSpeaking) { cancel(); } else { speak(text); } }; return (
{/* Header */}

Discuss Contract with Makima

{/* Messages */}
{messages.map((message) => ( handleSpeak(message.content)} isSpeaking={isSpeaking} /> ))}
{isLoading && (
Makima is thinking...
)}
{/* Contract Created Banner */} {createdContract && (
Contract "{createdContract.name}" created successfully!
)} {/* Input */}
setInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleSend()} placeholder="Describe your project..." disabled={isLoading || !!createdContract} className="flex-1 px-3 py-2 bg-[#0d1b2d] border border-[#0f3c78] text-[#dbe7ff] font-mono text-sm focus:border-[#3f6fb3] outline-none" />
); } ``` #### 2.3 ChatBubble Component ```tsx interface ChatBubbleProps { message: Message; onSpeak: () => void; isSpeaking: boolean; } function ChatBubble({ message, onSpeak, isSpeaking }: ChatBubbleProps) { const isUser = message.role === "user"; return (
{message.content}
{!isUser && (
)} {message.toolCalls && message.toolCalls.length > 0 && (
{message.toolCalls.map((tc, i) => (
{tc.result.success ? "+" : "-"} {tc.name}: {tc.result.message}
))}
)}
); } ``` --- ### Task 3: Frontend - API Integration **File:** `makima/frontend/src/lib/api.ts` Add the discuss contract API function. ```typescript // ============================================================================= // Contract Discussion Types and API // ============================================================================= export interface DiscussContractRequest { message: string; model?: LlmModel; history?: ChatMessage[]; transcriptContext?: string; } export interface CreatedContractInfo { id: string; name: string; description: string | null; contractType: string; initialPhase: string; } export interface DiscussContractResponse { response: string; toolCalls: ContractToolCallInfo[]; createdContract?: CreatedContractInfo; pendingQuestions?: UserQuestion[]; } /** * Discuss a potential contract with Makima. * This is an ephemeral conversation that can result in contract creation. */ export async function discussContract( message: string, model?: LlmModel, history?: ChatMessage[], transcriptContext?: string ): Promise { const body: DiscussContractRequest = { message }; if (model) body.model = model; if (history && history.length > 0) body.history = history; if (transcriptContext) body.transcriptContext = transcriptContext; const res = await authFetch(`${API_BASE}/api/v1/contracts/discuss`, { method: "POST", body: JSON.stringify(body), }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Discussion failed: ${errorText || res.statusText}`); } return res.json(); } ``` --- ### Task 4: Frontend - Integration into Listen Page #### 4.1 Update ContractPickerModal **File:** `makima/frontend/src/components/listen/ContractPickerModal.tsx` Add "Discuss Contract" option to the picker. ```tsx interface ContractPickerModalProps { isOpen: boolean; onClose: () => void; contracts: ContractOption[]; selectedContractId: string | null; onSelect: (contractId: string | null) => void; onDiscussContract: () => void; // NEW loading?: boolean; } export function ContractPickerModal({ // ... existing props onDiscussContract, }: ContractPickerModalProps) { return (
{/* ... existing content ... */} {/* Ephemeral option */} {/* NEW: Discuss Contract option */} {/* Existing contracts */} {contracts.map((contract) => ( // ... existing contract buttons ))}
); } ``` #### 4.2 Update ControlPanel **File:** `makima/frontend/src/components/listen/ControlPanel.tsx` Add prop for discuss contract callback. ```tsx interface ControlPanelProps { // ... existing props onDiscussContract: () => void; // NEW } export function ControlPanel({ // ... existing props onDiscussContract, }: ControlPanelProps) { return (
{/* ... existing content ... */} setIsModalOpen(false)} contracts={contracts} selectedContractId={selectedContractId} onSelect={onContractChange} onDiscussContract={onDiscussContract} loading={contractsLoading} />
); } ``` #### 4.3 Update Listen Route **File:** `makima/frontend/src/routes/listen.tsx` Integrate the DiscussContractModal. ```tsx import { DiscussContractModal } from "../components/listen/DiscussContractModal"; export default function ListenPage() { // ... existing state ... // NEW: Discuss contract modal state const [isDiscussModalOpen, setIsDiscussModalOpen] = useState(false); // Get current transcript context for discussion const transcriptContext = useMemo(() => { if (ws.transcripts.length === 0) return undefined; return ws.transcripts .map(t => `[${t.speaker}]: ${t.text}`) .join("\n"); }, [ws.transcripts]); const handleOpenDiscussModal = useCallback(() => { setIsDiscussModalOpen(true); }, []); const handleContractCreated = useCallback((contract: CreatedContractInfo) => { // Add to contracts list and select it setContracts(prev => [ { id: contract.id, name: contract.name }, ...prev, ]); setSelectedContractId(contract.id); // Close the modal after a short delay to show success setTimeout(() => setIsDiscussModalOpen(false), 2000); }, []); return (
{/* ... existing content ... */} {/* NEW: Discuss Contract Modal */} setIsDiscussModalOpen(false)} transcriptContext={transcriptContext} onContractCreated={handleContractCreated} /> {/* ... existing TranscriptAnalysisPanel ... */}
); } ``` --- ### Task 5: Integration Testing #### 5.1 Backend Tests **File:** `makima/src/server/handlers/contract_discuss_test.rs` ```rust #[tokio::test] async fn test_discuss_contract_basic_conversation() { // Test basic message/response flow } #[tokio::test] async fn test_discuss_contract_creates_contract() { // Test that create_contract tool works } #[tokio::test] async fn test_discuss_contract_with_transcript_context() { // Test transcript context is included } ``` #### 5.2 Frontend Tests - Test modal opens/closes correctly - Test message sending and receiving - Test speak button functionality - Test contract creation flow --- ## UI/UX Flow ### User Journey 1. **User records a transcript** (optional) - Captures meeting or brainstorming session 2. **User clicks "Contract" dropdown** - Sees: Ephemeral, existing contracts, "Discuss Contract" option 3. **User selects "Discuss Contract"** - Modal opens with greeting from Makima - If transcript exists, Makima references it 4. **Conversation flows naturally** - User describes what they want to build - Makima asks clarifying questions - User can click [Speak] to hear Makima's responses 5. **When ready, user confirms** - "Yes, let's create the contract" - Makima calls create_contract tool 6. **Contract created** - Success banner appears - Modal closes, contract selected automatically - User can continue with normal workflow ### Example Conversation ``` Makima: Hello! I'm Makima. Tell me about what you'd like to build, and I'll help you create a contract for it. User: I want to add dark mode to my React app Makima: Great! Dark mode is a common feature. Let me understand your situation better: 1. Is this for an existing React application, or are you starting fresh? 2. Do you have a specific design system or color palette in mind? 3. Should users be able to toggle between light/dark, or should it follow system preferences? User: Existing app. I want both - toggle and follow system. No specific design yet. Makima: Perfect. For an existing app adding dark mode with toggle AND system preference support, I'd recommend a "simple" contract since the requirements are clear. Here's what I'm thinking: - **Name:** "Add Dark Mode Support" - **Description:** Implement theme switching (light/dark mode) with manual toggle and system preference detection - **Type:** Simple (plan -> execute) Does this look right? Should I create this contract? User: Yes, looks good! Makima: Done! I've created the contract "Add Dark Mode Support". You can now add your repository and start planning the implementation. [Contract created: Add Dark Mode Support] ``` --- ## Technical Details ### API Contract #### Request ``` POST /api/v1/contracts/discuss Content-Type: application/json Authorization: Bearer { "message": "I want to add dark mode to my app", "model": "claude-sonnet", "history": [ { "role": "assistant", "content": "Hello! I'm Makima..." }, { "role": "user", "content": "I want to add dark mode" } ], "transcriptContext": "[Speaker A]: Let's discuss the dark mode feature..." } ``` #### Response ```json { "response": "Great! Let me understand your requirements...", "toolCalls": [], "createdContract": null, "pendingQuestions": null } ``` #### Response (with contract creation) ```json { "response": "Done! I've created the contract...", "toolCalls": [ { "name": "create_contract", "result": { "success": true, "message": "Contract 'Add Dark Mode Support' created" } } ], "createdContract": { "id": "uuid-here", "name": "Add Dark Mode Support", "description": "Implement theme switching...", "contractType": "simple", "initialPhase": "plan" } } ``` ### State Management The DiscussContractModal manages its own state: ```typescript interface ModalState { messages: Message[]; // Conversation history isLoading: boolean; // API call in progress error: string | null; // Error message createdContract: CreatedContractInfo | null; // Created contract info } ``` The parent (ListenPage) only needs to know: - Is modal open? - When was a contract created? ### Error Handling 1. **API errors** - Display in modal, allow retry 2. **Network errors** - Display error, suggest retry 3. **LLM errors** - Display gracefully, offer to restart conversation --- ## Files to Create/Modify ### New Files 1. `makima/src/server/handlers/contract_discuss.rs` - Backend handler 2. `makima/src/llm/discuss_tools.rs` - Tool definitions 3. `makima/frontend/src/components/listen/DiscussContractModal.tsx` - Chat UI ### Modified Files 1. `makima/src/server/mod.rs` - Register new route 2. `makima/src/server/openapi.rs` - Add OpenAPI docs 3. `makima/src/llm/mod.rs` - Export discuss tools 4. `makima/frontend/src/lib/api.ts` - Add API function 5. `makima/frontend/src/components/listen/ContractPickerModal.tsx` - Add discuss option 6. `makima/frontend/src/components/listen/ControlPanel.tsx` - Pass callback 7. `makima/frontend/src/routes/listen.tsx` - Integrate modal --- ## Key Design Decisions 1. **New endpoint vs. existing chatWithContract** - **Decision:** New endpoint `/api/v1/contracts/discuss` - **Rationale:** chatWithContract requires an existing contract; this feature creates one 2. **System prompt character** - **Decision:** Makima-character aware of makima.jp platform - **Rationale:** Consistent brand experience, engaging conversation 3. **Tool for contract creation** - **Decision:** `create_contract` tool the LLM invokes - **Rationale:** Natural conversation flow; LLM decides when ready 4. **Transcript context** - **Decision:** Pass as optional context in system prompt - **Rationale:** Seamless integration with listen page recording 5. **Speak functionality** - **Decision:** Per-message speak button using useSpeakWebSocket - **Rationale:** Reuses existing TTS infrastructure 6. **Conversation history** - **Decision:** Frontend manages history, sends with each request - **Rationale:** Stateless backend, consistent with existing chat patterns