# Project export: Homes.ai: Personal Real-Estate Agent

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: Getting a real-estate agent can be expensive. Homes.ai personalizes your search to fit your niche needs and argues for every and any leverage it can get!
- Devpost: https://devpost.com/software/real-estate-ai-agents
- GitHub: https://github.com/Steve-Dusty/homes-ai
- Demo: https://www.loom.com/share/a2948c06cbbb4d3a971036378d4e3bbc
- Video: https://www.youtube.com/embed/37283?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Steve-Dusty (12 commits), Jasper Morgal (2 commits)

## Devpost submission (written by the team)

### Inspiration

When buying a house, people always start by manually searching, contacting a realtor, negotiating with other people. We want to streamline this process using AI agents.

### What it does

Uses multiple AI agents to help you find the best home around you, suggesting homes as well as giving you insights about the local businesses and schools nearby. It can also negotiate with a realtor over a phone call to help you get the best price on the house you want to buy.

### How we built it

We built multiple agents off of Fetch AI's platform that all work together to give you the best experience finding and buying a house.

### Challenges we ran into

We had issues with integration of all the agents together, but using fetch AI we were able to solve them. We also had issues with the Wi-Fi, and things not working at the last minute.

### Accomplishments we're proud of

We are proud of the agents we were able to deploy to Fetch AI's agentverse, you can go use them for yourself here: Real Estate Agent (Coordinator) Scoping Agent Community Analysis Agent Real Estate Intern Local Discovery Agent Mapbox Agent Research Agent Prober Agent Vapi Negotiator

### What we learned

We learned a lot about how to build and deploy AI agents as well as the world of real estate, and the importance of planning ahead.

### What's next

for home.ai We plan to develop home.ai further into a fully featured home discovery and purchasing platform, as well as continuing to deploy agents for Fetch AI. Note Our video is on loom because we had issues with uploading. See it here.

## README (from the GitHub repository)

# Homes AI - AI-Powered Property Search

An intelligent real estate search platform powered by Fetch.ai uAgents, ASI-1 mini, and MCP tools. Natural conversation meets property discovery.

## Features

- **Natural Conversation** - Talk to the AI agent like a real estate professional
- **Smart Recommendations** - Get location suggestions based on your job and lifestyle
- **Multi-Source Search** - Powered by Tavily and BrightData MCP integrations
- **Interactive Map** - Visualize properties on Mapbox with real-time data
- **No AI Slop** - Straightforward, helpful responses without robotic repetition

## Architecture

```
Frontend (Next.js)
  ↓ API Routes
Backend (uAgents)
  ↓
┌─────────────────┐
│ Scoping Agent   │ → Natural conversation (ASI-1 mini)
│ (Port 8001)     │ → Gathers: location, budget, beds, baths
└─────────────────┘
  ↓ criteria
┌─────────────────┐
│ Research Agent  │ → Searches properties via MCP
│ (Port 8002)     │ → Tavily: Web search
└─────────────────┘ → BrightData: Real estate scraping
  ↓ properties
┌─────────────────┐
│ Mapbox Agent    │ → Interactive map visualization
│ (Port 8003)     │ → Real coordinates from BrightData
└─────────────────┘
```

## Tech Stack

**Backend:**
- Fetch.ai uAgents (pure REST, no FastAPI)
- ASI-1 mini for natural language
- Tavily MCP for web search
- BrightData MCP for property data
- Mapbox for visualization

**Frontend:**
- Next.js 15
- React 19
- TypeScript
- Mapbox GL JS
- Tailwind CSS

## Quick Start

### 1. Backend Setup

```bash
cd backend

# Install dependencies
pip install -r requirements.txt

# Configure .env
cp .env.example .env
# Add your API keys

# Start all agents
./start_all.sh

# Or start individually:
python agents/scoping_agent.py   # Port 8001
python agents/research_agent.py  # Port 8002
python agents/mapbox_agent.py    # Port 8003
```

### 2. Frontend Setup

```bash
cd frontend

# Install dependencies
npm install

# Configure environment
echo "NEXT_PUBLIC_MAPBOX_API_KEY=your_mapbox_key" > .env.local

# Start dev server
npm run dev
```

### 3. Open Browser

Navigate to `http://localhost:3000`

