summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-03 18:16:43 +0000
committersoryu <soryu@soryu.co>2026-02-03 18:16:43 +0000
commit334db7304209f48261563c0b9d955b3037b55833 (patch)
treec141919c73758d7768be8cae66576cd1e17c38ff
parent8361916ce67f3d2ba191ebf27cb50e79cb42e39c (diff)
downloadsoryu-makima/task-task-be1c651d-be1c651d.tar.gz
soryu-makima/task-task-be1c651d-be1c651d.zip
[WIP] Heartbeat checkpoint - 2026-02-03 18:16:43 UTCmakima/task-task-be1c651d-be1c651d
-rw-r--r--makima/docs/PLAN-discuss-contract.md953
1 files changed, 953 insertions, 0 deletions
diff --git a/makima/docs/PLAN-discuss-contract.md b/makima/docs/PLAN-discuss-contract.md
new file mode 100644
index 0000000..7543bdb
--- /dev/null
+++ b/makima/docs/PLAN-discuss-contract.md
@@ -0,0 +1,953 @@
+# 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<String>,
+ /// Conversation history for context continuity
+ #[serde(default)]
+ pub history: Option<Vec<ChatMessage>>,
+ /// Optional transcript context from current session
+ #[serde(default)]
+ pub transcript_context: Option<String>,
+}
+
+#[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<ToolCallInfo>,
+ /// If a contract was created, its details
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub created_contract: Option<CreatedContractInfo>,
+ /// Pending questions (if LLM needs clarification)
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub pending_questions: Option<Vec<UserQuestion>>,
+}
+
+#[derive(Debug, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct CreatedContractInfo {
+ pub id: String,
+ pub name: String,
+ pub description: Option<String>,
+ 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<Vec<Tool>> = 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<SharedState>,
+ Authenticated(auth): Authenticated,
+ Json(request): Json<DiscussContractRequest>,
+) -> 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<Message[]>([]);
+ const [input, setInput] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+ const [createdContract, setCreatedContract] = useState<CreatedContractInfo | null>(null);
+
+ const { speak, isSpeaking, cancel } = useSpeakWebSocket();
+ const messagesEndRef = useRef<HTMLDivElement>(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 (
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
+ <div className="panel w-full max-w-2xl h-[600px] flex flex-col">
+ {/* Header */}
+ <div className="flex items-center justify-between p-4 border-b border-[rgba(117,170,252,0.25)]">
+ <h2 className="font-mono text-sm text-[#dbe7ff] uppercase tracking-wide">
+ Discuss Contract with Makima
+ </h2>
+ <button onClick={onClose} className="...">
+ [X]
+ </button>
+ </div>
+
+ {/* Messages */}
+ <div className="flex-1 overflow-y-auto p-4 space-y-4">
+ {messages.map((message) => (
+ <ChatBubble
+ key={message.id}
+ message={message}
+ onSpeak={() => handleSpeak(message.content)}
+ isSpeaking={isSpeaking}
+ />
+ ))}
+ <div ref={messagesEndRef} />
+
+ {isLoading && (
+ <div className="flex items-center gap-2 text-[#9bc3ff]">
+ <span className="animate-pulse">Makima is thinking...</span>
+ </div>
+ )}
+ </div>
+
+ {/* Contract Created Banner */}
+ {createdContract && (
+ <div className="p-3 bg-green-400/10 border-t border-green-400/50">
+ <div className="font-mono text-xs text-green-400">
+ Contract "{createdContract.name}" created successfully!
+ </div>
+ </div>
+ )}
+
+ {/* Input */}
+ <div className="p-4 border-t border-[rgba(117,170,252,0.25)]">
+ <div className="flex gap-2">
+ <input
+ type="text"
+ value={input}
+ onChange={(e) => 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"
+ />
+ <button
+ onClick={handleSend}
+ disabled={isLoading || !input.trim() || !!createdContract}
+ className="px-4 py-2 bg-[#0f3c78] text-[#dbe7ff] font-mono text-xs uppercase hover:bg-[#153667] disabled:opacity-50"
+ >
+ Send
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
+```
+
+#### 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 (
+ <div className={`flex ${isUser ? "justify-end" : "justify-start"}`}>
+ <div
+ className={`max-w-[80%] p-3 font-mono text-sm ${
+ isUser
+ ? "bg-[#0f3c78] text-[#dbe7ff]"
+ : "bg-[#0d1b2d] border border-[rgba(117,170,252,0.25)] text-[#dbe7ff]"
+ }`}
+ >
+ <div className="whitespace-pre-wrap">{message.content}</div>
+
+ {!isUser && (
+ <div className="mt-2 flex items-center gap-2">
+ <button
+ onClick={onSpeak}
+ className="text-[10px] text-[#9bc3ff] hover:text-[#dbe7ff] uppercase"
+ >
+ {isSpeaking ? "[Stop]" : "[Speak]"}
+ </button>
+ </div>
+ )}
+
+ {message.toolCalls && message.toolCalls.length > 0 && (
+ <div className="mt-2 pt-2 border-t border-[rgba(117,170,252,0.25)]">
+ {message.toolCalls.map((tc, i) => (
+ <div key={i} className="text-[10px] text-[#75aafc]">
+ {tc.result.success ? "+" : "-"} {tc.name}: {tc.result.message}
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
+ </div>
+ );
+}
+```
+
+---
+
+### 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<DiscussContractResponse> {
+ 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 (
+ <div className="...">
+ {/* ... existing content ... */}
+
+ {/* Ephemeral option */}
+ <button onClick={() => handleSelect(null)} className="...">
+ <span className="uppercase tracking-wide">Ephemeral</span>
+ <span className="...">Transcript not saved</span>
+ </button>
+
+ {/* NEW: Discuss Contract option */}
+ <button
+ onClick={() => {
+ onClose();
+ onDiscussContract();
+ }}
+ className="w-full text-left px-3 py-2 font-mono text-xs border bg-[#0d1b2d] border-[#3f6fb3] text-[#dbe7ff] hover:bg-[#153667] transition-colors"
+ >
+ <span className="uppercase tracking-wide">Discuss Contract</span>
+ <span className="block text-[10px] text-[#75aafc] mt-0.5">
+ Chat with Makima to define a new contract
+ </span>
+ </button>
+
+ {/* Existing contracts */}
+ {contracts.map((contract) => (
+ // ... existing contract buttons
+ ))}
+ </div>
+ );
+}
+```
+
+#### 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 (
+ <div className="...">
+ {/* ... existing content ... */}
+
+ <ContractPickerModal
+ isOpen={isModalOpen}
+ onClose={() => setIsModalOpen(false)}
+ contracts={contracts}
+ selectedContractId={selectedContractId}
+ onSelect={onContractChange}
+ onDiscussContract={onDiscussContract}
+ loading={contractsLoading}
+ />
+ </div>
+ );
+}
+```
+
+#### 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 (
+ <div className="...">
+ {/* ... existing content ... */}
+
+ <ControlPanel
+ // ... existing props ...
+ onDiscussContract={handleOpenDiscussModal}
+ />
+
+ {/* NEW: Discuss Contract Modal */}
+ <DiscussContractModal
+ isOpen={isDiscussModalOpen}
+ onClose={() => setIsDiscussModalOpen(false)}
+ transcriptContext={transcriptContext}
+ onContractCreated={handleContractCreated}
+ />
+
+ {/* ... existing TranscriptAnalysisPanel ... */}
+ </div>
+ );
+}
+```
+
+---
+
+### 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 <token>
+
+{
+ "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