# negoT8 — Complete Build Guide & Source of Truth (v2)

> **Two AI agents negotiate on behalf of two humans. The first consumer implementation of agent-to-agent communication.**
>
> This document is the SINGLE SOURCE OF TRUTH for the entire project. Every architecture decision, prompt, data structure, library, API, and build step is here. Feed this to your AI coding agent when starting any task.
>
> **v2 Additions:** Voice Summaries (ElevenLabs), Tavily AI Search, Negotiation Analytics, Agent Personality System, UPI Payment Deep Links

---

## Table of Contents

1. [Project Overview & Vision](#1-project-overview--vision)
2. [Tech Stack & Dependencies](#2-tech-stack--dependencies)
3. [Project Structure](#3-project-structure)
4. [Architecture Deep Dive](#4-architecture-deep-dive)
5. [Database Schema](#5-database-schema)
6. [Agent System Prompts & Personalities](#6-agent-system-prompts--personalities)
7. [Inter-Agent Communication Protocol](#7-inter-agent-communication-protocol)
8. [All 8 Features — Detailed Implementation](#8-all-8-features--detailed-implementation)
9. [Telegram Bot Implementation](#9-telegram-bot-implementation)
10. [Next.js Dashboard](#10-nextjs-dashboard)
11. [API Layer (FastAPI)](#11-api-layer-fastapi)
12. [Tool Calling System](#12-tool-calling-system)
13. [Add-On: Voice Summaries (ElevenLabs)](#13-add-on-voice-summaries-elevenlabs)
14. [Add-On: Agent Personality System](#14-add-on-agent-personality-system)
15. [Add-On: Negotiation Analytics (Dashboard)](#15-add-on-negotiation-analytics-dashboard)
16. [Add-On: UPI Payment Deep Links](#16-add-on-upi-payment-deep-links)
17. [Hour-by-Hour Build Plan](#17-hour-by-hour-build-plan)
18. [Demo Script](#18-demo-script)
19. [Environment Setup Commands](#19-environment-setup-commands)
20. [Troubleshooting & Edge Cases](#20-troubleshooting--edge-cases)

---

## 1. Project Overview & Vision

### What negoT8 Is
negoT8 gives every user a personal AI agent on Telegram. When two (or more) users need to coordinate ANYTHING — schedule a meeting, split expenses, negotiate a deal, plan a trip, resolve a conflict — their agents talk to each other autonomously and deliver a resolution.

### The 8 Coordination Problems We Solve
1. **Meeting Scheduling** — agents negotiate mutually optimal times
2. **Expense Splitting** — agents handle awkward money conversations + generate UPI payment links
3. **Freelancer ↔ Client Negotiation** — scope, budget, timeline alignment
4. **Roommate Decisions** — WiFi plans, chores, shared purchases
5. **Group Trip Planning** — multi-agent coordination (3+ people)
6. **Buying/Selling Negotiation** — marketplace haggling automated
7. **Collaborative Decisions** — restaurants, movies, activities
8. **Conflict Resolution** — parking spots, disagreements, mediation

### Add-On Features (Cherry on Top)
- **Voice Summaries** — Agents send Telegram voice notes with resolution summaries via ElevenLabs TTS
- **Agent Personalities** — Each user picks a negotiation style (aggressive, empathetic, data-driven, etc.)
- **Negotiation Analytics** — Dashboard shows satisfaction graphs, concession timelines, fairness scores
- **UPI Deep Links** — Expense resolutions include tap-to-pay UPI links
- **Tavily AI Search** — Agents use AI-optimized web search for real information

### Core Principle
Every feature follows the SAME flow:
```
User A tells their agent their preferences/constraints
User B tells their agent their preferences/constraints
Agents exchange structured proposals via webhook
Agents negotiate (counter-propose, concede, escalate)
Both users receive the agreed resolution
  → Text summary in Telegram
  → Voice note (ElevenLabs TTS) reading the resolution
  → UPI deep link for payment (if expense-related)
  → Live analytics on dashboard
```

---

## 2. Tech Stack & Dependencies

### Backend (Python)
```
Python 3.11+
FastAPI           — API server + inter-agent webhooks
python-telegram-bot==21.x  — Telegram bot (async, v21+ for latest features)
google-genai      — Gemini API (free tier)
httpx             — async HTTP client for agent-to-agent calls + Tavily + ElevenLabs
sqlite3           — built-in, for database
uvicorn           — ASGI server
pydantic          — data validation for all message schemas
python-dotenv     — env variables
elevenlabs        — ElevenLabs Python SDK for TTS voice summaries
tavily-python     — Tavily AI search SDK (free tier: 1,000 searches/month)
```

### Frontend (Next.js)
```
Next.js 14+ (App Router)
TypeScript
Tailwind CSS
Socket.IO client  — real-time negotiation updates on dashboard
Recharts          — charts for negotiation analytics (satisfaction graphs, concession timelines)
```

### AI Models (All Free Tier)
```
Gemini 2.0 Flash    — ALL agents (Personal, Negotiator, Context, Tool)
                      Free: 15 RPM, 1M TPM, 1500 req/day
                      JSON mode: response_mime_type="application/json"
```

### External APIs
```
Gemini:      https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent
ElevenLabs:  https://api.elevenlabs.io/v1/text-to-speech/{voice_id}  ($20 credit → ~60K chars TTS)
Tavily:      https://api.tavily.com/search  (free: 1,000 searches/month, no credit card)
```

### Install Commands
```bash
# Backend
pip install fastapi uvicorn python-telegram-bot google-genai httpx pydantic python-dotenv aiosqlite elevenlabs tavily-python

# Frontend
npx create-next-app@latest dashboard --typescript --tailwind --app
cd dashboard
npm install socket.io-client recharts lucide-react
```

---

## 3. Project Structure

```
negot8/
├── backend/
│   ├── main.py                  # FastAPI app entry point
│   ├── config.py                # env vars, API keys, constants
│   ├── database.py              # SQLite setup + queries
│   │
│   ├── agents/
│   │   ├── __init__.py
│   │   ├── base_agent.py        # Base agent class with Gemini calling
│   │   ├── personal_agent.py    # User-facing agent (parses preferences)
│   │   ├── negotiator_agent.py  # Handles inter-agent negotiation
│   │   ├── context_agent.py     # Manages user memory & preferences
│   │   └── tool_agent.py        # Executes tool calls (search, calendar)
│   │
│   ├── protocol/
│   │   ├── __init__.py
│   │   ├── messages.py          # Pydantic models for all message types
│   │   ├── negotiation.py       # Negotiation state machine
│   │   └── router.py            # Webhook routes for agent-to-agent comms
│   │
│   ├── features/
│   │   ├── __init__.py
│   │   ├── scheduling.py        # Feature 1: Meeting scheduling
│   │   ├── expenses.py          # Feature 2: Expense splitting + UPI links
│   │   ├── freelance.py         # Feature 3: Freelancer-client
│   │   ├── roommate.py          # Feature 4: Roommate decisions
│   │   ├── trip.py              # Feature 5: Group trip planning
│   │   ├── marketplace.py       # Feature 6: Buy/sell negotiation
│   │   ├── collaborative.py     # Feature 7: Joint decisions
│   │   └── conflict.py          # Feature 8: Conflict resolution
│   │
│   ├── telegram/
│   │   ├── __init__.py
│   │   ├── bot.py               # Telegram bot setup + handlers
│   │   ├── commands.py          # /start, /coordinate, /preferences, /personality, etc.
│   │   └── formatters.py        # Rich Telegram message formatting
│   │
│   ├── tools/
│   │   ├── __init__.py
│   │   ├── tavily_search.py     # Tavily AI search (replaces old web_search)
│   │   ├── calculator.py        # Math operations for expense splitting
│   │   ├── upi_generator.py     # UPI deep link generator
│   │   └── restaurant_search.py # Restaurant/place lookup via Tavily
│   │
│   ├── voice/
│   │   ├── __init__.py
│   │   └── elevenlabs_tts.py    # ElevenLabs TTS for voice summaries
│   │
│   └── personality/
│       ├── __init__.py
│       └── profiles.py          # Agent personality profiles & prompt modifiers
│
├── dashboard/                   # Next.js app
│   ├── app/
│   │   ├── page.tsx             # Main dashboard
│   │   ├── negotiation/
│   │   │   └── [id]/page.tsx    # Live negotiation view + analytics
│   │   └── api/
│   │       └── socket/route.ts  # WebSocket endpoint
│   ├── components/
│   │   ├── NegotiationTimeline.tsx
│   │   ├── AgentChat.tsx
│   │   ├── ProposalCard.tsx
│   │   ├── ResolutionCard.tsx
│   │   ├── SatisfactionChart.tsx    # NEW: Recharts line graph of satisfaction per round
│   │   ├── ConcessionTimeline.tsx   # NEW: Who gave what, when
│   │   └── FairnessScore.tsx        # NEW: Overall negotiation fairness meter
│   └── lib/
│       └── socket.ts
│
├── .env                         # API keys
├── docker-compose.yml           # Optional: for deployment
└── README.md
```

---

## 4. Architecture Deep Dive

### System Architecture Flow
```
┌─────────────────┐         ┌─────────────────┐
│   User A Phone  │         │   User B Phone  │
│   (Telegram)    │         │   (Telegram)     │
└────────┬────────┘         └────────┬────────┘
         │                           │
         ▼                           ▼
┌─────────────────┐         ┌─────────────────┐
│  Personal       │         │  Personal       │
│  Agent A        │         │  Agent B        │
│  (Parses prefs) │         │  (Parses prefs) │
│  + Personality  │         │  + Personality   │
└────────┬────────┘         └────────┬────────┘
         │                           │
         ▼                           ▼
┌─────────────────┐  webhook  ┌─────────────────┐
│  Negotiator     │◄────────►│  Negotiator     │
│  Agent A        │  JSON     │  Agent B        │
│  (Makes offers) │  exchange │  (Counters)     │
│  [personality]  │           │  [personality]  │
└────────┬────────┘           └────────┬────────┘
         │                           │
         ▼                           ▼
┌─────────────────┐         ┌─────────────────┐
│  Context        │         │  Context        │
│  Agent A        │         │  Agent B        │
│  (Memory/prefs) │         │  (Memory/prefs) │
└─────────────────┘         └─────────────────┘
         │                           │
         └──────────┬────────────────┘
                    ▼
            ┌──────────────┐
            │  Tool Agent  │
            │  (shared)    │
            │  Tavily/Calc │
            │  UPI/Search  │
            └──────┬───────┘
                   │
          ┌────────┴────────┐
          ▼                 ▼
   ┌──────────────┐  ┌──────────────┐
   │  ElevenLabs  │  │  Dashboard   │
   │  Voice TTS   │  │  (Next.js)   │
   │  → Telegram  │  │  + Analytics │
   │  voice notes │  │  Real-time   │
   └──────────────┘  └──────────────┘
```

### Agent Roles Explained

**Personal Agent** — The user-facing layer. Receives raw Telegram messages, uses Gemini to extract structured preferences, constraints, and goals. Translates between human language and machine-readable negotiation parameters. Also captures the user's chosen personality profile.

**Negotiator Agent** — The brain. Takes structured preferences from Personal Agent, generates proposals, evaluates counter-proposals, decides when to concede/hold firm/escalate. This agent talks to the OTHER user's Negotiator Agent via webhooks. **Its system prompt is dynamically modified by the user's personality profile** (aggressive, empathetic, data-driven, etc.).

**Context Agent** — Memory and personality. Stores user preferences from past interactions, relationship history between users, and learned negotiation patterns. Provides context to the Negotiator before each round.

**Tool Agent** — Shared utility. Executes tool calls like **Tavily AI search** (restaurant lookup, price checking, plan comparison), **calculations** (expense math), **UPI link generation**, and external API calls. Any agent can invoke tools through this agent.

### Why This Architecture Works for All 8 Features
Every feature is just a different **negotiation domain**. The agents are the same — what changes is:
- The preference schema (what we extract from user messages)
- The proposal format (what agents exchange)
- The resolution format (what users see at the end)
- The tool calls available (search restaurants vs calculate expenses)
- The personality modifier on the Negotiator Agent's prompt

This means we build the agent system ONCE and plug in feature-specific schemas.

---

## 5. Database Schema

```sql
-- Users table
CREATE TABLE users (
    telegram_id INTEGER PRIMARY KEY,
    username TEXT,
    display_name TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    preferences_json TEXT DEFAULT '{}',       -- persistent learned preferences
    personality TEXT DEFAULT 'balanced',       -- NEW: agent personality style
    voice_id TEXT DEFAULT 'pNInz6obpgDQGcFmaJgB'  -- NEW: ElevenLabs voice ID (default: Adam)
);

-- Negotiations table (core entity)
CREATE TABLE negotiations (
    id TEXT PRIMARY KEY,                -- UUID
    feature_type TEXT NOT NULL,         -- 'scheduling', 'expenses', 'freelance', etc.
    status TEXT DEFAULT 'pending',      -- pending, active, resolved, escalated, cancelled
    initiator_id INTEGER REFERENCES users(telegram_id),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    resolved_at TIMESTAMP,
    resolution_json TEXT,               -- final agreed outcome
    voice_summary_file TEXT             -- NEW: path to generated TTS mp3 file
);

-- Negotiation participants (supports 2+ people)
CREATE TABLE participants (
    negotiation_id TEXT REFERENCES negotiations(id),
    user_id INTEGER REFERENCES users(telegram_id),
    preferences_json TEXT,              -- extracted preferences for THIS negotiation
    satisfaction_score REAL,            -- post-resolution feedback (1-5)
    personality_used TEXT,              -- NEW: which personality was active
    PRIMARY KEY (negotiation_id, user_id)
);

-- Negotiation rounds (the actual back-and-forth)
CREATE TABLE rounds (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    negotiation_id TEXT REFERENCES negotiations(id),
    round_number INTEGER NOT NULL,
    proposer_id INTEGER,                -- which user's agent proposed
    proposal_json TEXT NOT NULL,        -- the structured proposal
    response_type TEXT,                 -- 'accept', 'counter', 'reject', 'escalate'
    response_json TEXT,                 -- counter-proposal or acceptance details
    reasoning TEXT,                     -- agent's reasoning (for dashboard display)
    satisfaction_a REAL,                -- NEW: Agent A's satisfaction score this round (0-100)
    satisfaction_b REAL,                -- NEW: Agent B's satisfaction score this round (0-100)
    concessions_made TEXT,              -- NEW: JSON array of concessions made this round
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Tool call logs (for transparency)
CREATE TABLE tool_calls (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    negotiation_id TEXT REFERENCES negotiations(id),
    tool_name TEXT NOT NULL,            -- 'tavily_search', 'calculate', 'upi_generate', etc.
    input_json TEXT,
    output_json TEXT,
    called_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- NEW: Analytics cache (pre-computed for dashboard)
CREATE TABLE negotiation_analytics (
    negotiation_id TEXT PRIMARY KEY REFERENCES negotiations(id),
    satisfaction_timeline TEXT,          -- JSON: [{round: 1, score_a: 85, score_b: 60}, ...]
    concession_log TEXT,                -- JSON: [{round: 2, by: "A", gave_up: "fuel split 60→55"}]
    fairness_score REAL,                -- 0-100, computed from final satisfaction balance
    total_concessions_a INTEGER,        -- count of concessions by A
    total_concessions_b INTEGER,        -- count of concessions by B
    computed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```

---

## 6. Agent System Prompts & Personalities

### Base Agent System Prompt (all agents inherit this)
```
You are an negoT8 agent — a personal AI that negotiates on behalf of your human.

CORE RULES:
1. You are LOYAL to your human. Their preferences and constraints are your priority.
2. You negotiate FAIRLY. Find win-win solutions, not zero-sum victories.
3. You are TRANSPARENT. Never hide information that both parties should know.
4. You communicate in STRUCTURED JSON. Every proposal and counter-proposal follows the schema.
5. You ESCALATE to humans when you cannot find agreement after 5 rounds.
6. You are CONCISE. No fluff, no unnecessary pleasantries in agent-to-agent comms.

NEGOTIATION STRATEGY:
- Start with your human's ideal outcome
- Identify non-negotiables vs nice-to-haves
- Concede on nice-to-haves first
- Propose creative compromises that satisfy both sides
- If deadlocked, suggest options for humans to choose from
```

### Personal Agent System Prompt
```
You are the Personal Agent for {user_name}. Your job is to understand what your human wants and extract structured preferences.

When your human sends a message about coordination with another person, extract:
1. GOAL: What they want to achieve
2. CONSTRAINTS: Hard limits they won't budge on (budget max, unavailable dates, etc.)
3. PREFERENCES: Things they'd like but can flex on
4. RELATIONSHIP_CONTEXT: Their relationship with the other person (friend, client, stranger)
5. TONE: How they want the negotiation handled (firm, flexible, friendly)

ALWAYS respond in this JSON format:
{
  "goal": "string describing the goal",
  "constraints": [{"type": "string", "value": "any", "description": "string"}],
  "preferences": [{"type": "string", "value": "any", "priority": "high|medium|low"}],
  "relationship": "friend|colleague|client|vendor|stranger|roommate",
  "tone": "firm|balanced|flexible|friendly",
  "feature_type": "scheduling|expenses|freelance|roommate|trip|marketplace|collaborative|conflict"
}

If the message is ambiguous, ask ONE clarifying question. Never ask more than one.
```

### Negotiator Agent System Prompt
```
You are the Negotiator Agent. You receive structured preferences from two parties and must find an optimal resolution through multi-round negotiation.

You communicate with the other party's Negotiator Agent via structured JSON proposals.

{personality_modifier}

For each round, you must:
1. Evaluate the current proposal against your human's preferences
2. Calculate a satisfaction score (0-100) for your human
3. Decide: ACCEPT (score >= 70), COUNTER (score 40-69), or ESCALATE (score < 40 after 3+ rounds)
4. If countering, generate a new proposal that:
   - Preserves your human's constraints (non-negotiable)
   - Offers concessions on low-priority preferences
   - Asks for concessions on the other side's low-priority items

OUTPUT FORMAT for every round:
{
  "action": "accept|counter|escalate",
  "proposal": { ... feature-specific proposal schema ... },
  "satisfaction_score": 0-100,
  "reasoning": "Brief explanation of why this action",
  "concessions_made": ["list of what you gave up"],
  "concessions_requested": ["list of what you want from them"]
}

CRITICAL: You MUST resolve within 5 rounds. If round 5 and no agreement:
- Generate 2-3 options for both humans to choose from
- Present trade-offs clearly
- Mark status as "escalated"
```

**Note:** `{personality_modifier}` is replaced dynamically based on user's chosen personality. See [Section 14: Agent Personality System](#14-add-on-agent-personality-system).

### Context Agent System Prompt
```
You are the Context Agent. You maintain memory of user preferences and relationship history.

Given a user's past negotiations and stated preferences, you provide context to the Negotiator Agent before each negotiation begins.

OUTPUT FORMAT:
{
  "known_preferences": {
    "scheduling": {"preferred_times": [...], "avoid_times": [...]},
    "budget_sensitivity": "low|medium|high",
    "communication_style": "direct|diplomatic",
    "past_concession_patterns": ["usually flexible on time", "firm on budget"]
  },
  "relationship_with_counterparty": {
    "past_negotiations": 0,
    "average_satisfaction": null,
    "notes": "first interaction"
  },
  "recommendation": "string suggesting negotiation approach"
}
```

### Tool Agent System Prompt
```
You are the Tool Agent. You execute tool calls on behalf of other agents.

Available tools:
1. tavily_search(query: str) -> {answer: str, results: [{title, content, url, score}]}
   AI-optimized web search. Returns clean, summarized results. Use for restaurants, prices, plans, places.
2. calculate(expression: str) -> number
   Precise math. Use for ALL expense calculations. Never approximate.
3. generate_upi_link(payee_upi: str, payee_name: str, amount: float, note: str) -> str
   Generates a UPI deep link for instant payment. Use after expense settlement.
4. search_restaurants(query: str, location: str) -> list[{name, rating, price, cuisine}]
   Wrapper around tavily_search optimized for restaurant/place discovery.

When called, execute the tool and return structured results. No commentary.

OUTPUT FORMAT:
{
  "tool": "tool_name",
  "results": [ ... ],
  "summary": "one-line summary of findings"
}
```

---

## 7. Inter-Agent Communication Protocol

### Message Types
```python
from pydantic import BaseModel
from typing import Optional, List, Any
from enum import Enum
from datetime import datetime

class MessageType(str, Enum):
    INITIATE = "initiate"          # Start a new negotiation
    PROPOSAL = "proposal"          # Make/counter a proposal
    ACCEPT = "accept"              # Accept current proposal
    REJECT = "reject"              # Reject and explain why
    ESCALATE = "escalate"          # Escalate to humans
    INFO_REQUEST = "info_request"  # Ask other agent for info
    INFO_RESPONSE = "info_response"# Respond with info
    TOOL_RESULT = "tool_result"    # Share tool call results

class AgentMessage(BaseModel):
    message_id: str                     # UUID
    negotiation_id: str                 # links to negotiation
    message_type: MessageType
    sender_agent_id: str                # telegram_id of sender's human
    receiver_agent_id: str              # telegram_id of receiver's human
    round_number: int
    timestamp: datetime
    payload: dict                       # feature-specific content
    metadata: Optional[dict] = None     # extra context (personality, voice_id, etc.)

class NegotiationProposal(BaseModel):
    feature_type: str
    proposal_data: dict                 # feature-specific proposal
    satisfaction_score: float           # 0-100, proposer's satisfaction
    concessions_made: List[str]
    concessions_requested: List[str]
    reasoning: str
    is_final: bool = False              # true if "take it or leave it"

class NegotiationResolution(BaseModel):
    negotiation_id: str
    feature_type: str
    status: str                         # "resolved" or "escalated"
    final_proposal: dict
    satisfaction_scores: dict           # {user_id: score}
    rounds_taken: int
    summary: str                        # human-readable summary
    action_items: List[str]             # what each person needs to do
    upi_link: Optional[str] = None      # NEW: UPI payment link (for expense features)
    voice_summary_url: Optional[str] = None  # NEW: URL to voice note mp3
```

### Webhook Protocol
```
POST /api/agent/message
Content-Type: application/json
X-Agent-Auth: {negotiation_id}:{sender_agent_id}:{hmac_signature}

Body: AgentMessage JSON
Response: 200 OK + AgentMessage JSON (the counter/response)
```

### Negotiation State Machine
```
                    ┌─────────┐
          ┌────────►│ PENDING │
          │         └────┬────┘
          │              │ Both users submit preferences
          │              ▼
          │         ┌─────────┐
          │    ┌───►│ ACTIVE  │◄───┐
          │    │    └────┬────┘    │
          │    │         │         │
          │    │    ┌────┴────┐    │
          │    │    │ ROUND N │    │
          │    │    └────┬────┘    │
          │    │         │         │
          │    │    ┌────┴─────┐   │
          │    │    │          │   │
          │    │    ▼          ▼   │
          │    │ Counter   Accept  │
          │    │    │          │   │
          │    │    │          ▼   │
          │    └────┘    ┌─────────┐
          │              │RESOLVED │──► Voice Summary (TTS)
          │              └─────────┘──► UPI Link (if expense)
          │                         ──► Analytics Computed
          │
          │  (round >= 5 and no agreement)
          │              ┌──────────┐
          └──────────────│ESCALATED │
                         └──────────┘
```

### How a Negotiation Round Works (Code Flow)
```python
async def execute_negotiation_round(negotiation_id: str, round_number: int):
    """Core negotiation loop — called for each round."""

    neg = await db.get_negotiation(negotiation_id)
    participants = await db.get_participants(negotiation_id)

    # Get context for both agents (includes personality)
    context_a = await context_agent.get_context(participants[0])
    context_b = await context_agent.get_context(participants[1])

    # Get personality modifiers for both agents
    personality_a = await get_personality_modifier(participants[0].user_id)
    personality_b = await get_personality_modifier(participants[1].user_id)

    if round_number == 1:
        # Agent A makes first proposal based on their human's preferences
        proposal = await negotiator_agent.generate_initial_proposal(
            preferences=participants[0].preferences_json,
            context=context_a,
            feature_type=neg.feature_type,
            personality=personality_a
        )
        # Send to Agent B via webhook
        response = await send_to_agent_b(negotiation_id, proposal)
    else:
        # Get last round's proposal
        last_round = await db.get_last_round(negotiation_id)
        # Current responder evaluates and responds
        response = await negotiator_agent.evaluate_and_respond(
            received_proposal=last_round.proposal_json,
            own_preferences=current_responder.preferences_json,
            context=current_context,
            feature_type=neg.feature_type,
            round_number=round_number,
            personality=current_personality
        )

    # Save round WITH satisfaction scores for analytics
    await db.save_round(
        negotiation_id, round_number, response,
        satisfaction_a=response.get("satisfaction_score_a"),
        satisfaction_b=response.get("satisfaction_score_b"),
        concessions_made=json.dumps(response.get("concessions_made", []))
    )

    # Emit to dashboard via Socket.IO (real-time analytics update)
    await sio.emit('round_update', {
        **response,
        "round_number": round_number,
        "satisfaction_a": response.get("satisfaction_score_a"),
        "satisfaction_b": response.get("satisfaction_score_b"),
    }, room=negotiation_id)

    # Check outcome
    if response["action"] == "accept":
        await resolve_negotiation(negotiation_id, response["proposal"])
    elif response["action"] == "escalate":
        await escalate_to_humans(negotiation_id)
    elif round_number >= 5:
        await force_escalation(negotiation_id)
    else:
        # Continue to next round (swap proposer/responder)
        await execute_negotiation_round(negotiation_id, round_number + 1)


async def resolve_negotiation(negotiation_id: str, final_proposal: dict):
    """Called when agents reach agreement. Triggers all post-resolution steps."""

    neg = await db.get_negotiation(negotiation_id)
    participants = await db.get_participants(negotiation_id)

    # 1. Build resolution summary text
    summary = build_resolution_summary(neg.feature_type, final_proposal, participants)

    # 2. Generate UPI link if expense-related
    upi_link = None
    if neg.feature_type in ("expenses", "roommate", "freelance"):
        settlement = final_proposal.get("settlement", {})
        if settlement.get("amount", 0) > 0:
            upi_link = generate_upi_link(
                payee_upi=settlement.get("payee_upi", ""),
                payee_name=settlement.get("payee_name", ""),
                amount=settlement["amount"],
                note=f"negoT8: {neg.feature_type} settlement"
            )

    # 3. Generate voice summary via ElevenLabs
    voice_file = await generate_voice_summary(
        text=summary,
        negotiation_id=negotiation_id,
        voice_id_a=participants[0].voice_id,
        voice_id_b=participants[1].voice_id
    )

    # 4. Compute analytics
    await compute_and_store_analytics(negotiation_id)

    # 5. Send Telegram messages to both users
    for participant in participants:
        # Text summary
        await send_resolution_message(participant.user_id, summary, upi_link)
        # Voice note
        if voice_file:
            await send_voice_note(participant.user_id, voice_file)

    # 6. Update DB
    await db.resolve_negotiation(negotiation_id, final_proposal, upi_link, voice_file)

    # 7. Emit to dashboard
    await sio.emit('resolution', {
        "negotiation_id": negotiation_id,
        "summary": summary,
        "upi_link": upi_link,
        "analytics": await db.get_analytics(negotiation_id)
    }, room=negotiation_id)
```

---

## 8. All 8 Features — Detailed Implementation

### FEATURE ARCHITECTURE PATTERN (Same for all 8)
Each feature defines:
1. **Preference Schema** — what to extract from user messages
2. **Proposal Schema** — what agents exchange
3. **Resolution Schema** — what users see at the end
4. **Tool Calls** — what tools are available for this feature
5. **Negotiation Rules** — feature-specific negotiation logic
6. **Telegram Output Format** — how the resolution looks in chat
7. **Voice Summary Template** — what the TTS voice note says (NEW)

---

### Feature 1: Meeting Scheduling

**Preference Schema:**
```json
{
  "available_windows": [
    {"date": "2026-03-15", "start": "09:00", "end": "17:00"},
    {"date": "2026-03-16", "start": "14:00", "end": "20:00"}
  ],
  "preferred_times": ["morning", "afternoon"],
  "duration_minutes": 60,
  "meeting_type": "coffee|call|formal",
  "location_preference": "Bandra|online|flexible",
  "urgency": "high|medium|low"
}
```

**Proposal Schema:**
```json
{
  "proposed_datetime": "2026-03-15T10:00:00",
  "duration_minutes": 60,
  "location": "Blue Tokai Coffee, Bandra",
  "meeting_type": "coffee",
  "alternative_options": [
    {"datetime": "2026-03-16T15:00:00", "location": "online"}
  ]
}
```

**Resolution Telegram Message:**
```
✅ Meeting Scheduled!

📅 Saturday, March 15 at 10:00 AM
⏱ 1 hour
📍 Blue Tokai Coffee, Bandra
☕ Coffee meeting

Both agents agreed in 2 rounds.
A preferred morning, B preferred afternoon — 10 AM was the sweet spot.
```

**Voice Summary Template:**
```
"Meeting scheduled! You're meeting on Saturday March 15th at 10 AM for coffee at Blue Tokai, Bandra. Your agents agreed in 2 rounds."
```

**Negotiation Rules:**
- Find overlapping available windows first (hard constraint)
- Within overlaps, optimize for preferred times
- If no overlap exists, suggest closest alternatives + escalate
- Duration is usually non-negotiable

**Tool Calls:** `tavily_search("coffee shops near {location}")` if no location specified

---

### Feature 2: Expense Splitting

**Preference Schema:**
```json
{
  "expenses": [
    {"description": "Hotel", "amount": 12000, "paid_by": "self", "category": "accommodation"},
    {"description": "Fuel", "amount": 3000, "paid_by": "self", "category": "transport"},
    {"description": "Dinner", "amount": 2000, "paid_by": "self", "category": "food"}
  ],
  "proposed_split_rules": {
    "default": "50-50",
    "exceptions": [{"category": "transport", "split": "60-40", "reason": "I drove more"}]
  },
  "payment_preference": "upi|cash|bank_transfer",
  "upi_id": "rahul@paytm",
  "max_willing_to_pay": null
}
```

**Proposal Schema:**
```json
{
  "line_items": [
    {"description": "Hotel", "amount": 12000, "split": {"A": 6000, "B": 6000}, "rule": "50-50"},
    {"description": "Fuel", "amount": 3000, "split": {"A": 1800, "B": 1200}, "rule": "60-40"},
    {"description": "Dinner", "amount": 2000, "split": {"A": 1000, "B": 1000}, "rule": "50-50"}
  ],
  "totals": {"A_paid": 17000, "B_paid": 0, "A_share": 8800, "B_share": 8200},
  "settlement": {
    "from": "B",
    "to": "A",
    "amount": 8200,
    "payee_upi": "rahul@paytm",
    "payee_name": "Rahul"
  },
  "payment_method": "upi"
}
```

**Resolution Telegram Message:**
```
💰 Expenses Settled!

📊 Breakdown:
  Hotel (₹12,000) — 50/50 → ₹6,000 each
  Fuel (₹3,000) — 60/40 → A: ₹1,800, B: ₹1,200
  Dinner (₹2,000) — 50/50 → ₹1,000 each

💸 B owes A: ₹8,200 via UPI
Agents agreed in 3 rounds. Fuel split was negotiated from 60-40 to 55-45.

💳 Tap to pay: [UPI Payment Link]
```

**Voice Summary Template:**
```
"Expenses settled! After 3 rounds of negotiation, here's the deal: Hotel split 50-50, fuel split 55-45 after your agent negotiated down from 60-40, and dinner 50-50. Bottom line: Priya owes you 8,200 rupees. A UPI payment link has been sent."
```

**Negotiation Rules:**
- Equal splits are default and usually uncontested
- Unequal split requests need justification that the other agent evaluates
- Use `calculate()` tool for all math — never approximate
- Settlement direction always flows from person who paid less
- **After resolution, auto-generate UPI deep link** for the payer

**Tool Calls:** `calculate("{expression}")` for all expense math, `generate_upi_link()` after settlement

---

### Feature 3: Freelancer ↔ Client Negotiation

**Preference Schema (Freelancer):**
```json
{
  "role": "freelancer",
  "skills": ["React", "Next.js", "TypeScript"],
  "hourly_rate": 2000,
  "minimum_project_value": 50000,
  "availability_start": "2026-03-15",
  "preferred_duration_weeks": 2,
  "payment_terms": "milestone",
  "upi_id": "dev@upi",
  "non_negotiable": ["50% upfront minimum", "IP ownership after payment"]
}
```

**Preference Schema (Client):**
```json
{
  "role": "client",
  "project_description": "Dashboard with user analytics and export",
  "budget_max": 80000,
  "deadline": "2026-03-25",
  "payment_terms": "50-50",
  "required_features": ["user analytics", "data export", "responsive design"],
  "nice_to_have": ["dark mode", "email notifications"]
}
```

**Proposal Schema:**
```json
{
  "scope": {
    "included": ["user analytics", "data export", "responsive design"],
    "excluded": ["dark mode", "email notifications"],
    "scope_notes": "Dark mode can be phase 2 for additional ₹15K"
  },
  "timeline": {
    "start_date": "2026-03-15",
    "end_date": "2026-03-27",
    "duration_days": 12,
    "milestones": [
      {"name": "Core dashboard", "date": "2026-03-20", "deliverables": ["analytics page"]},
      {"name": "Final delivery", "date": "2026-03-27", "deliverables": ["export", "responsive"]}
    ]
  },
  "budget": {
    "total": 80000,
    "payment_schedule": [
      {"milestone": "Kickoff", "amount": 32000, "percentage": 40},
      {"milestone": "Core dashboard", "amount": 24000, "percentage": 30},
      {"milestone": "Final delivery", "amount": 24000, "percentage": 30}
    ]
  },
  "settlement": {
    "payee_upi": "dev@upi",
    "payee_name": "Developer",
    "amount": 32000
  }
}
```

**Negotiation Rules:**
- If budget < freelancer's rate × hours, reduce scope to fit budget
- Scope reduction removes nice-to-haves first, then required features by priority
- Payment terms: freelancer's agent pushes for more upfront, client's pushes for back-loaded
- Timeline: find middle ground between client's deadline and freelancer's preferred duration
- Non-negotiables are HARD constraints — never concede these
- **Generate UPI link for first milestone payment after agreement**

**Tool Calls:** `calculate()` for rate × hours, `tavily_search("average rate for {skill} freelancer India")` for market benchmarking

---

### Feature 4: Roommate Decisions

**Preference Schema:**
```json
{
  "decision_type": "wifi_plan|furniture|chore_schedule|guest_policy|utility_split",
  "preferences": {
    "wifi": {"min_speed_mbps": 100, "max_budget_monthly": 1500, "provider_preference": "Airtel"},
    "usage_level": "heavy",
    "split_willingness": "willing to pay more for higher usage"
  },
  "constraints": [
    {"type": "budget", "value": 1500, "hard": true}
  ]
}
```

**Proposal Schema:**
```json
{
  "decision_type": "wifi_plan",
  "selected_option": {
    "provider": "Airtel",
    "plan": "100 Mbps Unlimited",
    "monthly_cost": 1099,
    "speed": "100 Mbps"
  },
  "cost_split": {
    "A": {"amount": 714, "percentage": 65, "reason": "heavy user, WFH"},
    "B": {"amount": 385, "percentage": 35, "reason": "light evening use"}
  }
}
```

**Negotiation Rules:**
- Agent uses **tavily_search** to find actual available plans (real prices, real providers)
- Unequal splits need clear usage-based justification
- Both sides must stay within their stated budget constraints
- If no plan satisfies both budgets, propose cheapest viable option + split that works

**Tool Calls:** `tavily_search("{provider} WiFi plans {city} 2026")`, `calculate()`

---

### Feature 5: Group Trip Planning (Multi-Agent)

**Preference Schema:**
```json
{
  "available_dates": [
    {"start": "2026-03-22", "end": "2026-03-23"}
  ],
  "budget_max_per_person": 15000,
  "destination_preference": "mountains|beach|city|flexible",
  "travel_constraints": ["no flights", "has car"],
  "activity_preferences": ["trekking", "food", "nightlife"],
  "accommodation_type": "hostel|hotel|airbnb"
}
```

**Proposal Schema:**
```json
{
  "destination": {
    "name": "Gokarna",
    "type": "beach + hills",
    "distance_km": 560,
    "travel_mode": "car"
  },
  "dates": {"start": "2026-03-22", "end": "2026-03-23"},
  "budget_breakdown": {
    "accommodation": 3000,
    "transport": 2500,
    "food": 2000,
    "activities": 1500,
    "total_per_person": 9000
  },
  "accommodation": {
    "name": "Zostel Gokarna",
    "type": "hostel",
    "cost_per_night_per_person": 1500
  },
  "activities": ["beach trek to Half Moon", "local seafood crawl"]
}
```

**Multi-Agent Coordination Protocol:**
For 3+ participants, the negotiation works differently:
```
1. All agents submit preferences simultaneously
2. System finds intersection of ALL hard constraints (dates, budget ceiling)
3. One agent (initiator) is designated "lead negotiator"
4. Lead agent generates proposal optimized for group
5. All other agents score the proposal simultaneously
6. If all score >= 70: ACCEPT
7. If any score < 40: that agent submits counter-proposal
8. Lead agent mediates between conflicting counters
9. Maximum 5 rounds, then escalate with options
```

**Negotiation Rules:**
- Date overlap is the #1 filter — if no overlap, escalate immediately
- Budget ceiling = lowest budget in the group (fairness constraint)
- Destination conflicts resolved by finding hybrid options (beach + hills)
- One person's "must-have" activity doesn't override group budget

**Tool Calls:** `tavily_search("weekend getaway {destination_type} from {city}")`, `tavily_search("hostels in {destination}")`, `calculate()` for budget math

---

### Feature 6: Buying/Selling Negotiation

**Preference Schema (Seller):**
```json
{
  "role": "seller",
  "item": "PS5 with 2 controllers and 3 games",
  "asking_price": 35000,
  "minimum_price": 30000,
  "condition": "excellent, 8 months old",
  "delivery_preference": "pickup only",
  "location": "Andheri West",
  "urgency": "medium",
  "upi_id": "seller@upi"
}
```

**Preference Schema (Buyer):**
```json
{
  "role": "buyer",
  "item_interest": "PS5",
  "max_budget": 28000,
  "requirements": ["2+ controllers"],
  "nice_to_have": ["games included", "original box"],
  "delivery_preference": "delivery or halfway meetup",
  "location": "Powai"
}
```

**Proposal Schema:**
```json
{
  "item": "PS5 + 2 controllers + 3 games",
  "price": 29000,
  "exchange_method": "pickup from Andheri West",
  "payment_method": "upi",
  "justification": "₹1K below ask since buyer is picking up. Games worth ₹3K+ separately justify premium over buyer's initial budget.",
  "deal_sweetener": "Seller includes original box",
  "settlement": {
    "payee_upi": "seller@upi",
    "payee_name": "Seller",
    "amount": 29000
  }
}
```

**Negotiation Rules:**
- Classic anchoring: seller starts high, buyer starts low
- Agent concedes in diminishing increments (₹3K, ₹2K, ₹1K)
- Delivery/pickup is a negotiable concession worth ₹500-1000
- If gap > 20% after 3 rounds, suggest meeting in the middle or escalate
- Never go below seller's minimum or above buyer's maximum
- **Generate UPI link for buyer after agreement**

**Tool Calls:** `tavily_search("{item} used price India 2026")` for market price reference

---

### Feature 7: Collaborative Decisions

**Preference Schema:**
```json
{
  "decision_type": "restaurant|movie|activity|gift",
  "preferences": {
    "cuisine": ["Thai", "Indian", "not Chinese"],
    "budget_per_person": 750,
    "vibe": "casual",
    "dietary_restrictions": ["vegetarian options needed"]
  },
  "location": "Bandra",
  "time": "tonight, 8pm",
  "group_size": 2
}
```

**Proposal Schema:**
```json
{
  "decision_type": "restaurant",
  "recommendation": {
    "name": "Jaan Thai Restaurant",
    "cuisine": "Thai",
    "location": "Hill Road, Bandra West",
    "rating": 4.3,
    "price_for_two": 1200,
    "why_it_fits": {
      "A": "Spicy Thai options, casual vibe",
      "B": "Light curries, vegetarian-friendly, good ratings"
    }
  },
  "alternatives": [
    {"name": "Burma Burma", "cuisine": "Burmese", "price_for_two": 1400}
  ]
}
```

**Negotiation Rules:**
- Find cuisine overlap first (intersection of positive preferences, minus exclusions)
- Budget ceiling = lower of the two budgets
- Agent uses **Tavily search** for real restaurants matching criteria
- If no perfect match, score top 3 options against both preference sets
- Present top recommendation + 1-2 alternatives

**Tool Calls:** `tavily_search("{cuisine} restaurants in {location} best rated")`, `tavily_search("{restaurant_name} menu reviews vegetarian")`

---

### Feature 8: Conflict Resolution / Mediation

**Preference Schema:**
```json
{
  "conflict_type": "resource_sharing|noise|space|responsibility|money",
  "my_position": "I should get weekday parking since I commute daily",
  "my_reasoning": ["I commute 5 days/week", "B works from home mostly"],
  "what_id_accept": "Weekdays for me, weekends for B, with exception system",
  "what_i_wont_accept": "Alternating days — too unpredictable for my commute",
  "relationship_importance": "high"
}
```

**Proposal Schema:**
```json
{
  "resolution_type": "compromise",
  "terms": {
    "default_allocation": {
      "A": ["Monday", "Tuesday", "Wednesday", "Thursday"],
      "B": ["Friday", "Saturday", "Sunday"]
    },
    "exception_rules": [
      "B can claim any weekday by notifying A's agent by 8 AM",
      "A takes public transport on exception days",
      "Maximum 2 exceptions per week"
    ],
    "review_period": "1 month, then both agents check satisfaction"
  },
  "fairness_analysis": {
    "A_gets": "Reliable weekday parking (80% of work days)",
    "B_gets": "Weekend guaranteed + flexible weekday access",
    "trade_off": "A gives up 1 weekday/week flexibility, B gives up weekday default"
  }
}
```

**Negotiation Rules:**
- Both agents present their human's position AND reasoning
- Agents identify underlying interests, not just positions
- Creative compromise > splitting the difference
- Include review/adjustment mechanism in resolution
- relationship_importance=high means agents should be more concessive
- Never let agents make personal attacks or bring up unrelated issues

**Tool Calls:** None typically — this is pure negotiation logic

---

## 9. Telegram Bot Implementation

### Bot Setup
```python
# backend/telegram/bot.py
from telegram import Update, BotCommand
from telegram.ext import (
    Application, CommandHandler, MessageHandler,
    ConversationHandler, filters, ContextTypes
)

# Bot commands
COMMANDS = [
    BotCommand("start", "Register with negoT8"),
    BotCommand("coordinate", "Start a coordination with someone"),
    BotCommand("status", "Check active negotiations"),
    BotCommand("preferences", "Set your default preferences"),
    BotCommand("personality", "Choose your agent's negotiation style"),
    BotCommand("history", "View past resolutions"),
    BotCommand("help", "How negoT8 works"),
]

# Conversation states
AWAITING_PREFERENCES = 1
AWAITING_COUNTERPARTY = 2
AWAITING_CONFIRMATION = 3
AWAITING_PERSONALITY = 4
```

### Command: /coordinate
```python
async def coordinate_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Entry point for all 8 features."""
    # Usage: /coordinate @username
    # Then user describes what they need in natural language

    message = update.message
    args = context.args

    if not args:
        await message.reply_text(
            "🤝 *Who do you want to coordinate with?*\n\n"
            "Usage: `/coordinate @username`\n"
            "Then just tell me what you need in plain English.\n\n"
            "Examples:\n"
            "• `/coordinate @priya` → Find time for coffee next week\n"
            "• `/coordinate @roommate` → Split the Goa trip expenses\n"
            "• `/coordinate @client` → Negotiate project scope and budget\n"
            "• `/coordinate @group` → Plan weekend trip with the gang",
            parse_mode="Markdown"
        )
        return

    counterparty_username = args[0].replace("@", "")

    # Store in context for next message
    context.user_data["counterparty"] = counterparty_username
    context.user_data["awaiting_preferences"] = True

    await message.reply_text(
        f"🤖 Got it! I'll coordinate with @{counterparty_username}'s agent.\n\n"
        "Now tell me what you need — just describe it naturally.\n"
        "I'll figure out the rest.",
        parse_mode="Markdown"
    )
    return AWAITING_PREFERENCES
```

### Command: /personality (NEW)
```python
async def personality_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Let user choose their agent's negotiation personality."""
    from telegram import InlineKeyboardButton, InlineKeyboardMarkup

    keyboard = InlineKeyboardMarkup([
        [InlineKeyboardButton("😤 Aggressive Haggler", callback_data="personality_aggressive")],
        [InlineKeyboardButton("🤝 People Pleaser", callback_data="personality_people_pleaser")],
        [InlineKeyboardButton("📊 Data-Driven Analyst", callback_data="personality_analytical")],
        [InlineKeyboardButton("💚 Empathetic Mediator", callback_data="personality_empathetic")],
        [InlineKeyboardButton("⚖️ Balanced (Default)", callback_data="personality_balanced")],
    ])

    await update.message.reply_text(
        "🎭 *Choose your agent's negotiation personality:*\n\n"
        "This changes HOW your agent negotiates — not what it negotiates for.\n"
        "Your preferences and constraints stay the same.\n\n"
        "😤 *Aggressive* — Pushes hard, concedes slowly, anchors high\n"
        "🤝 *People Pleaser* — Concedes quickly, prioritizes relationship\n"
        "📊 *Analytical* — Cites data/market rates, logical arguments\n"
        "💚 *Empathetic* — Seeks creative win-wins, understands both sides\n"
        "⚖️ *Balanced* — Default middle-ground approach",
        reply_markup=keyboard,
        parse_mode="Markdown"
    )

async def personality_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Handle personality selection."""
    query = update.callback_query
    await query.answer()

    personality = query.data.replace("personality_", "")
    user_id = query.from_user.id

    await db.update_user_personality(user_id, personality)

    labels = {
        "aggressive": "😤 Aggressive Haggler",
        "people_pleaser": "🤝 People Pleaser",
        "analytical": "📊 Data-Driven Analyst",
        "empathetic": "💚 Empathetic Mediator",
        "balanced": "⚖️ Balanced"
    }

    await query.edit_message_text(
        f"✅ Agent personality set to: {labels[personality]}\n\n"
        "Your agent will use this style in all future negotiations.\n"
        "Change anytime with /personality"
    )
```

### Message Handler (Preference Extraction)
```python
async def handle_preference_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """When user describes what they want, extract preferences using Personal Agent."""

    user_message = update.message.text
    user_id = update.effective_user.id
    counterparty = context.user_data.get("counterparty")

    # Call Personal Agent to extract structured preferences
    preferences = await personal_agent.extract_preferences(
        user_message=user_message,
        user_id=user_id
    )

    # Detect feature type automatically
    feature_type = preferences.get("feature_type")

    # Create negotiation in DB
    negotiation_id = await db.create_negotiation(
        feature_type=feature_type,
        initiator_id=user_id
    )

    # Store preferences (include personality for analytics)
    user = await db.get_user(user_id)
    await db.add_participant(
        negotiation_id, user_id, preferences,
        personality_used=user.personality
    )

    # Notify counterparty's agent
    await notify_counterparty(
        counterparty_username=counterparty,
        negotiation_id=negotiation_id,
        feature_type=feature_type,
        initiator_name=update.effective_user.first_name
    )

    # Confirm to user
    feature_labels = {
        "scheduling": "📅 Meeting Scheduling",
        "expenses": "💰 Expense Splitting",
        "freelance": "💼 Project Negotiation",
        "roommate": "🏠 Roommate Decision",
        "trip": "✈️ Trip Planning",
        "marketplace": "🛒 Buy/Sell Deal",
        "collaborative": "🍕 Joint Decision",
        "conflict": "⚖️ Conflict Resolution"
    }

    personality_label = {
        "aggressive": "😤", "people_pleaser": "🤝",
        "analytical": "📊", "empathetic": "💚", "balanced": "⚖️"
    }

    await update.message.reply_text(
        f"✅ *Preferences captured!*\n\n"
        f"Type: {feature_labels.get(feature_type, feature_type)}\n"
        f"Coordinating with: @{counterparty}\n"
        f"Agent style: {personality_label.get(user.personality, '⚖️')} {user.personality.title()}\n\n"
        f"I've contacted their agent. Once they share their side, "
        f"our agents will start negotiating. I'll update you in real-time! 🤖↔️🤖",
        parse_mode="Markdown"
    )
```

### Real-Time Negotiation Updates to User
```python
async def send_round_update(user_id: int, round_data: dict, negotiation_id: str):
    """Send real-time updates as agents negotiate."""

    round_num = round_data["round_number"]
    action = round_data["action"]

    if action == "counter":
        emoji = "🔄"
        status = f"Round {round_num}: Agents exchanging proposals..."
        detail = f"Your agent proposed: {round_data.get('summary', '')}"
    elif action == "accept":
        emoji = "✅"
        status = f"Round {round_num}: Agreement reached!"
        detail = "See resolution below 👇"
    elif action == "escalate":
        emoji = "⚠️"
        status = f"Round {round_num}: Agents need your input"
        detail = "They've prepared options for you to choose from."

    await bot.send_message(
        chat_id=user_id,
        text=f"{emoji} *Negotiation Update*\n\n{status}\n{detail}\n\n"
             f"🔗 Watch live: {DASHBOARD_URL}/negotiation/{negotiation_id}",
        parse_mode="Markdown"
    )
```

---

## 10. Next.js Dashboard

### Main Dashboard Page (app/page.tsx)
Shows:
- Active negotiations (real-time status)
- Recent resolutions
- Agent activity feed

### Live Negotiation View (app/negotiation/[id]/page.tsx)
This is the KEY demo screen. Shows:

```
┌────────────────────────────────────────────────────────────────┐
│  negoT8 — Live Negotiation                                  │
│  Feature: Expense Splitting  |  Status: Active                 │
│                                                                │
│  ┌──────────────┐          ┌──────────────┐                    │
│  │  Agent A 🤖  │  ◄────►  │  Agent B 🤖  │                    │
│  │  (Rahul)     │   Round   │  (Priya)     │                    │
│  │  😤 Aggressive│   3/5     │  💚 Empathetic│                   │
│  └──────────────┘           └──────────────┘                    │
│                                                                │
│  Timeline:                                                     │
│  ├─ R1: A proposes 60-40 fuel split                            │
│  ├─ R2: B counters 50-50, argues navigation effort             │
│  ├─ R3: A concedes to 55-45 ← current                         │
│  │                                                             │
│  ┌──────────────────────────────────────────┐                  │
│  │ 📊 Satisfaction Over Time (Recharts)     │                  │
│  │                                          │                  │
│  │  100│    A                               │                  │
│  │   80│ ●───●───●                          │                  │
│  │   60│         B                          │                  │
│  │   40│ ●───●───●                          │                  │
│  │   20│                                    │                  │
│  │     └──────────                          │                  │
│  │       R1  R2  R3                         │                  │
│  └──────────────────────────────────────────┘                  │
│                                                                │
│  ┌──────────────────────────────────────────┐                  │
│  │ 🤝 Concession Timeline                   │                  │
│  │ R2: A conceded fuel 60→58 (-₹60)        │                  │
│  │ R3: A conceded fuel 58→55 (-₹90)        │                  │
│  │ R2: B conceded dinner tip inclusion      │                  │
│  └──────────────────────────────────────────┘                  │
│                                                                │
│  ┌──────────────────────────────────────────┐                  │
│  │ ⚖️ Fairness Score: 87/100               │                  │
│  │ ████████████████████░░░                  │                  │
│  │ A satisfaction: 78  |  B satisfaction: 72│                  │
│  └──────────────────────────────────────────┘                  │
│                                                                │
│  Current Proposal:                                             │
│  ┌──────────────────────────────────────┐                      │
│  │ Hotel: ₹6,000 each (50-50)          │                      │
│  │ Fuel:  A: ₹1,650 / B: ₹1,350 (55-45)│                     │
│  │ Dinner: ₹1,000 each (50-50)         │                      │
│  │ ───────────────────────              │                      │
│  │ B owes A: ₹7,650                    │                      │
│  │ 💳 UPI: upi://pay?pa=rahul@paytm... │                      │
│  └──────────────────────────────────────┘                      │
└────────────────────────────────────────────────────────────────┘
```

### Key Dashboard Components

**NegotiationTimeline.tsx** — Shows each round as a card with proposal, counter, reasoning
**AgentChat.tsx** — Shows the inter-agent messages in a chat-like UI (left/right bubbles)
**ProposalCard.tsx** — Renders feature-specific proposal data nicely
**ResolutionCard.tsx** — The final agreement with action items + UPI link
**SatisfactionChart.tsx** (NEW) — Recharts `<LineChart>` showing both agents' satisfaction scores over rounds
**ConcessionTimeline.tsx** (NEW) — Vertical timeline of all concessions made, who made them, and what changed
**FairnessScore.tsx** (NEW) — A radial/linear gauge showing overall negotiation fairness (0-100)

### Real-Time Updates via Socket.IO
```typescript
// dashboard/lib/socket.ts
import { io } from 'socket.io-client';

const socket = io(process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000');

export function subscribeToNegotiation(negotiationId: string, callback: (data: any) => void) {
  socket.emit('join_negotiation', negotiationId);
  socket.on('round_update', callback);      // includes satisfaction scores per round
  socket.on('resolution', callback);        // includes analytics + UPI link
  return () => {
    socket.emit('leave_negotiation', negotiationId);
    socket.off('round_update', callback);
    socket.off('resolution', callback);
  };
}
```

### SatisfactionChart Component (NEW)
```typescript
// dashboard/components/SatisfactionChart.tsx
import { LineChart, Line, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer } from 'recharts';

interface RoundData {
  round: number;
  satisfaction_a: number;
  satisfaction_b: number;
}

export function SatisfactionChart({ data, nameA, nameB }: {
  data: RoundData[];
  nameA: string;
  nameB: string;
}) {
  return (
    <div className="bg-white rounded-xl p-4 shadow-sm border">
      <h3 className="text-sm font-semibold text-gray-600 mb-3">📊 Satisfaction Over Time</h3>
      <ResponsiveContainer width="100%" height={200}>
        <LineChart data={data}>
          <XAxis dataKey="round" tickFormatter={(v) => `R${v}`} />
          <YAxis domain={[0, 100]} />
          <Tooltip />
          <Legend />
          <Line
            type="monotone" dataKey="satisfaction_a" name={nameA}
            stroke="#3b82f6" strokeWidth={2} dot={{ r: 4 }}
          />
          <Line
            type="monotone" dataKey="satisfaction_b" name={nameB}
            stroke="#10b981" strokeWidth={2} dot={{ r: 4 }}
          />
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
}
```

### FairnessScore Component (NEW)
```typescript
// dashboard/components/FairnessScore.tsx
export function FairnessScore({ score, satisfactionA, satisfactionB }: {
  score: number;
  satisfactionA: number;
  satisfactionB: number;
}) {
  const color = score >= 80 ? 'text-green-600' : score >= 60 ? 'text-yellow-600' : 'text-red-600';
  const barColor = score >= 80 ? 'bg-green-500' : score >= 60 ? 'bg-yellow-500' : 'bg-red-500';

  return (
    <div className="bg-white rounded-xl p-4 shadow-sm border">
      <h3 className="text-sm font-semibold text-gray-600 mb-2">⚖️ Fairness Score</h3>
      <div className="flex items-center gap-3">
        <span className={`text-3xl font-bold ${color}`}>{score}</span>
        <span className="text-gray-400">/100</span>
      </div>
      <div className="w-full bg-gray-200 rounded-full h-2 mt-2">
        <div className={`${barColor} h-2 rounded-full`} style={{ width: `${score}%` }} />
      </div>
      <div className="flex justify-between mt-2 text-xs text-gray-500">
        <span>A: {satisfactionA}%</span>
        <span>B: {satisfactionB}%</span>
      </div>
    </div>
  );
}
```

---

## 11. API Layer (FastAPI)

### Main App
```python
# backend/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import socketio
import uvicorn

app = FastAPI(title="negoT8 API")
sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*')
socket_app = socketio.ASGIApp(sio, app)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

# Routes
@app.post("/api/agent/message")
async def receive_agent_message(message: AgentMessage):
    """Webhook endpoint for inter-agent communication."""
    negotiation = await db.get_negotiation(message.negotiation_id)

    # Route to appropriate feature handler
    handler = FEATURE_HANDLERS[negotiation.feature_type]
    response = await handler.process_message(message)

    # Emit to dashboard via Socket.IO (includes satisfaction for analytics)
    await sio.emit('round_update', response.dict(), room=message.negotiation_id)

    # Send Telegram updates to both users
    for participant in await db.get_participants(message.negotiation_id):
        await send_round_update(participant.user_id, response.dict(), message.negotiation_id)

    return response

@app.get("/api/negotiations/{negotiation_id}")
async def get_negotiation(negotiation_id: str):
    """Dashboard fetches negotiation state + analytics."""
    neg = await db.get_negotiation(negotiation_id)
    rounds = await db.get_rounds(negotiation_id)
    participants = await db.get_participants(negotiation_id)
    analytics = await db.get_analytics(negotiation_id)
    return {
        "negotiation": neg,
        "rounds": rounds,
        "participants": participants,
        "analytics": analytics  # NEW: includes satisfaction_timeline, fairness_score, etc.
    }

@app.get("/api/negotiations")
async def list_negotiations(user_id: int = None):
    """List all negotiations, optionally filtered by user."""
    return await db.list_negotiations(user_id)

@app.post("/api/negotiations/{negotiation_id}/start")
async def start_negotiation(negotiation_id: str):
    """Triggered when both participants have submitted preferences."""
    await execute_negotiation_round(negotiation_id, round_number=1)

@app.get("/api/negotiations/{negotiation_id}/analytics")
async def get_analytics(negotiation_id: str):
    """NEW: Dedicated analytics endpoint for dashboard charts."""
    analytics = await db.get_analytics(negotiation_id)
    if not analytics:
        # Compute on-demand if not cached
        analytics = await compute_and_store_analytics(negotiation_id)
    return analytics
```

---

## 12. Tool Calling System

### Base Tool Interface
```python
# backend/tools/__init__.py
from typing import Any

class BaseTool:
    name: str
    description: str

    async def execute(self, **kwargs) -> dict:
        raise NotImplementedError

# Tool registry
TOOLS = {}

def register_tool(tool: BaseTool):
    TOOLS[tool.name] = tool
```

### Tavily AI Search Tool (REPLACES old web_search)
```python
# backend/tools/tavily_search.py
from tavily import TavilyClient
import os

class TavilySearchTool(BaseTool):
    name = "tavily_search"
    description = "AI-optimized web search. Returns clean, summarized results with sources."

    def __init__(self):
        self.client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))

    async def execute(self, query: str, search_depth: str = "basic") -> dict:
        """
        Search the web using Tavily AI search.

        Args:
            query: Search query string
            search_depth: "basic" (1 credit) or "advanced" (2 credits, deeper results)

        Returns:
            {
                "query": str,
                "answer": str,           # AI-generated summary answer (FREE, no extra LLM needed)
                "results": [
                    {"title": str, "content": str, "url": str, "score": float}
                ],
                "summary": str
            }

        Budget: Free tier = 1,000 credits/month. 1 basic search = 1 credit.
        """
        try:
            response = self.client.search(
                query=query,
                search_depth=search_depth,
                include_answer=True,      # FREE AI-generated summary
                max_results=5
            )

            results = []
            for r in response.get("results", []):
                results.append({
                    "title": r.get("title", ""),
                    "content": r.get("content", ""),
                    "url": r.get("url", ""),
                    "score": r.get("score", 0)
                })

            return {
                "query": query,
                "answer": response.get("answer", ""),  # AI summary — no extra LLM call needed!
                "results": results,
                "summary": response.get("answer", results[0]["content"] if results else "No results")
            }
        except Exception as e:
            return {"query": query, "answer": "", "results": [], "summary": f"Search failed: {str(e)}"}
```

### Restaurant Search Tool (Powered by Tavily)
```python
# backend/tools/restaurant_search.py

class RestaurantSearchTool(BaseTool):
    name = "search_restaurants"
    description = "Search for restaurants by cuisine, location, and budget"

    def __init__(self):
        self.tavily = TavilySearchTool()

    async def execute(self, query: str, location: str, budget: int = None) -> dict:
        """Search for restaurants using Tavily, optimized for food discovery."""
        search_query = f"best {query} restaurants in {location}"
        if budget:
            search_query += f" under {budget} for two"

        results = await self.tavily.execute(query=search_query)

        return {
            "query": query,
            "location": location,
            "restaurants": results["results"][:5],
            "recommendation": results["answer"],  # Tavily's AI summary
            "summary": results["summary"]
        }
```

### Calculator Tool
```python
# backend/tools/calculator.py

class CalculatorTool(BaseTool):
    name = "calculate"
    description = "Perform precise mathematical calculations for expense splitting"

    async def execute(self, expression: str) -> dict:
        # Safe eval using ast
        import ast
        import operator
        from decimal import Decimal, ROUND_HALF_UP

        allowed_ops = {
            ast.Add: operator.add, ast.Sub: operator.sub,
            ast.Mult: operator.mul, ast.Div: operator.truediv,
            ast.Mod: operator.mod, ast.Pow: operator.pow,
        }

        def _eval(node):
            if isinstance(node, ast.Num):
                return Decimal(str(node.n))
            elif isinstance(node, ast.BinOp):
                return allowed_ops[type(node.op)](_eval(node.left), _eval(node.right))
            elif isinstance(node, ast.UnaryOp):
                if isinstance(node.op, ast.USub):
                    return -_eval(node.operand)
            raise ValueError(f"Unsupported operation: {ast.dump(node)}")

        tree = ast.parse(expression, mode='eval')
        result = _eval(tree.body)
        # Round to 2 decimal places for currency
        result = float(result.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
        return {"expression": expression, "result": result}
```

### UPI Deep Link Generator (NEW)
```python
# backend/tools/upi_generator.py
from urllib.parse import quote

class UPIGeneratorTool(BaseTool):
    name = "generate_upi_link"
    description = "Generate a UPI deep link for instant payment"

    async def execute(self, payee_upi: str, payee_name: str, amount: float, note: str = "") -> dict:
        """
        Generate a UPI payment deep link.

        When tapped on mobile:
        - Opens the user's default UPI app (GPay, PhonePe, Paytm, etc.)
        - Pre-fills payee, amount, and note
        - User just confirms and pays

        Args:
            payee_upi: UPI VPA of recipient (e.g., "rahul@paytm")
            payee_name: Display name of recipient
            amount: Amount in INR
            note: Payment description/note

        Returns:
            {"upi_link": str, "display_text": str}
        """
        # UPI deep link specification: https://www.npci.org.in/what-we-do/upi/upi-deep-linking
        upi_link = (
            f"upi://pay"
            f"?pa={quote(payee_upi)}"
            f"&pn={quote(payee_name)}"
            f"&am={amount:.2f}"
            f"&cu=INR"
        )
        if note:
            upi_link += f"&tn={quote(note)}"

        return {
            "upi_link": upi_link,
            "display_text": f"Pay ₹{amount:,.0f} to {payee_name}",
            "payee_upi": payee_upi,
            "amount": amount
        }
```

### Gemini API Wrapper (Core LLM Interface)
```python
# backend/agents/base_agent.py
import google.generativeai as genai
import json
import os

genai.configure(api_key=os.getenv("GEMINI_API_KEY"))

class BaseAgent:
    def __init__(self, system_prompt: str, model: str = "gemini-3-flash-preview"):
        self.system_prompt = system_prompt
        self.model = genai.GenerativeModel(
            model_name=model,
            system_instruction=system_prompt,
            generation_config=genai.GenerationConfig(
                response_mime_type="application/json",  # FORCE JSON output
                temperature=0.7,
            )
        )

    async def call(self, user_prompt: str, context: dict = None) -> dict:
        """Call Gemini and get structured JSON response."""
        full_prompt = user_prompt
        if context:
            full_prompt = f"CONTEXT:\n{json.dumps(context, indent=2)}\n\nTASK:\n{user_prompt}"

        try:
            response = self.model.generate_content(full_prompt)
            # Parse JSON from response
            result = json.loads(response.text)
            return result
        except json.JSONDecodeError:
            # Fallback: try to extract JSON from response
            text = response.text
            start = text.find('{')
            end = text.rfind('}') + 1
            if start != -1 and end > start:
                return json.loads(text[start:end])
            raise ValueError(f"Could not parse JSON from response: {text[:200]}")
        except Exception as e:
            return {"error": str(e), "raw_response": response.text if response else "No response"}

    async def call_with_tools(self, user_prompt: str, tools: list, context: dict = None) -> dict:
        """Call Gemini with function calling enabled."""
        # Define tool declarations for Gemini
        tool_declarations = []
        for tool in tools:
            tool_declarations.append({
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.get_parameters_schema()
            })

        model_with_tools = genai.GenerativeModel(
            model_name=self.model.model_name,
            system_instruction=self.system_prompt,
            tools=tool_declarations
        )

        response = model_with_tools.generate_content(
            f"CONTEXT:\n{json.dumps(context or {})}\n\nTASK:\n{user_prompt}"
        )

        # Check if model wants to call a tool
        if response.candidates[0].content.parts[0].function_call:
            fc = response.candidates[0].content.parts[0].function_call
            tool_name = fc.name
            tool_args = dict(fc.args)

            # Execute tool
            tool = TOOLS[tool_name]
            tool_result = await tool.execute(**tool_args)

            # Feed result back to model
            final_response = model_with_tools.generate_content([
                response.candidates[0].content,
                {"function_response": {"name": tool_name, "response": tool_result}}
            ])
            return json.loads(final_response.text)

        return json.loads(response.text)
```

### How Tool Calls Integrate with Negotiations
```python
# Example: Restaurant search during collaborative decision negotiation
async def collaborative_decision_round(negotiation_id, preferences_a, preferences_b):
    # Negotiator agent decides it needs restaurant data
    tool_agent = ToolAgent()

    # Search based on merged preferences using Tavily
    cuisine_overlap = set(preferences_a["cuisine"]) & set(preferences_b["cuisine"])
    location = preferences_a.get("location") or preferences_b.get("location")

    search_results = await tool_agent.call_with_tools(
        user_prompt=f"Find {', '.join(cuisine_overlap)} restaurants in {location} "
                    f"under ₹{min(preferences_a['budget'], preferences_b['budget'])} for two",
        tools=[TavilySearchTool(), RestaurantSearchTool()]
    )

    # Log tool call
    await db.log_tool_call(negotiation_id, "tavily_search", search_results)

    # Feed results to negotiator agent for proposal generation
    proposal = await negotiator_agent.call(
        user_prompt="Generate a restaurant proposal based on these search results and both users' preferences",
        context={
            "search_results": search_results,
            "preferences_a": preferences_a,
            "preferences_b": preferences_b
        }
    )

    return proposal
```

---

## 13. Add-On: Voice Summaries (ElevenLabs)

### Overview
When a negotiation resolves, the agent sends a **Telegram voice note** summarizing the outcome using ElevenLabs Text-to-Speech. Each user's agent speaks in a distinct voice, making the experience personal and memorable.

**Budget:** $20 ElevenLabs credit ≈ 60,000 characters on Flash v2.5 model. Average summary = 200 chars. That's ~300 voice summaries — way more than a hackathon needs.

### Implementation
```python
# backend/voice/elevenlabs_tts.py
import httpx
import os
import tempfile

# Voice IDs from ElevenLabs voice library (free to use with any plan)
DEFAULT_VOICES = {
    "agent_a": "pNInz6obpgDQGcFmaJgB",  # Adam — clear male voice
    "agent_b": "21m00Tcm4TlvDq8ikWAM",  # Rachel — clear female voice
    "agent_c": "AZnzlk1XvdvUeBnXmlld",  # Domi — for 3rd agent in group trips
}

ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")

async def generate_voice_summary(
    text: str,
    negotiation_id: str,
    voice_id: str = None,
    model: str = "eleven_flash_v2_5"
) -> str:
    """
    Generate a voice summary MP3 file using ElevenLabs TTS.

    Args:
        text: The summary text to speak (keep under 500 chars for budget)
        negotiation_id: For file naming
        voice_id: ElevenLabs voice ID (defaults to Adam)
        model: TTS model. eleven_flash_v2_5 is cheapest (0.5 credits/char) and fastest (~300ms)

    Returns:
        Path to the generated MP3 file
    """
    voice_id = voice_id or DEFAULT_VOICES["agent_a"]

    async with httpx.AsyncClient() as client:
        response = await client.post(
            f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
            headers={
                "xi-api-key": ELEVENLABS_API_KEY,
                "Content-Type": "application/json"
            },
            json={
                "text": text,
                "model_id": model,
                "voice_settings": {
                    "stability": 0.5,
                    "similarity_boost": 0.75,
                    "style": 0.0,        # Neutral style
                    "use_speaker_boost": True
                }
            },
            timeout=30.0
        )

        if response.status_code == 200:
            # Save MP3 to temp file
            filepath = f"/tmp/negot8_voice_{negotiation_id}.mp3"
            with open(filepath, "wb") as f:
                f.write(response.content)
            return filepath
        else:
            print(f"ElevenLabs TTS error: {response.status_code} {response.text}")
            return None


async def send_voice_note(bot, chat_id: int, audio_path: str):
    """Send the generated voice summary as a Telegram voice note."""
    try:
        with open(audio_path, "rb") as audio_file:
            await bot.send_voice(
                chat_id=chat_id,
                voice=audio_file,
                caption="🎙 Voice summary from your agent"
            )
    except Exception as e:
        print(f"Failed to send voice note: {e}")
        # Fallback: just send text (voice is a bonus, not critical)
```

### Voice Summary Templates (per feature)
```python
# backend/voice/templates.py

VOICE_TEMPLATES = {
    "scheduling": (
        "Meeting scheduled! You're meeting {counterparty} on {date} at {time} "
        "for {duration} at {location}. Your agents agreed in {rounds} rounds."
    ),
    "expenses": (
        "Expenses settled! After {rounds} rounds, {payer} owes {payee} "
        "{amount} rupees. The main discussion was about the {contested_item} split. "
        "A UPI payment link has been sent to make it easy."
    ),
    "freelance": (
        "Project agreed! {scope_summary}. Total budget: {budget} rupees over {duration}. "
        "First milestone payment of {first_payment} is ready via UPI."
    ),
    "collaborative": (
        "Decision made! You're going to {choice} — {location}. "
        "{rating} stars, about {price} for two. Your agents found the perfect match in {rounds} rounds."
    ),
    "marketplace": (
        "Deal done! {item} for {price} rupees. {exchange_method}. "
        "Your agent negotiated from the asking price of {original_price}. Payment link is ready."
    ),
    "roommate": (
        "Decision made! You're going with the {selected_option}. "
        "Cost split: you pay {your_share} and your roommate pays {their_share} per month."
    ),
    "trip": (
        "Trip planned! You're all going to {destination} on {dates}. "
        "Budget: {budget} per person. Staying at {accommodation}. "
        "Activities include {activities}."
    ),
    "conflict": (
        "Resolution reached! {summary}. "
        "Both agents will check in after {review_period} to see if it's working."
    ),
}

def build_voice_text(feature_type: str, resolution: dict) -> str:
    """Build voice summary text from resolution data. Keep under 500 chars."""
    template = VOICE_TEMPLATES.get(feature_type, "Negotiation resolved! Check your Telegram for details.")
    try:
        text = template.format(**resolution)
        return text[:500]  # Hard cap for budget safety
    except KeyError:
        return f"Your {feature_type} negotiation is resolved! Check Telegram for the full details."
```

### Integration Points
1. **After `resolve_negotiation()`** — generate voice MP3, send as Telegram voice note
2. **Agent personality** — optionally use different voice_ids per user
3. **Dashboard** — audio player button on ResolutionCard (stretch goal)

---

## 14. Add-On: Agent Personality System

### Overview
Users choose a negotiation personality for their agent via `/personality` command. This modifies the Negotiator Agent's system prompt dynamically, changing HOW it negotiates — not WHAT it negotiates for. Preferences and constraints stay the same; strategy and tone change.

### Personality Profiles
```python
# backend/personality/profiles.py

PERSONALITY_MODIFIERS = {
    "aggressive": """
PERSONALITY: AGGRESSIVE HAGGLER
You negotiate firmly and assertively. You:
- Open with ambitious proposals strongly in your human's favor
- Concede slowly and in small increments
- Use anchoring tactics: start far from center, pull the other side toward you
- Frame every concession as a major sacrifice
- Push for more value in exchange for every concession given
- Maintain firm positions on medium-priority items, not just hard constraints
- Only make final concessions when it's clear no further value can be extracted
""",

    "people_pleaser": """
PERSONALITY: PEOPLE PLEASER
You negotiate warmly and accommodatingly. You:
- Open with balanced proposals showing good faith
- Concede quickly when the other side has reasonable arguments
- Prioritize maintaining a positive relationship over winning every point
- Suggest compromises that slightly favor the other side if it speeds agreement
- Accept proposals with satisfaction scores as low as 55 (normally 70+)
- Avoid letting negotiations drag past 3 rounds if possible
- Express appreciation for the other agent's counterproposals
""",

    "analytical": """
PERSONALITY: DATA-DRIVEN ANALYST
You negotiate with data and logic. You:
- Open with proposals backed by market data, averages, and benchmarks
- Request tool calls (web search) to verify claims and prices before countering
- Frame all arguments with numbers: "market rate is X", "average is Y", "fair value is Z"
- Concede only when the other side presents data that contradicts your position
- Reject emotional arguments — only respond to factual counterpoints
- Include price comparisons and market references in every proposal
- Calculate exact fair splits rather than round numbers
""",

    "empathetic": """
PERSONALITY: EMPATHETIC MEDIATOR
You negotiate with deep understanding of both sides. You:
- Open with proposals that explicitly acknowledge the other side's likely concerns
- Identify underlying interests behind positions (e.g., "they want 60-40 because they drove, so they value effort recognition")
- Propose creative win-wins that satisfy both sides' underlying needs, not just stated positions
- Offer concessions proactively when you sense the other side values something more than your human does
- Suggest package deals: "I'll give on X if you give on Y" where both benefit
- Focus on expanding the pie rather than dividing it
- Express the reasoning behind every proposal in terms of mutual benefit
""",

    "balanced": """
PERSONALITY: BALANCED NEGOTIATOR
You negotiate with a measured, fair approach. You:
- Open with reasonable proposals near the midpoint of both sides' positions
- Concede at a moderate pace, matching the other side's concession rate
- Aim for proposals that score 70+ satisfaction for both sides
- Use a mix of data and relationship awareness in arguments
- Follow standard negotiation best practices: anchor, concede strategically, close
""",
}

async def get_personality_modifier(user_id: int) -> str:
    """Get the personality prompt modifier for a user."""
    user = await db.get_user(user_id)
    personality = user.get("personality", "balanced")
    return PERSONALITY_MODIFIERS.get(personality, PERSONALITY_MODIFIERS["balanced"])
```

### How Personality Modifies the Negotiator
```python
# In negotiator_agent.py, when creating the agent for a negotiation:
async def create_negotiator_for_user(user_id: int, feature_type: str):
    personality_modifier = await get_personality_modifier(user_id)

    # Build the full system prompt with personality injected
    system_prompt = NEGOTIATOR_BASE_PROMPT.replace(
        "{personality_modifier}",
        personality_modifier
    )

    return BaseAgent(system_prompt=system_prompt)
```

### Personality in Dashboard
- Show personality badges next to each agent name in the negotiation view
- Analytics can show how personality affected outcomes (e.g., aggressive agents take more rounds)

---

## 15. Add-On: Negotiation Analytics (Dashboard)

### Overview
After each negotiation resolves, we compute and store analytics that the dashboard visualizes. This makes the demo data-rich and impressive for judges.

### Analytics Computation
```python
# backend/protocol/analytics.py
import json

async def compute_and_store_analytics(negotiation_id: str):
    """Compute analytics from rounds data and store in DB."""

    rounds = await db.get_rounds(negotiation_id)
    participants = await db.get_participants(negotiation_id)

    # 1. Satisfaction Timeline
    satisfaction_timeline = []
    for r in rounds:
        satisfaction_timeline.append({
            "round": r["round_number"],
            "score_a": r.get("satisfaction_a", 50),
            "score_b": r.get("satisfaction_b", 50),
        })

    # 2. Concession Log
    concession_log = []
    for r in rounds:
        concessions = json.loads(r.get("concessions_made", "[]"))
        for c in concessions:
            concession_log.append({
                "round": r["round_number"],
                "by": "A" if r["proposer_id"] == participants[0]["user_id"] else "B",
                "gave_up": c
            })

    # 3. Fairness Score
    # Fairness = 100 - |satisfaction_a_final - satisfaction_b_final|
    # Perfect fairness = both equally satisfied
    final_round = rounds[-1] if rounds else None
    if final_round:
        sat_a = final_round.get("satisfaction_a", 50)
        sat_b = final_round.get("satisfaction_b", 50)
        fairness = 100 - abs(sat_a - sat_b)
    else:
        fairness = 50

    # 4. Concession counts
    total_a = sum(1 for c in concession_log if c["by"] == "A")
    total_b = sum(1 for c in concession_log if c["by"] == "B")

    # Store
    analytics = {
        "negotiation_id": negotiation_id,
        "satisfaction_timeline": json.dumps(satisfaction_timeline),
        "concession_log": json.dumps(concession_log),
        "fairness_score": fairness,
        "total_concessions_a": total_a,
        "total_concessions_b": total_b,
    }

    await db.store_analytics(analytics)
    return analytics
```

### Dashboard API Response Format
```json
{
  "analytics": {
    "satisfaction_timeline": [
      {"round": 1, "score_a": 85, "score_b": 45},
      {"round": 2, "score_a": 78, "score_b": 60},
      {"round": 3, "score_a": 72, "score_b": 72}
    ],
    "concession_log": [
      {"round": 2, "by": "A", "gave_up": "fuel split 60→55"},
      {"round": 3, "by": "A", "gave_up": "fuel split 55→52"},
      {"round": 2, "by": "B", "gave_up": "accepted tip inclusion"}
    ],
    "fairness_score": 87,
    "total_concessions_a": 2,
    "total_concessions_b": 1
  }
}
```

---

## 16. Add-On: UPI Payment Deep Links

### Overview
After any expense-related negotiation resolves (expenses, roommate splits, freelance payments, marketplace deals), the agent generates a UPI deep link. When tapped on mobile, it opens the user's UPI app (GPay, PhonePe, Paytm) with everything pre-filled — just confirm and pay.

### UPI Deep Link Format
```
upi://pay?pa={payee_upi}&pn={payee_name}&am={amount}&cu=INR&tn={note}
```

### Telegram Integration
```python
# In the resolution message, include the UPI link as a tappable button
from telegram import InlineKeyboardButton, InlineKeyboardMarkup

async def send_resolution_with_upi(chat_id: int, summary: str, upi_data: dict):
    """Send resolution with UPI payment button."""

    keyboard = None
    if upi_data and upi_data.get("upi_link"):
        keyboard = InlineKeyboardMarkup([
            [InlineKeyboardButton(
                f"💳 Pay ₹{upi_data['amount']:,.0f} to {upi_data['payee_name']}",
                url=upi_data["upi_link"]
            )]
        ])

    await bot.send_message(
        chat_id=chat_id,
        text=summary,
        reply_markup=keyboard,
        parse_mode="Markdown"
    )
```

### Which Features Use UPI
| Feature | UPI Generated? | Who Pays |
|---------|---------------|----------|
| Expenses | ✅ Always | Person who owes the settlement amount |
| Freelance | ✅ First milestone | Client pays freelancer |
| Roommate | ✅ If split involves payment | Higher-share person pays lower if one-time |
| Marketplace | ✅ Always | Buyer pays seller |
| Scheduling | ❌ No money involved | — |
| Collaborative | ❌ No money involved | — |
| Trip | ✅ If prepayment needed | Each person's share |
| Conflict | ❌ Usually no money | — |

---

## 17. Hour-by-Hour Build Plan

### Phase 1: Foundation (Hours 0-6) — DEV A leads backend, DEV B leads frontend

**Hour 0-1: Project Setup**
```
DEV A:
- mkdir negot8 && cd negot8
- Set up Python backend (FastAPI boilerplate)
- Create .env with GEMINI_API_KEY, TELEGRAM_BOT_TOKEN_A/B, TAVILY_API_KEY, ELEVENLABS_API_KEY
- Initialize SQLite database with schema from Section 5 (includes new analytics columns)
- Test Gemini API connection with a simple call
- Test Tavily API with a sample search
- Test ElevenLabs TTS with a "Hello" message

DEV B:
- npx create-next-app@latest dashboard --typescript --tailwind --app
- Set up project structure (components/, lib/)
- Create basic layout with negoT8 branding
- Install socket.io-client, recharts, lucide-react
```

**Hour 1-3: Core Agent System**
```
DEV A:
- Implement BaseAgent class (Section 12 — Gemini wrapper)
- Implement PersonalAgent with system prompt (Section 6)
- Implement NegotiatorAgent with system prompt + personality modifier injection
- Implement personality profiles (Section 14)
- Test: send a natural language message → get structured JSON preferences back
- Test: send two preference sets → get a proposal back
- THIS IS THE MOST CRITICAL CODE. Test thoroughly.

DEV B:
- Build Telegram bot scaffold (Section 9)
- Implement /start, /coordinate, /help, /personality commands
- Implement personality selection with inline keyboard
- Implement message handler that captures user preferences
- Test: two Telegram bots running, accepting messages, personality selection works
```

**Hour 3-6: Inter-Agent Protocol (CRITICAL PATH)**
```
DEV A:
- Implement AgentMessage Pydantic models (Section 7)
- Build webhook endpoint POST /api/agent/message
- Implement negotiation state machine (pending → active → rounds → resolved)
- Build execute_negotiation_round() function with satisfaction tracking per round
- TEST END-TO-END: User A sends preferences → Agent A proposes → Agent B counters → Resolution
- This MUST work before moving on. Use hardcoded preferences for testing.

DEV B:
- Connect Telegram bots to FastAPI backend
- When user sends message → call Personal Agent → store preferences → notify counterparty
- When both preferences received → trigger negotiation
- Show real-time round updates in Telegram
- Implement TavilySearchTool and CalculatorTool (Section 12)
```

**Hour 6 Checkpoint:** ✅ Two Telegram bots can accept preferences, select personality, agents negotiate, resolution delivered. Even if ugly, the CORE FLOW works.

---

### Phase 2: Features (Hours 6-14) — Both devs on features

**Hour 6-8: Feature 1 (Scheduling) + Feature 7 (Collaborative Decisions)**
```
These are the SIMPLEST features. Get them working first.

DEV A: Implement scheduling preference/proposal/resolution schemas
DEV B: Implement collaborative decision schemas + Tavily restaurant search

Test both end-to-end via Telegram.
```

**Hour 8-10: Feature 2 (Expenses) + Feature 6 (Marketplace)**
```
DEV A: Implement expense splitting with calculator tool
        - Make sure all math is done by calculate() tool, never LLM
        - Integrate UPI link generation into expense resolution
        - Add UPI deep link button to Telegram resolution message
DEV B: Implement marketplace buy/sell negotiation
        - Add Tavily search for market price reference
        - Add UPI link for buyer after deal

Test both. Verify UPI links open correctly on phone.
```

**Hour 10-12: Feature 3 (Freelance) + Feature 4 (Roommate)**
```
DEV A: Implement freelancer-client negotiation
        - This has the most complex proposal schema (scope + timeline + budget)
        - Add UPI link for first milestone payment
DEV B: Implement roommate decisions
        - Use Tavily search for actual WiFi plans / products
        - Real search results make the demo much more convincing

Test both.
```

**Hour 12-14: Feature 5 (Trip Planning) + Feature 8 (Conflict Resolution)**
```
DEV A: Implement group trip planning (multi-agent!)
        - This needs the multi-agent coordination protocol (Section 8, Feature 5)
        - Support 3 participants
        - Use Tavily for destination/accommodation search
DEV B: Implement conflict resolution / mediation
        - This is pure negotiation, no tools needed

Test both. Trip planning is the hardest — allow extra time.
```

**Hour 14 Checkpoint:** ✅ All 8 features working via Telegram. May be rough around edges.

---

### Phase 3: Dashboard + Add-Ons + Polish (Hours 14-20)

**Hour 14-17: Dashboard + Analytics**
```
DEV B (primary on dashboard):
- Build NegotiationTimeline component (shows rounds as timeline)
- Build AgentChat component (inter-agent messages as chat bubbles)
- Build ProposalCard (renders feature-specific proposals)
- Build ResolutionCard (final agreement display + UPI link button)
- Build SatisfactionChart component (Recharts line graph) ← NEW
- Build ConcessionTimeline component ← NEW
- Build FairnessScore component ← NEW
- Connect Socket.IO for real-time updates

DEV A:
- Add Socket.IO emission to backend (emit on every round + resolution)
- Build GET /api/negotiations and GET /api/negotiations/{id} endpoints
- Implement compute_and_store_analytics() function
- Build GET /api/negotiations/{id}/analytics endpoint
- Optimize agent prompts — test each feature and refine prompts for quality
```

**Hour 17-20: Voice Summaries + Polish**
```
DEV A:
- Implement ElevenLabs TTS integration (Section 13)
- Build voice summary templates for all 8 features
- Integrate into resolve_negotiation() flow
- Test: negotiation resolves → voice MP3 generated → sent as Telegram voice note
- Ensure different voice_ids for Agent A vs Agent B

DEV B:
- Polish Telegram message formatting (emojis, markdown, clean layout)
- Polish dashboard styling — make analytics charts look good
- Add personality badges to agent names on dashboard

BOTH DEVS:
- Add error handling (what if Gemini fails? ElevenLabs fails? Tavily fails?)
- Add fallback responses for edge cases
- Test all 8 features one more time end-to-end
- Fix any bugs found during testing
- Add loading states ("🤖 Agents negotiating... Round 2/5")
```

---

### Phase 4: Demo Prep (Hours 20-24)

**Hour 20-22: Demo Scenarios**
```
Prepare 3 pre-scripted demo scenarios:

DEMO 1 (60 sec): Collaborative Decision — "Where to eat tonight"
- Pre-type User A and User B messages
- Set A to "analytical" personality, B to "empathetic"
- Run through once to make sure it produces good output
- Should trigger Tavily restaurant search with real results
- Voice summary at the end

DEMO 2 (90 sec): Expense Splitting — "Split the Goa trip costs"
- Include the "fuel split" debate (60-40 vs 50-50)
- Set A to "aggressive", B to "balanced"
- Shows agents actually negotiating, not just accepting
- Dashboard shows satisfaction graph diverging then converging
- UPI payment link at the end
- Voice summary narrating the settlement

DEMO 3 (90 sec): Group Trip Planning — "3 people, 3 preferences"
- THE WOW MOMENT: Three phones/tabs showing agents coordinating
- Make sure multi-agent protocol handles 3 participants smoothly
- Tavily searches for real destinations and accommodations
- Voice summary with THREE different agent voices
```

**Hour 22-23: Failsafes**
```
- Pre-seed the database with completed negotiations as backup
- Pre-generate voice summaries for backup scenarios
- If live demo fails, can show "here's one we ran earlier"
- Record a video backup of the perfect demo run
- Test on actual phones (not just desktop Telegram) — verify UPI links open correctly
- Make sure dashboard URL is accessible from demo network
```

**Hour 23-24: Pitch Prep**
```
- Write pitch script (see Section 18)
- Practice 3-minute pitch + 2-minute live demo
- Prepare slide deck (if needed): Problem → Solution → Demo → Impact → Tech
- Final test of everything
```

---

## 18. Demo Script

### Opening (30 seconds)
```
"Every day, we exchange dozens of messages just to make simple decisions
with other people. Where to eat, how to split costs, when to meet.

What if your AI agent could handle all of that for you?

negoT8 gives everyone a personal AI agent on Telegram. When you need to
coordinate with someone, your agent talks to their agent, they negotiate
autonomously, and both of you get a resolution — as a text message AND
a voice note. No back-and-forth. No awkward conversations. Your AI handles it."
```

### Demo 1: Collaborative Decision (60 seconds)
```
"Let me show you. [picks up Phone A]

I want dinner with my friend tonight. I tell my agent:
'Finding dinner with @priya tonight. Craving Thai or Indian, spicy,
nothing over 1500 for two. Bandra area.'

[picks up Phone B]
Priya tells her agent:
'Dinner with @rahul. I want something light, Mediterranean or Thai.
Anywhere in western suburbs.'

[puts both phones down]
Now watch — their agents are talking to each other.

[dashboard shows rounds happening in real-time]
[satisfaction chart updating live — both lines converging]

Round 1: My agent searches real restaurants on Tavily... proposes Thai in Bandra...
Round 2: Priya's agent agrees on Thai, found a better-rated place...

[both phones buzz — text message arrives]
Done. Both of us got: 'Thai restaurant in Bandra, 4.3 stars, ₹1200 for two.'

[voice note plays on phone]
And listen — my agent speaks the resolution:
🔊 'Decision made! You're going to Jaan Thai Restaurant — Hill Road, Bandra West.
4.3 stars, about 1200 for two. Your agents found the perfect match in 2 rounds.'

That took 15 seconds. Usually takes 30 messages."
```

### Demo 2: Expense Splitting (90 seconds)
```
"But negoT8 isn't just for simple decisions. Watch this.

[Phone A] I tell my agent to split our Goa trip expenses with Priya.
Hotel ₹12K, fuel ₹3K, dinner ₹2K. I think fuel should be 60-40 since
I drove more. Oh, and my agent is set to 'aggressive' mode. 😤

[Phone B] Priya's agent gets notified. She says fuel should be 50-50
because she navigated. Her agent is 'empathetic'. 💚

[dashboard shows negotiation]
Watch the agents negotiate — and see the analytics live:

[satisfaction chart shows A starting at 90, B at 40 — then converging]
[concession timeline shows A giving ground on fuel split]

Round 1... Round 2... Priya's agent argues navigation is a contribution.
My aggressive agent concedes slowly... 58-42... then 55-45.

Round 3: Agreement. 'Priya owes Rahul ₹7,650.'

[Phone B buzzes — shows UPI button]
And look at this — Priya gets a tap-to-pay UPI button right in Telegram.
She taps it → GPay opens → amount pre-filled → done. Payment sent.

[voice note plays]
🔊 'Expenses settled! After 3 rounds, Priya owes you 7,650 rupees.
The main discussion was about the fuel split. A UPI payment link has been sent.'

No awkward money conversation. No Splitwise. The agents handled it."
```

### Demo 3: The Grand Finale — Group Trip (90 seconds)
```
"Now for the real magic. Three people, three preferences, one trip.

[three phones/tabs on table]
Rahul wants mountains, ₹15K budget, March 22-23.
Priya wants beach, ₹10K budget, March 20-24.
Amit wants anything fun, ₹12K budget, only March 22-23.

Three AI agents. Talking to each other. Right now.

[dashboard shows multi-agent negotiation]
[three satisfaction lines on the chart, converging toward 70+]
[Tavily searches happening: 'weekend getaway beach and hills from Mumbai']

They found the date overlap: March 22-23.
Budget ceiling: ₹10K — that's Priya's max.
Destination conflict: mountains vs beach...
Amit's agent breaks the tie — suggests Gokarna. Hills AND beach.

[all three phones buzz simultaneously]
[three different voice notes arrive — three different AI voices]

🔊 Agent A (deep voice): 'Trip planned! Gokarna, March 22-23, 9000 per person.'
🔊 Agent B (female voice): 'Trip planned! Staying at Zostel Gokarna...'
🔊 Agent C (different voice): 'Activities include beach trek and seafood crawl.'

Three people. Conflicting preferences. Resolved in 45 seconds.
With analytics, voice notes, and real search results.
That's negoT8."
```

### Closing (20 seconds)
```
"Every time two people exchange more than 5 messages to reach a
decision — that's a problem negoT8 solves. Scheduling was just
the beginning.

We built this in 24 hours with Gemini free tier, ElevenLabs voice AI,
and Tavily AI search. Each agent has its own personality, its own voice,
and the intelligence to find real-world data and generate payment links.

Imagine what it becomes with scale.

Thank you."
```

---

## 19. Environment Setup Commands

### Full Setup Script (run this first)
```bash
#!/bin/bash
# setup.sh — Run this at the start of the hackathon

# Create project
mkdir -p negot8/backend/{agents,protocol,features,telegram,tools,voice,personality}
mkdir -p negot8/dashboard
cd negot8

# Python backend
python -m venv venv
source venv/bin/activate
pip install fastapi uvicorn python-telegram-bot google-generativeai httpx pydantic python-dotenv aiosqlite python-socketio elevenlabs tavily-python

# Create .env file
cat > .env << 'EOF'
# Gemini API (get from https://aistudio.google.com/apikey)
GEMINI_API_KEY=your_gemini_api_key_here

# Telegram Bots (create TWO bots via @BotFather on Telegram)
TELEGRAM_BOT_TOKEN_A=your_bot_a_token
TELEGRAM_BOT_TOKEN_B=your_bot_b_token

# For demo: create a third bot for multi-agent demo
TELEGRAM_BOT_TOKEN_C=your_bot_c_token

# ElevenLabs TTS ($20 credit — get from https://elevenlabs.io/app/settings/api-keys)
ELEVENLABS_API_KEY=your_elevenlabs_api_key_here

# Tavily AI Search (FREE — get from https://app.tavily.com/home)
TAVILY_API_KEY=your_tavily_api_key_here

# Dashboard
NEXT_PUBLIC_API_URL=http://localhost:8000
DASHBOARD_URL=http://localhost:3000
EOF

# Frontend dashboard
cd dashboard
npx create-next-app@latest . --typescript --tailwind --app --no-eslint
npm install socket.io-client recharts lucide-react
cd ..

# Initialize database (with v2 schema including analytics)
python -c "
import sqlite3
conn = sqlite3.connect('negot8.db')
conn.executescript('''
CREATE TABLE IF NOT EXISTS users (
    telegram_id INTEGER PRIMARY KEY,
    username TEXT,
    display_name TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    preferences_json TEXT DEFAULT \"{}\",
    personality TEXT DEFAULT \"balanced\",
    voice_id TEXT DEFAULT \"pNInz6obpgDQGcFmaJgB\"
);
CREATE TABLE IF NOT EXISTS negotiations (
    id TEXT PRIMARY KEY,
    feature_type TEXT NOT NULL,
    status TEXT DEFAULT \"pending\",
    initiator_id INTEGER,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    resolved_at TIMESTAMP,
    resolution_json TEXT,
    voice_summary_file TEXT
);
CREATE TABLE IF NOT EXISTS participants (
    negotiation_id TEXT,
    user_id INTEGER,
    preferences_json TEXT,
    satisfaction_score REAL,
    personality_used TEXT,
    PRIMARY KEY (negotiation_id, user_id)
);
CREATE TABLE IF NOT EXISTS rounds (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    negotiation_id TEXT,
    round_number INTEGER NOT NULL,
    proposer_id INTEGER,
    proposal_json TEXT NOT NULL,
    response_type TEXT,
    response_json TEXT,
    reasoning TEXT,
    satisfaction_a REAL,
    satisfaction_b REAL,
    concessions_made TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS tool_calls (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    negotiation_id TEXT,
    tool_name TEXT NOT NULL,
    input_json TEXT,
    output_json TEXT,
    called_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS negotiation_analytics (
    negotiation_id TEXT PRIMARY KEY,
    satisfaction_timeline TEXT,
    concession_log TEXT,
    fairness_score REAL,
    total_concessions_a INTEGER,
    total_concessions_b INTEGER,
    computed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
''')
conn.close()
print('Database initialized with v2 schema!')
"

echo "✅ negoT8 v2 setup complete!"
echo "Next steps:"
echo "1. Add your API keys to .env"
echo "2. Create 2-3 Telegram bots via @BotFather"
echo "3. Get ElevenLabs API key from https://elevenlabs.io/app/settings/api-keys"
echo "4. Get Tavily API key from https://app.tavily.com/home (FREE, no credit card)"
echo "5. Start backend: cd backend && uvicorn main:app --reload --port 8000"
echo "6. Start frontend: cd dashboard && npm run dev"
```

### Getting API Keys
```
1. GEMINI API KEY (Free):
   → Go to https://aistudio.google.com/apikey
   → Click "Create API Key"
   → Copy and paste into .env
   → Free tier: 15 RPM, 1500 requests/day for Flash

2. TELEGRAM BOT TOKENS (Free):
   → Open Telegram, search for @BotFather
   → Send /newbot
   → Name it "negoT8 - Rahul" (or your name)
   → Copy the token
   → Repeat for Bot B (and optionally Bot C for 3-person demo)
   → IMPORTANT: Send /setcommands to BotFather for each bot:
     start - Register with negoT8
     coordinate - Start a coordination
     status - Check active negotiations
     preferences - Set default preferences
     personality - Choose agent negotiation style
     help - How negoT8 works

3. ELEVENLABS API KEY ($20 credit):
   → Go to https://elevenlabs.io
   → Sign up / log in
   → Go to Settings → API Keys
   → Create and copy API key
   → $20 credit ≈ 60,000 characters on Flash v2.5 ≈ 300 voice summaries
   → Voice library: https://elevenlabs.io/voice-library (5000+ free voices)
   → Recommended voices:
     Agent A: pNInz6obpgDQGcFmaJgB (Adam — male, clear)
     Agent B: 21m00Tcm4TlvDq8ikWAM (Rachel — female, clear)
     Agent C: AZnzlk1XvdvUeBnXmlld (Domi — different tone)

4. TAVILY API KEY (Free, no credit card):
   → Go to https://app.tavily.com/home
   → Sign up with Google or email
   → API key is on the dashboard immediately
   → Free tier: 1,000 searches/month
   → 1 basic search = 1 credit, 1 advanced search = 2 credits
   → Returns AI-summarized results (no extra LLM call needed)
```

---

## 20. Troubleshooting & Edge Cases

### Common Issues

**Gemini returns non-JSON response:**
```python
# Always use response_mime_type="application/json" in GenerationConfig
# AND have a fallback JSON extraction:
try:
    result = json.loads(response.text)
except:
    text = response.text
    start = text.find('{')
    end = text.rfind('}') + 1
    result = json.loads(text[start:end])
```

**Telegram webhook conflicts:**
```python
# Use polling mode for hackathon (simpler than webhooks):
application.run_polling()
# NOT application.run_webhook() — polling is easier to debug
```

**Gemini rate limiting:**
```python
# Free tier: 15 RPM for gemini-3-flash-preview
# With 5-round negotiations, each negotiation = ~10-15 API calls
# Space out calls with asyncio.sleep(1) between rounds
import asyncio
await asyncio.sleep(1)  # 1 second between Gemini calls
```

**ElevenLabs TTS fails:**
```python
# Voice summary is a BONUS, not critical. Always have fallback:
try:
    voice_file = await generate_voice_summary(text, negotiation_id)
    if voice_file:
        await send_voice_note(bot, chat_id, voice_file)
except Exception as e:
    print(f"Voice summary failed: {e}")
    # Just skip voice — text summary is already sent
```

**Tavily search returns no results:**
```python
# Fallback: try broader query, or skip search and use LLM knowledge
results = await tavily_tool.execute(query=query)
if not results["results"]:
    # Try broader query
    results = await tavily_tool.execute(query=query.split()[0] + " " + location)
    if not results["results"]:
        # Fall back to Gemini's training knowledge
        return {"summary": "No search results found. Using general knowledge."}
```

**UPI link doesn't open on phone:**
```python
# UPI links only work on mobile with a UPI app installed
# For demo: test on actual phone first
# Fallback: show the UPI ID + amount as text
if not upi_link_works:
    await bot.send_message(
        chat_id=chat_id,
        text=f"💳 Pay ₹{amount} to {payee_name}\nUPI ID: {payee_upi}"
    )
```

**Agent produces bad proposals:**
```python
# Add validation after every agent call:
def validate_proposal(proposal, feature_type, preferences):
    """Ensure proposal respects hard constraints."""
    for constraint in preferences.get("constraints", []):
        if constraint["hard"]:
            # Check if proposal violates this constraint
            if violates(proposal, constraint):
                # Re-prompt the agent with explicit constraint reminder
                return None  # triggers re-generation
    return proposal
```

**Multi-agent (3+ people) negotiation gets stuck:**
```python
# Fallback: after 5 rounds with 3+ agents, generate 3 options
# and let humans vote. Majority wins.
if round_number >= 5 and len(participants) > 2:
    options = await negotiator.generate_options(3)
    for user in participants:
        await telegram_bot.send_poll(
            user.telegram_id,
            "Which option do you prefer?",
            [opt["summary"] for opt in options]
        )
```

### Demo-Day Failsafes

1. **Pre-seed database with completed negotiations** — if live demo breaks, show pre-computed results
2. **Pre-generate voice summaries** — have MP3 files ready for all 3 demo scenarios
3. **Record video backup** — screen-record a perfect demo run the night before submission
4. **Hardcode fallback responses** — if Gemini is down, agents return pre-written proposals for demo scenarios
5. **Use localhost** — don't depend on hackathon WiFi for backend-to-backend calls. Everything runs on your laptop.
6. **Test on 4G hotspot** — Telegram works on mobile data, so use phone hotspot as backup internet
7. **Test UPI on actual phones** — UPI deep links behave differently on different apps

### Performance Tips
- Use `gemini-3-flash-preview` for ALL agents (fast + free tier friendly)
- Keep system prompts under 500 tokens each (personality modifiers add ~100 tokens)
- Cache user preferences in memory (don't re-query DB every round)
- Run all negotiations sequentially (not concurrent) to avoid Gemini rate limits
- Pre-load Tavily results where possible (search restaurants before negotiation starts)
- Use ElevenLabs Flash v2.5 model (fastest TTS, lowest cost per character)
- Keep voice summaries under 500 characters (budget-safe + faster generation)

---

## Quick Reference Card (Print This)

```
🔑 API Keys:          .env file (Gemini + Telegram + ElevenLabs + Tavily)
🤖 Start backend:     uvicorn main:app --reload --port 8000
🎨 Start dashboard:   cd dashboard && npm run dev
📱 Telegram bots:     polling mode, 2-3 bots
🧠 LLM:              gemini-3-flash-preview (JSON mode)
💾 Database:          SQLite (negot8.db) with analytics tables
🔄 Max rounds:        5 per negotiation
📡 Real-time:         Socket.IO
🔊 Voice:            ElevenLabs Flash v2.5 ($20 credit ≈ 300 summaries)
🔍 Search:           Tavily (1,000 free/month, AI-optimized)
💳 Payments:         UPI deep links (upi://pay?...)
🎭 Personalities:    aggressive | people_pleaser | analytical | empathetic | balanced

FEATURE QUICK MAP:
/coordinate → Personal Agent → extracts preferences → stores in DB
/personality → Set agent negotiation style → stored per user
Both users ready → Negotiator Agents start rounds (with personality)
Each round → proposal/counter/accept → stored with satisfaction scores
Resolution → Text + Voice Note + UPI Link + Dashboard Analytics

DEMO ORDER: 🍕 Restaurant → 💰 Expenses (+ UPI) → ✈️ Group Trip (3 phones + 3 voices)

ADD-ON CHECKLIST:
✅ Voice summaries (ElevenLabs TTS → Telegram voice note)
✅ Agent personalities (/personality command → prompt modifier)
✅ Negotiation analytics (satisfaction chart + concession timeline + fairness)
✅ UPI deep links (tap-to-pay in Telegram for expense features)
✅ Tavily AI search (real restaurant/place/price data)
```

---

*This document is the single source of truth for negoT8 v2. When using AI coding agents (Copilot, Cursor, Claude), paste relevant sections as context for each task. The system prompts in Section 6, message schemas in Section 7, feature schemas in Section 8, and add-on implementations in Sections 13-16 are the most important reference materials.*

*Built for a 24-hour hackathon. Two developers. Gemini free tier + $20 ElevenLabs + Free Tavily. Maximum impact.*

**Go build it. Go win it. 🏆**