## Environment Variables

### Backend (.env)

```bash
ASI_API_KEY=your_asi_api_key
TAVILY_API_KEY=your_tavily_key
BRIGHT_DATA_API_KEY=your_brightdata_key
MAPBOX_API_KEY=your_mapbox_key
```

### Frontend (.env.local)

```bash
NEXT_PUBLIC_MAPBOX_API_KEY=your_mapbox_key
SCOPING_AGENT_URL=http://localhost:8001
RESEARCH_AGENT_URL=http://localhost:8002
```

## Usage Example

**User:** Hi, I'm looking for a place in the Bay Area

**Agent:** Hey there! It's awesome that you're thinking about finding a home in the Bay Area. Do you have any specific area in mind? Like San Francisco, East Bay, or somewhere like Palo Alto?

**User:** I work in tech in Mountain View, budget is 1.5M, need 3 bedrooms and 2 bathrooms

**Agent:** Nice! With a tech job in Mountain View, you're right in the heart of Silicon Valley. A budget of $1.5M and 3 beds/2 baths sounds doable. Besides Mountain View, nearby areas like Sunnyvale, Santa Clara, or San Jose could be great options with good schools. Any other must-haves like a backyard?

**User:** Good schools are important, and a backyard would be nice. Ready to search

**Agent:** Perfect! I've found 10 properties matching your criteria...

*[Properties appear on the map]*

## Project Structure

```
estate/
├── backend/
│   ├── agents/
│   │   ├── scoping_agent.py      # Natural conversation
│   │   ├── research_agent.py     # Property search
│   │   └── mapbox_agent.py       # Visualization
│   ├── utils/
│   │   ├── asi_client.py         # ASI-1 mini wrapper
│   │   └── mcp_client.py         # MCP clients
│   ├── start_all.sh              # Start all agents
│   ├── stop_all.sh               # Stop all agents
│   └── README.md
│
└── frontend/
    ├── app/
    │   ├── api/chat/route.ts     # API integration
    │   └── page.tsx              # Main page
    ├── components/
    │   ├── ChatInterface.tsx     # Chat UI
    │   └── MapView.tsx           # Map UI
    └── lib/
        └── mockData.ts           # Fallback data
```

## API Endpoints

### Backend

**Scoping Agent:**
```bash
POST http://localhost:8001/chat
{
  "text": "Looking for a 3br in SF",
  "session_id": "user123"
}
```

**Research Agent:**
```bash
POST http://localhost:8002/search
{
  "location": "Mountain View",
  "max_budget": 1500000,
  "bedrooms": 3,
  "bathrooms": 2,
  "preferences": ["good schools"],
  "session_id": "user123"
}
```

**Mapbox Agent:**
```bash
POST http://localhost:8003/visualize
{
  "properties": [...],
  "center_location": "Mountain View",
  "session_id": "user123"
}
```

### Frontend

**Chat API:**
```bash
POST /api/chat
{
  "message": "Looking for a home",
  "sessionId": "session_123"
}
```

## Design Decisions

1. **Pure uAgents** - No adapters, no LangGraph wrapping. Direct REST endpoints.
2. **ASI-1 mini** - Fast, natural conversations without Claude dependency.
3. **MCP via HTTP** - Direct SSE endpoint integration with Tavily and BrightData.
4. **Coordinate Extraction** - BrightData returns lat/lng for accurate map placement.
5. **Natural Flow** - Avoids "doesn't meet criteria" loops with context-aware responses.

## Development

```bash
# Backend
cd backend
python -m pytest tests/        # Run tests
python agents/scoping_agent.py # Dev mode

# Frontend
cd frontend
npm run dev                    # Dev server
npm run build                  # Production build
npm run lint                   # Lint check
```

## Troubleshooting

**Agents won't start:**
- Check API keys in `.env`
- Ensure ports 8001-8003 are free
- Activate venv: `source .venv/bin/activate`

**Frontend can't connect:**
- Check backend agents are running
- Verify CORS is enabled (included in uAgents)
- Check browser console for errors

**No properties showing:**
- Check MCP API keys are valid
- View research agent logs: `tail -f logs/research.log`
- Fall back to mock data if MCP fails

## Future Enhancements

