# 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
