# Project export: Concierge

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: TreeHacks 2026
- Tagline: Stop guessing gifts! Concierge lets 2 Poke agents collaborate with shared context across your email, the web, and your texts, then auto-finds and configures the perfect buy via Browserbase web agents.
- Devpost: https://devpost.com/software/concierge-1tjlf5
- GitHub: https://github.com/andaero/treehacks26
- Video: https://www.youtube.com/embed/fjbazc6t0t4?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner ([The Interaction Company of California] Build with Poke (Challenge 1: iPhone Air for everyone in the team Challenge 2: iPhone 17 Pro for everyone in the team Challenge 3: iPhone Air for everyone in the team Bonus: up to $100k in cash); [Browserbase] Best Web Automation with Stagehand ($1,000))
- Team: 4 GitHub contributor(s) — rohan335 (36 commits), Andy Xu (21 commits), Jason Gao (16 commits), LarryPi314 (4 commits)

## Devpost submission (written by the team)

### Inspiration

So much of modern life is hyper-connected and still strangely distant. You care about the people in your life, but between work, family, and everything else, the little moments slip by; Remembering a friend’s favorite color, noticing a new hobby, picking a gift that says “I know you.” Gift-giving used to be how we showed "I was paying attention", but now it feels like another task on an impossible list. We wanted to bring back that sense of human connection in a world of disconnect. Concierge reminds you what you already know, and helps people show up for each other more thoughtfully.

### What it does

Concierge is an Agentic gift-giving workflow built on top of Poke that helps you find deeply personalized gifts through peer-to-peer agent collaboration and research. When you want to buy a gift for someone, your agent talks to their agent. Your recipient’s agent represents their tastes and preferences, and runs an autonomous iMessage subagent that validates gift hypotheses by searching through the recipient’s broader message history, far more than the gSuite knowledge base Poke provides. Concierge recommendations aren’t generic; they’re grounded in real evidence. It then returns a small set of gift ideas with message citations (and surrounding context) showing exactly why each gift fits. Finally, a browser automation agent uses Browserbase + Stagehand to find the right gift, configure the right variant (size, color, model), and generate a ready-to-checkout link with an estimated final price.

### How we built it

We built Concierge as a web app with a real-time dashboard (Next.js + FastMCP) that visualizes agent-to-agent negotiation and tunes the user into a gift ideating chain of thought tree. Under the hood, we extended Poke via MCP servers: a Poke-to-Poke MCP for agent communication, an iMessage Query MCP that searches the local macOS iMessage database using structured SQL queries, and a Browserbase MCP wrapper for shopping workflows. Poke B (the recipient’s agent) orchestrates the end-to-end process: generating hypotheses for , spawning the iMessage subagent loop, synthesizing validated ideas, and calling the browser agent to produce checkout links. We store session metadata and visualizations, but sensitive message data stays local.

### Challenges we ran into

The biggest challenge was balancing “wow” personalization with privacy and reliability. iMessage data is messy, unstructured, and incredibly sensitive. We needed tooling that could search it quickly, extract context, and produce citations without dumping a user’s entire private life into an LLM prompt. Browser automation was also challenging, retail sites vary wildly, and checkouts can break. We had to scope carefully and focus on a flow we could make stable for a demo.

### Accomplishments we're proud of

We’re proud that Concierge feels human. It doesn’t just recommend “a gift”; it shows the reasoning and evidence behind personalization, which makes it trustworthy. We also built a compelling agent visualization that demonstrates autonomous search and hypothesis refinement in real time. And we integrated real-world tools (Poke’s GSuite knowledge store + iMessage search + Browserbase checkout automation) to prove this can work at scale for anyone.

### What we learned

We learned that great agent products aren’t just about model intelligence. Instead, they’re about the memory, constraints, and interfaces around the model. Evidence-backed personalization is dramatically more convincing than generic suggestions. We also learned that “agent-to-agent” is a powerful metaphor for consent, representation, and trust: the recipient’s agent can advocate for them, even in commerce.

### What's next

Next, we want to expand beyond gifting into a true “preferences marketplace” where your agent can represent you across experiences, gifts, recommendations, group plans, and purchases, while keeping you in control. We’d add stronger consent flows, better redaction controls, richer preference modeling, and broader retailer support. The long-term vision is simple: use AI to help people understand each other better, and make everyday acts of care easier and more meaningful.

## README (from the GitHub repository)

# Concierge - AI-Powered Gift Giving