- [ ] User authentication and saved searches
- [ ] Email alerts for new listings
- [ ] Property comparison tool
- [ ] Mortgage calculator integration
- [ ] Price history and trends
- [ ] Neighborhood insights
- [ ] Virtual tour scheduling

## License

MIT

## Acknowledgments

- Fetch.ai for uAgents framework
- Tavily for search MCP
- BrightData for scraping MCP
- Mapbox for visualization


## Detected evidence (automated analysis)

Indexed codebase: 32 recognized source files, 235 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (38 of 38)

```
.gitignore
backend/.env.example
backend/agents/__init__.py
backend/agents/brightdata_client.py
backend/agents/community_analysis_agent.py
backend/agents/general_agent.py
backend/agents/llm_client.py
backend/agents/local_discovery_agent.py
backend/agents/mapbox_agent.py
backend/agents/models.py
backend/agents/prober_agent.py
backend/agents/research_agent.py
backend/agents/scoping_agent.py
backend/agents/tavily_client.py
backend/agents/vapi_agent.py
backend/agents/vapi_client.py
backend/BRIGHTDATA_INTEGRATION.md
backend/main.py
backend/negotiation_workflow.py
backend/README.md
backend/requirements.txt
frontend/app/api/chat/route.ts
frontend/app/globals.css
frontend/app/layout.tsx
frontend/app/page.tsx
frontend/components/ChatInterface.tsx
frontend/components/MapView.tsx
frontend/components/NegotiationModal.tsx
frontend/components/NeighborhoodStats.tsx
frontend/lib/mapUtils.ts
frontend/lib/mockData.ts
frontend/next-env.d.ts
frontend/next.config.ts
frontend/package.json
frontend/postcss.config.mjs
frontend/tsconfig.json
QUICKSTART.md
README.md
```

### Dependencies

- backend/requirements.txt: aiohttp@>=3.13.1, asyncio-contextmanager@>=1.0.0, fastapi@>=0.115.0, mcp@>=1.0.0, pydantic@>=2.12.3, python-dotenv@>=1.1.1, uagents@>=0.22.10, uvicorn@>=0.38.0
- frontend/package.json: @tailwindcss/postcss@^4, @types/mapbox-gl@^3.4.1, @types/node@^20, @types/react@^19, @types/react-dom@^19, mapbox-gl@^3.16.0, next@16.0.0, react@19.2.0, react-dom@19.2.0, react-map-gl@^8.1.0, tailwindcss@^4, typescript@^5

### Recent commits (newest first)

- completed project
- completed final functionality
- got vapi to work
- fixed borders
- remvoed gmail agent
- fixed radius
- added working v1 vapi
- added v1 prober agent and negotiation page
- remove mock data
- created filter for location in research
- added context to every prompt
- Merge pull request #1 from Steve-Dusty/community_analysis_agent
- Added community analysis agent
- first version

## Key source files (fetched from GitHub, selected and truncated for size)

### QUICKSTART.md

```markdown
# Estate Search - Quick Start Guide

## 🚀 Running the Full Stack

### 1. Backend Setup (Python + uAgents)

```bash
cd backend

# Install dependencies
pip install -r requirements.txt

# Configure environment
cp .env.example .env
# Edit .env and add your API keys:
#   - ASI_API_KEY
#   - TAVILY_API_KEY

# Start the API server
python api_server.py
```

The backend will start on **http://localhost:8080**

### 2. Frontend Setup (Next.js)

```bash
cd frontend

# Install dependencies
npm install

# Start the development server
npm run dev
```

The frontend will start on **http://localhost:3000**

## 🎯 How It Works

### Architecture Flow

```
User Message (Frontend)
    ↓
Next.js API Route (/api/chat)
    ↓
FastAPI Streaming Endpoint (localhost:8080/api/chat)
    ↓
EstateCoordinator Agent
    ↓
┌─────────────────┬──────────────────┐
│                 │                  │
Scoping Agent  Research Agent
    ↓                 ↓
ASI:1 LLM      Tavily Search + ASI:1
    ↓                 ↓
