112 KiB
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
- Project Overview & Vision
- Tech Stack & Dependencies
- Project Structure
- Architecture Deep Dive
- Database Schema
- Agent System Prompts & Personalities
- Inter-Agent Communication Protocol
- All 8 Features — Detailed Implementation
- Telegram Bot Implementation
- Next.js Dashboard
- API Layer (FastAPI)
- Tool Calling System
- Add-On: Voice Summaries (ElevenLabs)
- Add-On: Agent Personality System
- Add-On: Negotiation Analytics (Dashboard)
- Add-On: UPI Payment Deep Links
- Hour-by-Hour Build Plan
- Demo Script
- Environment Setup Commands
- 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
- Meeting Scheduling — agents negotiate mutually optimal times
- Expense Splitting — agents handle awkward money conversations + generate UPI payment links
- Freelancer ↔ Client Negotiation — scope, budget, timeline alignment
- Roommate Decisions — WiFi plans, chores, shared purchases
- Group Trip Planning — multi-agent coordination (3+ people)
- Buying/Selling Negotiation — marketplace haggling automated
- Collaborative Decisions — restaurants, movies, activities
- 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
# 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
-- 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.
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
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)
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:
- Preference Schema — what to extract from user messages
- Proposal Schema — what agents exchange
- Resolution Schema — what users see at the end
- Tool Calls — what tools are available for this feature
- Negotiation Rules — feature-specific negotiation logic
- Telegram Output Format — how the resolution looks in chat
- Voice Summary Template — what the TTS voice note says (NEW)
Feature 1: Meeting Scheduling
Preference Schema:
{
"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:
{
"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:
{
"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:
{
"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):
{
"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):
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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):
{
"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):
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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
# 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
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)
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)
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
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
// 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)
// 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)
// 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
# 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
# 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)
# 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)
# 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
# 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)
# 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)
# 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
# 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
# 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)
# 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
- After
resolve_negotiation()— generate voice MP3, send as Telegram voice note - Agent personality — optionally use different voice_ids per user
- 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
# 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
# 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
# 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
{
"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
# 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)
#!/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:
# 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:
# Use polling mode for hackathon (simpler than webhooks):
application.run_polling()
# NOT application.run_webhook() — polling is easier to debug
Gemini rate limiting:
# 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:
# 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:
# 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:
# 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:
# 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:
# 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
- Pre-seed database with completed negotiations — if live demo breaks, show pre-computed results
- Pre-generate voice summaries — have MP3 files ready for all 3 demo scenarios
- Record video backup — screen-record a perfect demo run the night before submission
- Hardcode fallback responses — if Gemini is down, agents return pre-written proposals for demo scenarios
- Use localhost — don't depend on hackathon WiFi for backend-to-backend calls. Everything runs on your laptop.
- Test on 4G hotspot — Telegram works on mobile data, so use phone hotspot as backup internet
- Test UPI on actual phones — UPI deep links behave differently on different apps
Performance Tips
- Use
gemini-3-flash-previewfor 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. 🏆