# Handoff 01: Prospect classifier (server-side)

**Priority:** P1
**Status:** Core implementation done. MCP wiring remains.

## Platform map

**lilith-platform.live**
- hosts `@quinn/api` on apricot :3030
  - `/m/messages/*` — message + contact read surface
  - `/api/vip-roster` — VIP list source of truth
  - `client-intel` mirror — nightly pull from @mac-sync `/my/prospects`

**quinn-m-mcp** (stdio, runs on the Claude host)
- calls `@quinn/api /m/messages/*`
- tools: `search_messages`, `list_conversations`, `get_thread`, `list_contacts`, `list_devices`
- no prospect tools yet — this is the remaining gap

**@mac-sync** (apricot :3201)
- `icloud.messages` / `icloud.contacts` — synced from macOS via Swift client
- `icloud.prospect` — classifier output (DONE)
- `GET  /my/prospects` (DONE)
- `GET  /my/prospects/:handle` (DONE)
- `POST /admin/prospects/reclassify` (DONE)

## Why @mac-sync owns classification

`chat.db` + `CNContactStore` data is synced into `icloud.messages` + `icloud.contacts` by the Swift client. @mac-sync is the only place that data exists in queryable Postgres form. The classifier runs here; @quinn/api and `quinn-m-mcp` are consumers, not owners.

## What's done

- `src/server/src/entities/prospect/` — `icloud.prospect` table, migration `2026-05-12_prospect_initial`
- `src/server/src/features/prospect/index.ts` — `classifyHandle`, `reclassifyDevice`; tier decision tree, heat bucketing, metro from area code, v0 rate-quote REGULAR heuristic
- `GET /my/prospects` + `GET /my/prospects/:handle` (SSO-protected, `/my` surface)
- `POST /admin/prospects/reclassify` (service-token-protected, `/admin` surface)

## What's left

### 1. quinn-m-mcp: add prospect tools

File: `@applications/quinn-m-mcp/src/client.ts`  
Add `getProspect(handle, deviceId)` and `listProspects(filter)` calling `@mac-sync`'s `/my/prospects` directly — different base URL and token from `QUINN_M_BASE_URL`.

New env vars for the MCP process:
- `MAC_SYNC_BASE_URL` (e.g. `http://apricot.local:3201`)
- `MAC_SYNC_TOKEN` (SSO or service token — confirm which is appropriate for the Claude agent identity)

File: `@applications/quinn-m-mcp/src/index.ts`  
Add two tools:
- `get_prospect` — takes `handle` + optional `deviceId`; returns tier, heat, metro, msg counts, flags
- `list_prospects` — takes `?tier=`, `?heat=`, `?metro=`, `?deviceId=`, `?limit=`

### 2. @quinn/api: client-intel mirror (nightly)

Pull `GET /my/prospects?since=<last_run>` from @mac-sync and upsert into the apricot `client` table. Keeps @quinn/api's client-intel surface current without @mac-sync becoming a general-purpose API dependency.

### 3. External lists

- **BLOCK_LIST**: `~/.local/share/quinn-outreach/block-list.json` on plum — rsync to apricot on a cron, configure `BLOCK_LIST_PATH` in @mac-sync's env.
- **VIP roster**: `@quinn/api` `/api/vip-roster` — configure `VIP_ROSTER_URL` + `SERVICE_TOKEN` in @mac-sync's env. Fetched live on each classify run.

## Tier decision tree (implemented)

1. `BLOCK_LIST` match → `BLOCK`
2. VIP roster match → `VIP`
3. Present in `icloud.contacts` → `PEER`
4. ≥2 rate-quoted outbounds with <24h response, ≥14d apart → `REGULAR`
5. Else → `PROSPECT`

Heat: `HOT` <7d, `WARM` 7–30d, `COLD` 30–90d, `DEAD` ≥90d (from last inbound).  
Metro: derived from US area code (LA/NYC/SF/CHI seeded inline in `features/prospect/index.ts`).

## Verification

- Boot @mac-sync, `POST /admin/prospects/reclassify` with a real device id → confirm rows in `icloud.prospect`.
- `GET /my/prospects?tier=PEER` — should match Quinn's contact list.
- `GET /my/prospects?tier=REGULAR` — should match regulars Quinn would name.
- After MCP tools land: `get_prospect` from Claude returns correct tier for a known handle.

## Out of scope (v0)

- **Lifetime $**: needs `engine_drafts` + booking amounts from @quinn/api.
- **State decay** beyond heat bucketing.
- **Tier-change push notifications** — log-only.
- **BLOCK writes** from the classifier — block-list edits stay manual via `block-cli.py`.