Streaming Updates ← ← ← Back to Frontend
```

### Real-Time Streaming

The system uses **NDJSON streaming** (newline-delimited JSON) to provide real-time updates:

1. **Agent Updates**: See which agent is processing
   ```json
   {"agent": "Scoping Agent", "message": "Processing...", "type": "scoping"}
   ```

2. **Final Results**: Properties and search summary
   ```json
   {
     "type": "complete",
     "result": {
       "properties": [...],
       "search_summary": "...",
       "requirements": {...}
     }
   }
   ```

## 📝 Example Conversation

**User:** "I'm looking for a house in Oakland"

**Scoping Agent:** "Great! What's your budget range?"

**User:** "Around $800k, need 3 bedrooms"

**Scoping Agent:** "Perfect! How many bathrooms?"

**User:** "2 bathrooms, prefer good schools"

**Coordinator:** ✅ Scoping complete! Triggering search...

**Research Agent:** 🔍 Searching properties on Zillow, Redfin, Realtor.com...

**Result:** Found 5 properties! *[Properties displayed on map]*

## 🛠️ Troubleshooting

### Backend Issues

**Port conflicts:**
```bash
# The system uses random ports 9000-9500 for agents
# If you get port errors, just restart
```

**Missing API keys:**
```bash
# Make sure .env has both keys:
cat backend/.env
```

**Agent timeout:**
```bash
# Increase timeout in main.py:189
max_timeout = 180  # 3 minutes
```

### Frontend Issues

**Can't connect to backend:**
```bash
# Check BACKEND_URL in frontend/app/api/chat/route.ts
# Default: http://localhost:8080
```

**Streaming not working:**
- Check browser console for errors
- Ensure backend is running and accessible
- Try curl test: `curl -X POST http://localhost:8080/api/chat -H "Content-Type: application/json" -d '{"message":"test","session_id":"1"}'`

## 🧪 Testing Individual Components

### Test Backend Agents Only

```bash
cd backend
python main.py
```

This runs a standalone test without the API server.

### Test API Server

```bash
# Terminal 1: Start API server
cd backend
python api_server.py

# Te
[truncated — 1666 more characters]
```

### backend/BRIGHTDATA_INTEGRATION.md

```markdown
# Bright Data MCP Integration

## Overview

The research agent now uses **Bright Data MCP** instead of Tavily for property searches. This provides more reliable and structured real estate listing data by scraping Zillow directly.

## How It Works

### 1. **Architecture**

```
User Query
    ↓
Coordinator → Scoping Agent → Research Agent
                                    ↓
                            BrightDataClient (MCP)
                                    ↓
                    ┌───────────────┴───────────────┐
                    ↓                               ↓
            search_engine tool              scrape_as_markdown tool
            (Google Search)                 (Scrapes Zillow page)
                    ↓                               ↓
            Find Zillow URLs        →       Extract property listings
```

### 2. **MCP Integration**

The `BrightDataClient` class (backend/agents/brightdata_client.py) connects to Bright Data's MCP server:

```python
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

# Connect to Bright Data MCP
server_params = StdioServerParameters(
    command="npx",
    args=["-y", "@brightdata/mcp"],
    env={**os.environ}
)

read, write = await stdio_client(server_params)
session = ClientSession(read, write)
await session.__aenter__()
```

### 3. **Search Flow**

1. **Build search query**: "zillow homes for sale in San Francisco 2 bedrooms 2 bathrooms $1.5M"

2. **Call `search_engine` tool** via MCP:
   ```python
   result = await session.call_tool(
       "search_engine",
       arguments={
           "query": search_query,
           "engine": "google"
       }
   )
   ```

3. **Extract Zillow URL** from search results

4. **Call `scrape_as_markdown` tool** to get listing page content:
   ```python
   scrape_result = await session.call_tool(
       "scrape_as_markdown",
       arguments={"url": category_url}
   )
   ```

5. **Parse markdown** to extract structured property data:
   - Address
   - City
   - Price
   - Bedrooms
   - Bathrooms
   - Square footage
   - URL

6. **Filter by user requirements** (budget, bedrooms, bathrooms)

7. **Return PropertyListing objects** to coordinator

## Benefits Over Tavily

1. **More Reliable Data**: Direct scraping from Zillow ensures accurate property information
2. **Structured Extraction**: Parses Zillow's markdown format for consistent data
3. **No LLM Parsing**: Properties are extracted deterministically, not via LLM inference
4. **Better Filtering**: Can precisely filter by price, bedrooms, bathrooms before returning results

## Setup

### Requirements

1. Add to `backend/requirements.txt`:
   ```
   mcp>=1.0.0
   ```

2. Bright Data MCP server is installed automatically via `npx -y @brightdata/mcp`

3. No API key needed for basic usage

### Running

The MCP client automatically connects when the research agent receives a request:

```bash
cd backend
python main.py
```

## Files Modified

- `backend/agents/brightdata_
[truncated — 518 more characters]
```

### backend/requirements.txt

```
# Core uAgents framework
uagents>=0.22.10