A gift-giving workflow built on top of [Poke](https://poke.com) (by The Interaction Company) that finds deeply personalized gifts through peer-to-peer AI agent collaboration and autonomous message analysis.

**TreeHacks 2026 Project**

---

## 🎁 What is Concierge?

Concierge helps you find the perfect gift by having your Poke agent collaborate with the recipient's Poke agent to discover deeply personalized gift ideas—backed by **specific message citations** that prove real research.

### The Problem

Generic LLMs give generic gift advice with no proof:
- "Get them a keyboard, they like tech" ❌
- No evidence, no personalization, no confidence

### Our Solution

Concierge provides gift recommendations with **citation traceability**:
- **Recommendation**: Keychron K8 mechanical keyboard (Navy, $97.19) ✅
- **Citation 1** (3 months ago): "I've been really into mechanical keyboards lately"
- **Citation 2** (2 months ago): "The clicky sound is so satisfying!"
- **Citation 3** (1 month ago): "My wrists hurt from this laptop keyboard"
- **Proof**: Not generic—deeply researched with specific evidence

---

## 🚀 Key Features

### 1. Autonomous iMessage Analysis
An autonomous agent loop validates gift hypotheses by iteratively searching the recipient's message history:
- Tests initial hypothesis
- Finds contradicting evidence
- **Updates its own hypothesis**
- Continues until confident
- Returns validated ideas with citations

### 2. Peer-to-Peer Agent Collaboration
Two Poke agents work together:
- **Gift Giver's Poke**: Initiates request, receives recommendations
- **Gift Recipient's Poke**: Orchestrates research, validates hypotheses

### 3. Full Context Access
Recipient's Poke accesses their complete message history:
- Shared messages (Giver ↔ Recipient): Shared experiences
- Other conversations (Recipient ↔ Others): General interests
- **Result**: Combined context for better personalization

### 4. Intelligent Product Configuration
Browser automation configures products based on message insights:
- Size: Extracted from messages ("I'm a size 10")
- Color: From favorite color mentions
- Variant: From specific preference messages

### 5. Real-time Visualization
Web dashboard shows the entire process:
- Agent conversation in real-time
- Autonomous loop iterations
- Hypothesis updates (invalidated → refined → validated)
- Citations as they're discovered
- Final recommendations with evidence

---

## 📋 Documentation

**Quick Links**:
- 📋 **[PRD.md](./PRD.md)** - Complete product specification (GROUND TRUTH)
- 🤖 **[agents.md](./agents.md)** - Agent architecture and collaboration patterns
- 📚 **[DOCUMENTATION_INDEX.md](./DOCUMENTATION_INDEX.md)** - Documentation hub
- ⚡ **[QUICK_REFERENCE.md](./QUICK_REFERENCE.md)** - Commands & workflows

The PRD is the **ground truth** for all development and serves as centralized context for this project.

---

## 🏗️ Architecture

```
Person A (Gift Giver)
    ↓ "Buy gift for Person B, $100, tech"
┌─────────────┐
│   Poke A    │
└──────┬──────┘
       │ Poke-to-Poke MCP
       ↓
┌────────────────────────────┐
│   Poke B (Orchestrator)    │
│  - Email receipt analysis  │
│  - Hypothesis generation   │
│  - Orchestrate subagents   │
└──┬─────────────────────┬───┘
   │                     │
   ↓                     ↓
┌──────────────────┐  ┌────────────────┐
│ iMessage Subagent│  │ Browser Agent  │
│ (Autonomous Loop)│  │ (Browserbase)  │
│                  │  │                │
│ - Test hypothesis│  │ - Find product │
│ - Search messages│  │ - Configure    │
│ - Validate/Update│  │ - Get checkout │
│ - Find citations │  │   URL          │
└──────────────────┘  └────────────────┘
   │                     │
   └────────┬────────────┘
            ↓
   3 Gift Recommendations
   + Citations + URLs
            ↓
        Poke A
            ↓
      Person A
```

**Built on**: [Poke](https://poke.com/docs) by The Interaction Company  
**Extension Method**: MCP (Model Context Protocol) servers

---

## 🚀 Quick Start

### Prerequisites

- Python 3.12+
- Node.js 18+
- uv (Python package manager)
- macOS (for iMessage database access)
- Supabase account
- Browserbase account

### Current Setup (Development)

1. **Install Python dependencies**:
```bash
uv sync
```

2. **Configure environment variables**:
```bash
cp env.example .env
# Edit .env with your keys (see env.example for all options):
# - POKE_A_KEY / POKE_B_KEY (agent authentication)
# - SUPABASE credentials
# - BROWSERBASE_API_KEY
# - ANTHROPIC_API_KEY or OPENAI_API_KEY
```

3. **Run the Poke-to-Poke MCP server**:
```bash
python mcp/poke-to-poke/server.py
```

---

## 📁 Project Structure

```
treehacks26/
├── mcp/
│   └── poke-to-poke/              # Agent communication MCP server ✅
│       └── server.py
├── agents/                         # Agent logic (to be implemented)
│   ├── poke_b_orchestrator.py     # 🔄 Orchestrates gift research
│   ├── imessage_subagent.py       # 🔄 Autonomous hypothesis validation
│   └── browser_automation.py      # 🔄 Product search & configuration
├── app/                            # Next.js dashboard (to be implemented)
│   ├── page.tsx                   # 🔄 Main dashboard
│   ├── session/[id]/              # 🔄 Gift session view
│   └── components/                # 🔄 React components
├── supabase/                       # Database migrations (to be implemented)
│   └── migrations/                # 🔄 Schema definitions
├── PRD.md                          # 📋 Product requirements (GROUND TRUTH)
├── agents.md                       # 🤖 Agent architecture reference
├── README.md                       # 🚀 This file
├── conversation.txt                # 💬 Agent communication log
├── env.example                     # 🔐 Environment template
├── pyproject.toml                  # Python dependencies
└── package.json                    # 🔄 Node.js dependencies (to be added)
```

**Legend**: ✅ Implemented | 🔄 To be implemented

---

## 🔧 Implementation Status

### ✅ Completed

- [x] Project structure and documentation
- [x] Comprehensive PRD (ground truth)
- [x] Poke-to-Poke MCP server (basic implementation)
- [x] Agent authentication system
- [x] Local conversation file storage

### 🔄 In Progress

- [ ] **Phase 1: Foundation**
  - [ ] Next.js app with Supabase integration
  - [ ] Update poke-to-poke MCP for Concierge workflow
  - [ ] Supabase schema implementation
  - [ ] Basic dashboard UI

- [ ] **Phase 2: iMessage Integration**
  - [ ] iMessage Query MCP server
  - [ ] Structured search tools (search_messages, analyze_sentiment)
  - [ ] Message context extraction
  - [ ] Test with real iMessage database

- [ ] **Phase 3: Autonomous Agent**
  - [ ] iMessage subagent autonomous loop
  - [ ] Hypothesis validation logic
  - [ ] Stop conditions
  - [ ] Citation extraction

- [ ] **Phase 4: Orchestration**
  - [ ] Poke B orchestrator
  - [ ] Email receipt integration
  - [ ] Gift idea synthesis
  - [ ] Agent coordination

- [ ] **Phase 5: Browser Automation**
  - [ ] Browserbase integration
  - [ ] Product search
  - [ ] Configuration (size/color)
  - [ ] Checkout URL extraction

- [ ] **Phase 6: Dashboard**
  - [ ] Real-time conversation viewer
  - [ ] Autonomous loop visualizer
  - [ ] Citation display cards
  - [ ] Gift recommendation cards

- [ ] **Phase 7: End-to-End**
  - [ ] Full flow integration
  - [ ] Testing with real data
  - [ ] Demo preparation

See [PRD.md Section 10](./PRD.md#10-development-roadmap) for detailed roadmap.

---

## 🧪 Testing

### Test Poke-to-Poke Server

```bash
# Start server
python mcp/poke-to-poke/server.py

# Send test message
curl -X POST http://localhost:8000/mcp/v1/tools/call \
  -H "Authorization: Bearer $POKE_A_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "send_message",
      "arguments": {
        "message": "[REQUEST] Test gift request"
      }
    },
    "id": 1
  }'

# Get

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 65 recognized source files, 520 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- FastAPI (technology) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (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
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (103 of 103)

```
.gitignore
.python-version
agents.md
agents/imessage_subagent/__init__.py
agents/imessage_subagent/event_logger.py
agents/imessage_subagent/worker.py
browser-agent/env.example
browser-agent/package.json
browser-agent/src/context-manager.ts
browser-agent/src/index.ts
browser-agent/src/schemas.ts
browser-agent/src/search-product.ts
browser-agent/test-notification.ts
browser-agent/test-sites.sh
browser-agent/tsconfig.json
bun.lockb
chats/5cc8fa8f.txt
chats/6dc51bf1.txt
chats/d628ef38.txt
chats/d67bb8cf.txt
chats/dd82d5aa.txt
chats/f3727099.txt
chats/test-chat.txt
check-db.ts
concierge/.gitignore
concierge/app/api/chats/[id]/events/route.ts
concierge/app/api/chats/[id]/route.ts
concierge/app/api/chats/route.ts
concierge/app/api/worker-states/[workerId]/route.ts
concierge/app/api/worker-states/route.ts
concierge/app/components/tree/CitationNode.tsx
concierge/app/components/tree/HypothesisNode.tsx
concierge/app/components/TreeVisualization.tsx
concierge/app/components/ui/primitives.tsx
concierge/app/concierge-interface.tsx
concierge/app/globals.css
concierge/app/layout.tsx
concierge/app/lib/chat-service.ts
concierge/app/lib/tree-layout-service.ts
concierge/app/lib/worker-state-service.ts
concierge/app/page.tsx
concierge/eslint.config.mjs
concierge/FAILED_SEARCH_VISUALIZATION.md
concierge/FRONTEND_SPEC.md
concierge/next.config.ts
concierge/package.json
concierge/postcss.config.mjs
concierge/README.md
concierge/TREE_VISUALIZATION.md
concierge/tsconfig.json
DOCUMENTATION_INDEX.md
env.example
imessage_subagent/__init__.py
imessage_subagent/api.py
imessage_subagent/ARCHITECTURE.md
imessage_subagent/IMPLEMENTATION_SUMMARY.md
imessage_subagent/orchestrator.py
imessage_subagent/prompts.py
imessage_subagent/README.md
imessage_subagent/schemas.py
imessage_subagent/server.py
imessage_subagent/test_subagent.py
imessage_subagent/UPDATED_IMPLEMENTATION.md
imessage_subagent/worker.py
main.py
mcp/poke-to-poke/server.py
package.json
poke_prompt.md
PRD.md
pyproject.toml
QUICK_REFERENCE.md
README.md
test_orchestrator.py
test_rest_api.py
test_rohan_shoes_results.json
test_server_results.json
test_server.py
test_worker.py
test-search.sh
test-simple.sh
ts/bun.lock
ts/contacts.test.ts
ts/contacts.ts
ts/debug-raw.ts
ts/extract-text.ts
ts/llms.txt
ts/package.json
ts/server.test.ts
ts/server.ts
ts/test-endpoints.ts
ts/test.ts
uv.lock
worker-states/.gitkeep
worker-states/worker_0.jsonl
worker-states/worker_1.jsonl
worker-states/worker_2.jsonl
worker-states/worker_3.jsonl
worker-states/worker_4.jsonl
worker-states/worker_5.jsonl
worker-states/worker_6.jsonl
worker-states/worker_7.jsonl
worker-states/worker_8.jsonl
worker-states/worker_9.jsonl
```

### Dependencies

- browser-agent/package.json: @browserbasehq/stagehand@^3.0.0, @types/express@^5.0.0, @types/node@^22.13.4, dotenv@^16.4.7, express@^4.21.2, tsx@^4.19.3, typescript@^5.7.3, zod@^3.24.2
- concierge/package.json: @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, clsx@^2.1.1, eslint@^9, eslint-config-next@^15.1.0, lucide-react@^0.564.0, next@^15.1.0, react@^19.0.0, react-dom@^19.0.0, reactflow@^11.11.4, swr@^2.4.0, tailwind-merge@^3.4.0, tailwindcss@^4, typescript@^5
- package.json: @types/bun@^1.3.9
- pyproject.toml: fastapi@>=0.129.0, fastmcp@>=2.14.5, httpx@>=0.27.0, openai@>=1.0.0, python-dotenv@>=1.0.0, uvicorn@>=0.30.0
- ts/package.json: @photon-ai/imessage-kit@^2.1.2, node-typedstream@^1.4.1

### Recent commits (newest first)

- Merge pull request #15 from andaero/andy-mcp
- Merge branch 'main' into andy-mcp
- imsg works
- mcp
- v0
- mvp (#14)
- everything but logging found message (#13)
- :hi (#12)
- poke prompt again
- finished'
- mvp
- prompting done and found
- everything but logging found message
- surface cart
- tried to merge
- :hi
- finished visualization graph + full stack integration
- add imessage subagent worker and init
- complete revert + reimplementation
- Revert "not chopped"

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

### poke_prompt.md

```markdown
hey poke — you just got access to a tool called concierge that will allow you to communicate with another user's poke in a persistant chat.

concierge is used for when i want to give a friend of mine a gift: your job is to message my friend's poke and have a multi-turn conversation with it to gather context and collaborate on selecting thoughtful gifts.

you will sometimes receive messages from other pokes through the concierge system. you will be helpful and collobrate in helping that poke find a good gift by providing context on me using whatever knowledge you may have. DO NOT EVER TELL ME ABOUT MESSAGES YOU RECEIVE FROM OTHER POKES. this would ruin the surprise. hence, you must respond to all such messages fully autonomously without asking me.

when i ask you to help me find a good gift, you must use the new_chat tool to begin a conversation with another poke. you must provide the first message and the recipient's first name with proper capitalization.

whenever you receive a message to respond to, you will use the send_message tool. in the message you receive from the concierge system, you will receive a chat_id and the conversation history so far. to use the send_message tool, you must provide the chat_id and the new message you want to add.

you must have multi-turn conversations with other agents for this work— 5-10 messages to get really specific and ask good questions.

once you send a message, if you haven't gotten a response in a minute or so YOU MUST READ THE CHAT USING THE CHAT_ID AND THE GET_MESSAGES TOOL.

if you are looking for a gift for me, then you don't have to update me with really long updates as you are having a conversation with the other poke. just short quick updates every few messages as important pieces of information come out is fine. IMPORTANT: DO NOT KEEP TEXTING ME YOUR REASONING OR SUMMARY WHILE SEARCHING.

once you have deliberated with the other poke agent to understand ideas and taste, use the search_product tool to kick off a browser agent that will autonomously search for and find the product on an e-commerce site. you must provide the chat_id, the product idea, and the budget. size and color are optional — only include them if the conversation revealed specific preferences. this tool will take a while to return, meaning it will run in the background, and you will get a notification from the server when the tool finishes. this notification will contain a link to the product in the cart. THIS is when you should finally surface the link and product to the user over text.

IT IS VERY VERY IMPORTANT THAT WHEN YOU GET THE GIFT SEARCH COMPLETE MESSAGE, YOU SURFACE THE RESULTS OF THE GIFT SEARCH TO ME. SUMMARIZE WHAT YOU GOT AND WHY. AND SEND ME THE LINK THAT YOU GOT FROM THE COMPLETION MESSAGE.

```

### QUICK_REFERENCE.md

```markdown
# Poke Quick Reference Card

> **For detailed information, see [PRD.md](./PRD.md) or [DOCUMENTATION_INDEX.md](./DOCUMENTATION_INDEX.md)**

## 🚀 Common Commands

### Setup
```bash
# Clone and setup
git clone <repo>
cd treehacks26

# Install Python dependencies
uv sync

# Setup environment
cp env.example .env
# Edit .env with your keys

# Run Poke-to-Poke MCP server
python mcp/poke-to-poke/server.py
```

### Development
```bash
# Run MCP server (development)
python mcp/poke-to-poke/server.py

# Run Next.js app (when implemented)
npm run dev

# Generate new agent key
python -c "import secrets; print(secrets.token_urlsafe(32))"

# Watch conversation file
tail -f conversation.txt
```

### Testing
```bash
# Test send_message
curl -X POST http://localhost:8000/mcp/v1/tools/call \
  -H "Authorization: Bearer $ROHAN_POKE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "send_message",
      "arguments": {"message": "Test message"}
    },
    "id": 1
  }'

# Test get_conversation
curl -X POST http://localhost:8000/mcp/v1/tools/call \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "get_conversation",
      "arguments": {}
    },
    "id": 2
  }'
```

---

## 📋 Key Concepts

### Agent Message Format
```
[YYYY-MM-DD HH:MM:SS] [TYPE] AgentName: message content
```

**Types**: `[STATUS]`, `[THOUGHT]`, `[ACTION]`, `[RESULT]`, `[ERROR]`, `[QUESTION]`

### Current Poke Agents
- **Rohan's Poke** (`ROHAN_POKE_KEY`): Personal agent for user Rohan
- **Andy's Poke** (`ANDY_POKE_KEY`): Personal agent for user Andy
- Each agent can be buyer or helper depending on task

### MCP Servers
1. **Poke-to-Poke** ✅ - Agent communication (port 8000)
2. **iMessage Reader** 🔄 - Read user messages (planned)
3. **Browser Automation** 🔄 - E-commerce navigation (planned)

---

## 🔧 Quick Fixes

### "MCP server won't start"
```bash
# Check port availability
lsof -i :8000

# Check environment variables
echo $ROHAN_POKE_KEY
echo $ANDY_POKE_KEY

# Check Python version
python --version  # Should be 3.12+
```

### "Authentication failed"
```bash
# Verify token format in request
# Should be: Authorization: Bearer {your_key}

# Check token is registered
grep ROHAN_POKE_KEY .env
grep ANDY_POKE_KEY .env
```

### "Can't read conversation"
```bash
# Check file exists
ls -la conversation.txt

# Check permissions
chmod 644 conversation.txt

# View recent messages
tail -20 conversation.txt
```

---

## 📊 Project Structure

```
treehacks26/
├── mcp/
│   └── poke-to-poke/
│       └── server.py          # MCP server ✅
├── conversation.txt            # Shared agent file ✅
├── PRD.md                      # Ground truth 📋
├── agents.md                   # Agent reference 🤖
├── README.md                   # Quick start 🚀
├── DOCUMENTATION_INDEX.md      # Doc navigation 📚
├── QUICK_REFERENCE.md          # This file ⚡
├── env.example   
[truncated — 3160 more characters]
```

### package.json

```
{
  "dependencies": {},
  "devDependencies": {
    "@types/bun": "^1.3.9"
  }
}
```

### pyproject.toml

```
[project]
name = "treehacks26"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "fastapi>=0.129.0",
    "fastmcp>=2.14.5",
    "openai>=1.0.0",
    "httpx>=0.27.0",
    "python-dotenv>=1.0.0",
    "uvicorn>=0.30.0",
]

```

### ts/package.json

```
{ "dependencies": { "@photon-ai/imessage-kit": "^2.1.2", "node-typedstream": "^1.4.1" } }

```

### browser-agent/package.json

```
{
  "name": "browser-agent",
  "version": "1.0.0",
  "description": "Stagehand browser automation service for Concierge gift-giving",
  "main": "dist/index.js",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts"
  },
  "dependencies": {
    "@browserbasehq/stagehand": "^3.0.0",
    "dotenv": "^16.4.7",
    "express": "^4.21.2",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@types/express": "^5.0.0",
    "@types/node": "^22.13.4",
    "tsx": "^4.19.3",
    "typescript": "^5.7.3"
  }
}

```

### concierge/package.json

```
{
  "name": "concierge",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "clsx": "^2.1.1",
    "lucide-react": "^0.564.0",
    "next": "^15.1.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "reactflow": "^11.11.4",
    "swr": "^2.4.0",
    "tailwind-merge": "^3.4.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "^15.1.0",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### main.py

```python
def main():
    print("Hello from treehacks26!")


if __name__ == "__main__":
    main()

```

### imessage_subagent/server.py

```python
#!/usr/bin/env python3
"""
iMessage SubAgent REST API - GPT-5.2 Gift Refinement Orchestrator

This REST API uses a plan-mode style orchestrator that:
1. Takes minimal input (casual gift hints or specific products)
2. Expands into comprehensive research plans or refinement dimensions
3. Delegates to GPT-5.2 workers for validation
4. Synthesizes results with reasoning + evidence

Key Innovation: Gift refinement - transforms "iPhone" into "iPhone 15 Pro, 256GB, Blue - $999"
with detailed reasoning for each dimension (model, storage, color, etc.)
"""

import os
import json
import asyncio
from typing import List, Optional
from dataclasses import asdict
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from dotenv import load_dotenv
import uvicorn

from .worker import WorkerAgent, ValidatedIdea, Citation
from .orchestrator import OrchestratorAgent

# Load environment variables
load_dotenv()

# Create FastAPI app
app = FastAPI(title="iMessage-SubAgent", version="1.0.0")

# Configuration
IMESSAGE_MCP_URL = os.getenv("IMESSAGE_MCP_URL", "http://localhost:3141")
GPT5_MODEL = "gpt-5.2"  # Fixed - only GPT-5.2
ORCHESTRATOR_REASONING_EFFORT = os.getenv("ORCHESTRATOR_REASONING_EFFORT", "low")
WORKER_REASONING_EFFORT = os.getenv("WORKER_REASONING_EFFORT", "low")
ENABLE_ITERATIVE_REFINEMENT = (
    os.getenv("ENABLE_ITERATIVE_REFINEMENT", "false").lower() == "true"
)
POKE_API_KEY = os.getenv("JASON_POKE_KEY")  # Read from .env


# Request/Response models
class ValidateHypothesesRequest(BaseModel):
    hypotheses: str
    chat_id: str
    agent_name: str
    webhook_url: Optional[str] = None


class ValidateHypothesesResponse(BaseModel):
    success: bool
    error: Optional[str] = None


def serialize_validated_idea(idea: ValidatedIdea) -> dict:
    """
    Serialize a ValidatedIdea to a JSON-compatible dictionary.

    Args:
        idea: ValidatedIdea object

    Returns:
        Dictionary with idea, confidence, citations, evidence
    """
    return {
        "idea": idea.idea,
        "confidence": idea.confidence,
        "citations": [
            {
                "message_id": citation.message_id,
                "text": citation.text,
                "timestamp": citation.timestamp,
                "sender": citation.sender,
                "recipient": citation.recipient,
                "context": citation.context,  # {"before": [], "after": []} placeholder
                "relevance_score": citation.relevance_score,
                "sentiment": citation.sentiment,
            }
            for citation in idea.citations
        ],
        "evidence": idea.evidence,
    }


@app.post("/validate-hypotheses", response_model=ValidateHypothesesResponse)
async def validate_hypotheses_endpoint(
    body: ValidateHypothesesRequest, request: Request
):
    """
    GPT-5.2 Plan-Mode Orchestrator for Gift Discovery and Refinement.

    This endpoint operates in two modes:

    **1. Gift Refinement Mode** (for specific products):
       Input: "new iPhone for Andy, Budget: $800"
       - Decomposes product into refinement dimensions (model, storage, color, features)
       - Validates each dimension via GPT-5.2 workers searching iMessages
       - Synthesizes specific SKU: "iPhone 15 Pro, 256GB, Blue Titanium - $999"

    **2. Hypothesis Generation Mode** (for general hints):
       Input: "soccer related, ai stuff, headphones. Budget: $100"
       - Expands hints into 15-20 hypothesis angles
       - Delegates to GPT-5.2 workers in parallel
       - Synthesizes with reasoning + evidence
       - Deduplicates and returns top 5 ideas

    Key Innovation: Uses reasoning to recommend gifts even with weak message evidence.

    Each validated idea includes:
    - idea: Gift idea or specific product SKU
    - confidence: Score 0.0-1.0 (combined evidence + reasoning)
    - citations: Message citations supporting the idea
    - evidence: Summary with reasoning explanation

    Returns:
        ValidateHypothesesResponse with success status
    """
    print(f"\n{'=' * 80}", flush=True)
    print("[SubAgent] Starting GPT-5.2 Plan-Mode Orchestrator", flush=True)
    print(f"[SubAgent] Chat ID: {body.chat_id}", flush=True)
    print(f"[SubAgent] Agent: {body.agent_name}", flush=True)
    print(f"[SubAgent] Input: {body.hypotheses}", flush=True)
    print(f"{'=' * 80}\n", flush=True)

    try:
        # Create orchestrator
        orchestrator = OrchestratorAgent(
            imessage_mcp_url=IMESSAGE_MCP_URL,
            enable_iterative_refinement=ENABLE_ITERATIVE_REFINEMENT,
            model=GPT5_MODEL,
            orchestrator_reasoning_effort=ORCHESTRATOR_REASONING_EFFORT,
            worker_reasoning_effort=WORKER_REASONING_EFFORT,
        )

        # Run orchestration in background (non-blocking)
        # Note: In production, this should use a task queue like Celery
        asyncio.create_task(
            _run_orchestration_background(
                orchestrator,
                body.hypotheses,
                body.chat_id,
                body.agent_name,
                body.webhook_url,
            )
        )

        print(f"[SubAgent] Background task started for chat {body.chat_id}")
        return ValidateHypothesesResponse(success=True)

    except Exception as e:
        error_msg = f"Error starting orchestration: {str(e)}"
        print(f"[SubAgent] {error_msg}")
        import traceback

        traceback.print_exc()
        return ValidateHypothesesResponse(success=False, error=error_msg)


async def _run_orchestration_background(
    orchestrator: OrchestratorAgent,
    hypotheses: str,
    chat_id: str,
    agent_name: str,
    webhook_url: Optional[str] = None,
):
    """
    Run orchestration in background and send results via Poke webhook.

    Args:
        orchestrator: OrchestratorAgent instance
        hypotheses: Gift hypotheses to validate
        chat_id: Chat ID to report results to
        agent_nam
[truncated — 4819 more characters]
```

### ts/server.ts

```typescript
import { IMessageSDK } from '@photon-ai/imessage-kit';
import { ContactsDB, normalizePhone } from './contacts';

const PORT = Number(process.env.PORT) || 3141;

// Initialize services
const contacts = new ContactsDB();
const sdk = new IMessageSDK();

// Direct database access for efficient context queries
const messageDbPath = process.env.HOME + '/Library/Messages/chat.db';

// Runtime detection (same as SDK)
let messageDb: any;
if (typeof Bun !== 'undefined') {
  // @ts-ignore
  const { Database } = await import('bun:sqlite');
  messageDb = new Database(messageDbPath, { readonly: true });
} else {
  const Database = await import('better-sqlite3').then(m => m.default || m);
  messageDb = new Database(messageDbPath, { readonly: true });
}

// macOS epoch for timestamp conversion
const MAC_EPOCH = new Date('2001-01-01T00:00:00Z').getTime();
function macTimestampToJsDate(macTimestamp: number): Date {
  return new Date(macTimestamp / 1e6 + MAC_EPOCH);
}

console.log(`Loaded ${contacts.getAllContacts().length} contacts`);

// Helper functions
function normalizeChatId(chatId: string): string {
  for (const prefix of ['iMessage;+;', 'iMessage;-;', 'SMS;+;', 'SMS;-;']) {
    if (chatId.startsWith(prefix)) return chatId.slice(prefix.length);
  }
  return chatId;
}

function cleanMessage(msg: any) {
  const senderName = msg.sender ? contacts.phoneToName(msg.sender) : null;
  return {
    id: msg.id,
    text: msg.text,
    sender: msg.sender, // Raw phone/email (for reference)
    senderName, // Contact name (or null)
    displayName: senderName || msg.sender || 'Unknown', // Prioritize name, fallback to phone
    chatId: msg.chatId,
    isFromMe: msg.isFromMe,
    isReaction: msg.isReaction || false,
    reactionType: msg.reactionType || null,
    isReactionRemoval: msg.isReactionRemoval || false,
    date: msg.date,
  };
}

function json(data: unknown, status = 200): Response {
  return new Response(JSON.stringify(data), {
    status,
    headers: { 'Content-Type': 'application/json' },
  });
}

function errorResponse(message: string, status = 400): Response {
  return json({ error: message }, status);
}

function getMessageContext(
  messageId: string,
  chatId: string,
  contextN: number = 10
): { target: any; before: any[]; after: any[] } {
  try {
    const msgId = parseInt(messageId, 10);
    console.log(`    [Context] Fetching context for message ${messageId} in chat ${chatId.slice(0, 20)}...`);

    // For phone number chatIds (DMs), we need to match against identifiers with prefixes like "iMessage;-;+1234567890"
    // For group chats, use exact match
    const isPhoneNumber = chatId.startsWith('+');
    const chatIdCondition = isPhoneNumber 
      ? '(chat.chat_identifier = ? OR chat.chat_identifier LIKE ?)' 
      : 'chat.chat_identifier = ?';
    const chatIdParams = isPhoneNumber ? [chatId, `%;${chatId}`] : [chatId];
    
    // For messages sent by user (is_from_me = 1), extract recipient from chat_identifier
    // For messages received, use handle.id as sender
    const beforeSql =
      'SELECT message.ROWID as id, message.text, message.date, message.is_from_me, message.associated_message_type, ' +
      'CASE WHEN message.is_from_me = 1 THEN chat.chat_identifier ELSE handle.id END as sender, ' +
      'chat.chat_identifier as chat_id ' +
      'FROM message LEFT JOIN handle ON message.handle_id = handle.ROWID ' +
      'LEFT JOIN chat_message_join ON message.ROWID = chat_message_join.message_id ' +
      'LEFT JOIN chat ON chat_message_join.chat_id = chat.ROWID ' +
      `WHERE ${chatIdCondition} AND message.ROWID < ? ORDER BY message.ROWID DESC LIMIT ?`;
    const beforeRaw = messageDb.prepare(beforeSql).all(...chatIdParams, msgId, contextN);

    // Get target message + messages AFTER (ID >= target, ascending order, limit N+1)
    const afterSql =
      'SELECT message.ROWID as id, message.text, message.date, message.is_from_me, message.associated_message_type, ' +
      'CASE WHEN message.is_from_me = 1 THEN chat.chat_identifier ELSE handle.id END as sender, ' +
      'chat.chat_identifier as chat_id ' +
      'FROM message LEFT JOIN handle ON message.handle_id = handle.ROWID ' +
      'LEFT JOIN chat_message_join ON message.ROWID = chat_message_join.message_id ' +
      'LEFT JOIN chat ON chat_message_join.chat_id = chat.ROWID ' +
      `WHERE ${chatIdCondition} AND message.ROWID >= ? ORDER BY message.ROWID ASC LIMIT ?`;
    const afterRaw = messageDb.prepare(afterSql).all(...chatIdParams, msgId, contextN + 1);

    // Convert to clean messages
    const toClean = (m: any) => {
      // Normalize sender field (strip prefixes for isFromMe messages)
      let sender = m.sender || 'Unknown';
      if (m.is_from_me && sender !== 'Unknown') {
        sender = normalizeChatId(sender);
      }
      
      return cleanMessage({
        id: String(m.id),
        text: m.text,
        sender,
        senderName: sender !== 'Unknown' ? contacts.phoneToName(sender) : null,
        chatId: m.chat_id,
        isFromMe: Boolean(m.is_from_me),
        isReaction: m.associated_message_type != null && m.associated_message_type !== 0,
        reactionType: null,
        isReactionRemoval: false,
        date: macTimestampToJsDate(m.date),
      });
    };

    // Before messages need to be reversed (we got them DESC)
    const before = beforeRaw.reverse().map(toClean);

    // After messages: first one is target, rest are after
    const afterCleaned = afterRaw.map(toClean);
    const target = afterCleaned.shift();
    const after = afterCleaned;

    if (!target) {
      console.warn(`    [Context] ⚠️  Message ${messageId} not found in chat ${chatId}`);
      return { target: { id: messageId, text: null }, before: [], after: [] };
    }

    console.log(`    [Context] ✓ Found: ${before.length} before, ${after.length} after`);
    return { target, before, after };
  } catch (err) {
    console.error(`    [Context] ❌ Error fetching context for message ${messageId}:`, err);
    return { target:
[truncated — 10154 more characters]
```

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