# API Server
fastapi>=0.115.0
uvicorn>=0.38.0

# LLM and AI
aiohttp>=3.13.1
python-dotenv>=1.1.1

# Data validation
pydantic>=2.12.3

# Async support
asyncio-contextmanager>=1.0.0

# MCP SDK for Bright Data integration
mcp>=1.0.0

```

### frontend/package.json

```
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "mapbox-gl": "^3.16.0",
    "next": "16.0.0",
    "react": "19.2.0",
    "react-dom": "19.2.0",
    "react-map-gl": "^8.1.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/mapbox-gl": "^3.4.1",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### frontend/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Homes AI",
  description: "Intelligent property search powered by Fetch.ai uAgents and ASI:One blockchain technology",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

```

### frontend/app/page.tsx

```typescript
'use client';

import { useState } from 'react';
import ChatInterface from '@/components/ChatInterface';
import MapView from '@/components/MapView';
import { Property } from '@/lib/mockData';

export default function Home() {
  const [selectedProperty, setSelectedProperty] = useState<Property | null>(null);
  const [properties, setProperties] = useState<Property[]>([]);
  const [topResultCoords, setTopResultCoords] = useState<{ latitude: number; longitude: number; address: string; image_url?: string } | null>(null);
  const [rawSearchResults, setRawSearchResults] = useState<any[]>([]);
  const [topResultDetails, setTopResultDetails] = useState<any>(null);
  const [sessionId] = useState(() => `session_${Date.now()}`);
  const [currentListingIndex, setCurrentListingIndex] = useState(0);
  const [communityAnalysis, setCommunityAnalysis] = useState<any>(null);

  const handlePropertiesFound = (newProperties: any[]) => {
    // Convert backend properties to frontend Property format
    const geoReady = newProperties.filter(
      (prop) => typeof prop.latitude === 'number' && typeof prop.longitude === 'number'
    );

    const convertedProperties: Property[] = geoReady.map((prop, idx) => ({
      id: idx + 1000,
      address: prop.address || prop.title || 'Unknown Address',
      city: prop.location || 'Bay Area',
      state: 'CA',
      price: prop.price || 0,
      bedrooms: prop.bedrooms || 0,
      bathrooms: prop.bathrooms || 0,
      sqft: 0,
      latitude: prop.latitude,
      longitude: prop.longitude,
      description: prop.description || '',
      imageUrl: '/properties/default.jpg',
      propertyType: 'Single Family'
    }));

    // Set converted properties (no mock data fallback)
    setProperties(convertedProperties);
  };

  const handleRawSearchResults = (results: any[]) => {
    console.log('[Home] Storing raw search results:', results.length);
    console.log('[Home] Raw results with POIs:', results);

    // Limit to 5 listings max for faster cycling
    const limitedResults = results.slice(0, 5);
    setRawSearchResults(limitedResults);
    setCurrentListingIndex(0); // Reset to first listing

    // Extract top result details
    if (limitedResults.length > 0) {
      const topResult = limitedResults[0];
      console.log('[Home] Top result details:', topResult);
      console.log('[Home] Top result POIs:', topResult.pois);
      setTopResultDetails(topResult);
    }
  };

  const handleNextListing = () => {
    if (rawSearchResults.length === 0) return;

    const nextIndex = (currentListingIndex + 1) % rawSearchResults.length;
    setCurrentListingIndex(nextIndex);

    const nextListing = rawSearchResults[nextIndex];
    console.log('[Home] Cycling to listing', nextIndex + 1, 'of', rawSearchResults.length, ':', nextListing);

    // Force update by creating completely new objects with timestamp
    const timestamp = Date.now();

    // Update top result details - create new object to force re-render
    setTopResultDetails({
      ...nextListing,
      _updateKey: timestamp
    });

    // Update coordinates - create new object to force map update
    if (nextListing.latitude && nextListing.longitude) {
      setTopResultCoords({
        latitude: nextListing.latitude,
        longitude: nextListing.longitude,
        address: nextListing.address || nextListing.title || 'Unknown Address',
        image_url: nextListing.image_url,
        _updateKey: timestamp
      });
    }
  };

  const handleTopResultCoordinates = (coords: { latitude: number; longitude: number; address: string; image_url?: string } | null) => {
    console.log('[Home] Received top result coordinates:', coords);
    setTopResultCoords(coords);
  };

  const handleCommunityAnalysis = (analysis: any) => {
    console.log('[Home] Received community analysis:', analysis);
    setCommunityAnalysis(analysis);
  };

  return (
    <div className="h-screen w-screen overflow-hidden bg-black flex">
      <div className="w-1/4 h-full border-r border-slate-700/50">
        <ChatInterface
          onPropertiesFound={handlePropertiesFound}
          onTopResultCoordinates={handleTopResultCoordinates}
          onRawSearchResults={handleRawSearchResults}
          onCommunityAnalysis={handleCommunityAnalysis}
          sessionId={sessionId}
        />
      </div>
      <div className="w-3/4 h-full">
        <MapView
          selectedProperty={selectedProperty}
          allProperties={properties}
          topResultCoords={topResultCoords}
          topResultDetails={topResultDetails}
          rawSearchResults={rawSearchResults}
          onNextListing={handleNextListing}
          currentListingIndex={currentListingIndex}
          communityAnalysis={communityAnalysis}
        />
      </div>
    </div>
  );
}

```

### backend/main.py

```python
"""
Estate Search Main - Coordinator with REST API
"""
import asyncio
from uagents import Agent, Context, Model, Bureau
from typing import Dict, Any
from agents.models import (
    ScopingRequest,
    ScopingResponse,
    ResearchRequest,
    ResearchResponse,
    GeneralRequest,
    GeneralResponse,
    MapboxRequest,
    MapboxResponse,
    LocalDiscoveryRequest,
    LocalDiscoveryResponse,
    CommunityAnalysisRequest,
    CommunityAnalysisResponse,
    ProberRequest,
    ProberResponse,
)
from agents.scoping_agent import create_scoping_agent
from agents.research_agent import create_research_agent
from agents.general_agent import create_general_agent
from agents.mapbox_agent import create_mapbox_agent
from agents.local_discovery_agent import create_local_discovery_agent
from agents.community_analysis_agent import create_community_analysis_agent
from agents.prober_agent import create_prober_agent
from agents.vapi_agent import create_vapi_agent, VapiRequest, VapiResponse
from agents.llm_client import SimpleLLMAgent


# REST API Models
class ChatRequest(Model):
    message: str
    session_id: str


class ChatResponse(Model):
    status: str
    data: Dict[str, Any]


class NegotiateRequest(Model):
    address: str
    name: str
    email: str
    additional_info: str = ""


class NegotiateResponse(Model):
    success: bool
    message: str
    leverage_score: float
    next_actions: list
    call_summary: str = ""


def main():
    print("=" * 60)
    print("🏠 Estate Search System Starting")
    print("=" * 60)

    # Create all agents
    scoping_agent = create_scoping_agent(port=8001)
    research_agent = create_research_agent(port=8002)
    general_agent = create_general_agent(port=8003)
    mapbox_agent = create_mapbox_agent(port=8004)
    local_discovery_agent = create_local_discovery_agent(port=8005)
    community_analysis_agent = create_community_analysis_agent(port=8006)
    prober_agent = create_prober_agent(port=8007)
    vapi_agent = create_vapi_agent(port=8008)

    # Create coordinator agent
    coordinator = Agent(
        name="coordinator",
        port=8080,
        seed="coordinator_seed",
        endpoint=["http://localhost:8080/submit"]
    )

    # Store agent addresses
    scoping_address = scoping_agent.address
    research_address = research_agent.address
    general_address = general_agent.address
    mapbox_address = mapbox_agent.address
    local_discovery_address = local_discovery_agent.address
    community_analysis_address = community_analysis_agent.address
    prober_address = prober_agent.address
    vapi_address = vapi_agent.address

    # Session storage
    sessions = {}
    prober_sessions = {}  # Separate storage for prober responses
    vapi_sessions = {}  # Separate storage for vapi responses

    # Create LLM summarizer
    llm_summarizer = SimpleLLMAgent(
        name="NegotiationSummarizer",
        system_prompt="You are an expert real estate negotiation analyst. Summarize negotiation conversations concisely."
    )

    @coordinator.on_event("startup")
    async def startup(ctx: Context):
        ctx.logger.info("=" * 60)
        ctx.logger.info("Coordinator started")
        ctx.logger.info(f"Scoping Agent: {scoping_address}")
        ctx.logger.info(f"Research Agent: {research_address}")
        ctx.logger.info(f"Local Discovery Agent: {local_discovery_address}")
        ctx.logger.info(f"Community Analysis Agent: {community_analysis_address}")
        ctx.logger.info("=" * 60)

    @coordinator.on_message(model=ScopingResponse)
    async def handle_scoping(ctx: Context, sender: str, msg: ScopingResponse):
        ctx.logger.info(f"Received scoping response for session {msg.session_id}")
        ctx.logger.info(f"DEBUG - is_general_question: {msg.is_general_question}")
        ctx.logger.info(f"DEBUG - general_question: {msg.general_question}")
        ctx.logger.info(f"DEBUG - is_complete: {msg.is_complete}")

        if msg.session_id not in sessions:
            sessions[msg.session_id] = {}

        sessions[msg.session_id]["scoping"] = msg

        # Route based on intent
        if msg.is_general_question and msg.general_question:
            # Forward to general agent with context
            ctx.logger.info(f"Forwarding to general agent with question: {msg.general_question}")

            # Get last search location from session for context
            last_location = sessions[msg.session_id].get("last_search_location")
            context = f"The user's last property search was in: {last_location}" if last_location else None

            await ctx.send(
                general_address,
                GeneralRequest(
                    question=msg.general_question,
                    session_id=msg.session_id,
                    context=context
                )
            )
        elif msg.is_complete and msg.requirements:
            # Save last search location for context
            sessions[msg.session_id]["last_search_location"] = msg.requirements.location

            # Forward to research agent for property search
            ctx.logger.info(f"Forwarding to research agent")
            await ctx.send(
                research_address,
                ResearchRequest(
                    requirements=msg.requirements,
                    session_id=msg.session_id
                )
            )

            # Also send to community analysis agent if we have a community name
            if msg.community_name:
                ctx.logger.info(f"Forwarding to community analysis agent for: {msg.community_name}")
                await ctx.send(
                    community_analysis_address,
                    CommunityAnalysisRequest(
                        location_name=msg.community_name,
                        session_id=msg.session_id
                    )
                )

    @coordinator.on_message(model=ResearchResponse)
    async def handle_research(ctx: Context, sender: str, msg: ResearchResponse):
        ctx.
[truncated — 24007 more characters]
```

### frontend/app/api/chat/route.ts

```typescript
import { NextRequest } from 'next/server';

// Backend uAgents REST endpoint
const BACKEND_URL = process.env.BACKEND_URL || 'http://127.0.0.1:8080';

// Force dynamic behavior (no caching)
export const dynamic = 'force-dynamic';
export const revalidate = 0;

export async function POST(req: NextRequest) {
  try {
    const { message, sessionId } = await req.json();

    // Call uAgents REST endpoint
    const response = await fetch(`${BACKEND_URL}/api/chat`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        message,
        session_id: sessionId || 'default'
      })
    });

    if (!response.ok) {
      throw new Error(`Backend error: ${response.statusText}`);
    }

    // Parse single JSON response from uAgents REST
    const data = await response.json();

    return Response.json(data);

  } catch (error) {
    console.error('Chat API error:', error);
    return Response.json(
      { error: 'Failed to process message' },
      { status: 500 }
    );
  }
}

```

### frontend/next.config.ts

```typescript
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
};

export default nextConfig;

```

### frontend/next-env.d.ts

```typescript
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

```

[22 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]