@gonzih/meet-the-one-ai 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +41 -0
- package/.node-version +1 -0
- package/basis/BERNAYS.md +233 -0
- package/basis/FOUNDING_TRANSCRIPT.md +218 -0
- package/basis/TECH_SPEC.md +303 -0
- package/basis/VALS.md +255 -0
- package/basis/layers/L1_IDENTITY_AUTH.md +78 -0
- package/basis/layers/L2_CONVERSATION.md +159 -0
- package/basis/layers/L3_RECORDING_STORE.md +104 -0
- package/basis/layers/L4_ANALYSIS_PIPELINE.md +257 -0
- package/basis/layers/L5_MATCHING_ENGINE.md +164 -0
- package/basis/layers/L6_CONSENT_INTRODUCTION.md +143 -0
- package/basis/layers/L7_PORTABLE_IDENTITY.md +139 -0
- package/basis/layers/STACK.md +64 -0
- package/basis/schema.sql +203 -0
- package/dist/agent.d.ts +2 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +114 -0
- package/dist/agent.js.map +1 -0
- package/dist/api/routes/auth.d.ts +2 -0
- package/dist/api/routes/auth.d.ts.map +1 -0
- package/dist/api/routes/auth.js +79 -0
- package/dist/api/routes/auth.js.map +1 -0
- package/dist/api/routes/identity.d.ts +2 -0
- package/dist/api/routes/identity.d.ts.map +1 -0
- package/dist/api/routes/identity.js +92 -0
- package/dist/api/routes/identity.js.map +1 -0
- package/dist/api/routes/text-submission.d.ts +2 -0
- package/dist/api/routes/text-submission.d.ts.map +1 -0
- package/dist/api/routes/text-submission.js +56 -0
- package/dist/api/routes/text-submission.js.map +1 -0
- package/dist/api/webhooks/twilio.d.ts +2 -0
- package/dist/api/webhooks/twilio.d.ts.map +1 -0
- package/dist/api/webhooks/twilio.js +144 -0
- package/dist/api/webhooks/twilio.js.map +1 -0
- package/dist/api/webhooks/vapi.d.ts +2 -0
- package/dist/api/webhooks/vapi.d.ts.map +1 -0
- package/dist/api/webhooks/vapi.js +177 -0
- package/dist/api/webhooks/vapi.js.map +1 -0
- package/dist/bot.d.ts +3 -0
- package/dist/bot.d.ts.map +1 -0
- package/dist/bot.js +39 -0
- package/dist/bot.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs/compact-identity.d.ts +2 -0
- package/dist/jobs/compact-identity.d.ts.map +1 -0
- package/dist/jobs/compact-identity.js +159 -0
- package/dist/jobs/compact-identity.js.map +1 -0
- package/dist/jobs/consent-call.d.ts +2 -0
- package/dist/jobs/consent-call.d.ts.map +1 -0
- package/dist/jobs/consent-call.js +70 -0
- package/dist/jobs/consent-call.js.map +1 -0
- package/dist/jobs/export-identity.d.ts +2 -0
- package/dist/jobs/export-identity.d.ts.map +1 -0
- package/dist/jobs/export-identity.js +129 -0
- package/dist/jobs/export-identity.js.map +1 -0
- package/dist/jobs/introduction-call.d.ts +2 -0
- package/dist/jobs/introduction-call.d.ts.map +1 -0
- package/dist/jobs/introduction-call.js +86 -0
- package/dist/jobs/introduction-call.js.map +1 -0
- package/dist/jobs/reanalyze-identity.d.ts +2 -0
- package/dist/jobs/reanalyze-identity.d.ts.map +1 -0
- package/dist/jobs/reanalyze-identity.js +56 -0
- package/dist/jobs/reanalyze-identity.js.map +1 -0
- package/dist/jobs/run-matching.d.ts +2 -0
- package/dist/jobs/run-matching.d.ts.map +1 -0
- package/dist/jobs/run-matching.js +200 -0
- package/dist/jobs/run-matching.js.map +1 -0
- package/dist/jobs/scheduled-matching.d.ts +2 -0
- package/dist/jobs/scheduled-matching.d.ts.map +1 -0
- package/dist/jobs/scheduled-matching.js +44 -0
- package/dist/jobs/scheduled-matching.js.map +1 -0
- package/dist/jobs/transcribe-session.d.ts +2 -0
- package/dist/jobs/transcribe-session.d.ts.map +1 -0
- package/dist/jobs/transcribe-session.js +66 -0
- package/dist/jobs/transcribe-session.js.map +1 -0
- package/dist/lib/anthropic.d.ts +4 -0
- package/dist/lib/anthropic.d.ts.map +1 -0
- package/dist/lib/anthropic.js +32 -0
- package/dist/lib/anthropic.js.map +1 -0
- package/dist/lib/config.d.ts +57 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +73 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/deepgram.d.ts +15 -0
- package/dist/lib/deepgram.d.ts.map +1 -0
- package/dist/lib/deepgram.js +37 -0
- package/dist/lib/deepgram.js.map +1 -0
- package/dist/lib/inngest.d.ts +42 -0
- package/dist/lib/inngest.d.ts.map +1 -0
- package/dist/lib/inngest.js +7 -0
- package/dist/lib/inngest.js.map +1 -0
- package/dist/lib/openai.d.ts +3 -0
- package/dist/lib/openai.d.ts.map +1 -0
- package/dist/lib/openai.js +13 -0
- package/dist/lib/openai.js.map +1 -0
- package/dist/lib/prompts.d.ts +8 -0
- package/dist/lib/prompts.d.ts.map +1 -0
- package/dist/lib/prompts.js +258 -0
- package/dist/lib/prompts.js.map +1 -0
- package/dist/lib/r2.d.ts +7 -0
- package/dist/lib/r2.d.ts.map +1 -0
- package/dist/lib/r2.js +49 -0
- package/dist/lib/r2.js.map +1 -0
- package/dist/lib/session-helpers.d.ts +8 -0
- package/dist/lib/session-helpers.d.ts.map +1 -0
- package/dist/lib/session-helpers.js +31 -0
- package/dist/lib/session-helpers.js.map +1 -0
- package/dist/lib/supabase.d.ts +2 -0
- package/dist/lib/supabase.d.ts.map +1 -0
- package/dist/lib/supabase.js +11 -0
- package/dist/lib/supabase.js.map +1 -0
- package/dist/lib/twilio.d.ts +7 -0
- package/dist/lib/twilio.d.ts.map +1 -0
- package/dist/lib/twilio.js +34 -0
- package/dist/lib/twilio.js.map +1 -0
- package/dist/lib/vapi.d.ts +4 -0
- package/dist/lib/vapi.d.ts.map +1 -0
- package/dist/lib/vapi.js +59 -0
- package/dist/lib/vapi.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +177 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/types/index.d.ts +104 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +28 -0
- package/railway.json +14 -0
- package/src/agent.ts +123 -0
- package/src/api/routes/auth.ts +95 -0
- package/src/api/routes/identity.ts +112 -0
- package/src/api/routes/text-submission.ts +64 -0
- package/src/api/webhooks/twilio.ts +181 -0
- package/src/api/webhooks/vapi.ts +219 -0
- package/src/bot.ts +44 -0
- package/src/index.ts +11 -0
- package/src/jobs/compact-identity.ts +211 -0
- package/src/jobs/consent-call.ts +87 -0
- package/src/jobs/export-identity.ts +166 -0
- package/src/jobs/introduction-call.ts +101 -0
- package/src/jobs/reanalyze-identity.ts +65 -0
- package/src/jobs/run-matching.ts +243 -0
- package/src/jobs/scheduled-matching.ts +59 -0
- package/src/jobs/transcribe-session.ts +77 -0
- package/src/lib/anthropic.ts +37 -0
- package/src/lib/config.ts +81 -0
- package/src/lib/deepgram.ts +57 -0
- package/src/lib/inngest.ts +33 -0
- package/src/lib/openai.ts +14 -0
- package/src/lib/prompts.ts +266 -0
- package/src/lib/r2.ts +79 -0
- package/src/lib/session-helpers.ts +37 -0
- package/src/lib/supabase.ts +15 -0
- package/src/lib/twilio.ts +49 -0
- package/src/lib/vapi.ts +80 -0
- package/src/mcp-server.ts +195 -0
- package/src/types/index.ts +146 -0
- package/supabase/.branches/_current_branch +1 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/.temp/gotrue-version +1 -0
- package/supabase/.temp/pooler-url +1 -0
- package/supabase/.temp/postgres-version +1 -0
- package/supabase/.temp/project-ref +1 -0
- package/supabase/.temp/rest-version +1 -0
- package/supabase/.temp/storage-migration +1 -0
- package/supabase/.temp/storage-version +1 -0
- package/supabase/config.toml +384 -0
- package/supabase/migrations/20260303000000_initial_schema.sql +203 -0
- package/supabase/migrations/20260304000000_brand_consents.sql +13 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Layer 6 — Consent Gate & Introduction
|
|
2
|
+
|
|
3
|
+
**Goal:** Both people say yes before they meet. Neither knows if the other declined. The call from the system feels personal — like a real matchmaker who knows you, not a push notification.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## The Consent Call
|
|
8
|
+
|
|
9
|
+
**Twilio Voice + OpenAI Realtime API** — same infrastructure as intake, different system prompt.
|
|
10
|
+
|
|
11
|
+
This is not an automated robocall. It's the AI playing matchmaker. Warm. Specific. Feels like someone who has been listening to you for weeks and found something real.
|
|
12
|
+
|
|
13
|
+
### System prompt for consent calls
|
|
14
|
+
|
|
15
|
+
Different persona from the intake AI. Less curious, more knowing. It's already done the work. It's presenting a finding.
|
|
16
|
+
|
|
17
|
+
Tone: a good friend who happens to be a brilliant matchmaker. "I think I found someone."
|
|
18
|
+
|
|
19
|
+
### Call structure (AI improvises from this, not a script)
|
|
20
|
+
|
|
21
|
+
1. "I've been thinking about you, and I found someone I think you'd really connect with."
|
|
22
|
+
2. Share 2–3 specific resonances from the `consent_call_framing` field — pulled directly from what the LLM wrote about this pair. Specific, not generic. "You both see the world as a place to explore. You both take your time opening up."
|
|
23
|
+
3. Brief modality framing — "They're looking for something real too" (for long-term). "They're curious about connection with no pressure" (for friends/casual). Never reveals the other person's full modality profile.
|
|
24
|
+
4. "Would you be open to a conversation with them?"
|
|
25
|
+
|
|
26
|
+
User says **yes** → logged, match status updated to `a_accepted` or `b_accepted`.
|
|
27
|
+
User says **no** → logged, match closed. Other person never notified, never called.
|
|
28
|
+
User says **maybe, tell me more** → AI provides one more resonance detail, asks again.
|
|
29
|
+
User doesn't answer → voicemail left (short, warm), follow-up SMS sent, retry next day.
|
|
30
|
+
|
|
31
|
+
### Timing
|
|
32
|
+
|
|
33
|
+
Consent calls go out within 24 hours of match confirmation.
|
|
34
|
+
|
|
35
|
+
Both A and B called within the same calendar day where possible — minimize the window between one person's yes and the other's.
|
|
36
|
+
|
|
37
|
+
If Person A has been called but B hasn't responded within 48 hours → match goes to `stale` status, system re-evaluates.
|
|
38
|
+
|
|
39
|
+
### Privacy
|
|
40
|
+
|
|
41
|
+
The AI never reveals:
|
|
42
|
+
- Name
|
|
43
|
+
- Location
|
|
44
|
+
- Age (unless user specified they care about age range)
|
|
45
|
+
- Appearance
|
|
46
|
+
- Occupation
|
|
47
|
+
- Any identifying detail
|
|
48
|
+
|
|
49
|
+
Only: resonances, shared world-view, modality alignment.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Scheduling the Introduction
|
|
54
|
+
|
|
55
|
+
When both parties accept → system sends SMS to both within minutes:
|
|
56
|
+
|
|
57
|
+
"Great news — you're both in. Pick a time for a 30-minute call:"
|
|
58
|
+
[link to scheduling page]
|
|
59
|
+
|
|
60
|
+
**Cal.com** (open source, self-hostable) for scheduling:
|
|
61
|
+
- Both people see the same available slots (overlapping availability)
|
|
62
|
+
- Pick a time, confirm
|
|
63
|
+
- System books the call
|
|
64
|
+
|
|
65
|
+
Alternative for v0.1: system just proposes 3 specific times via SMS and asks each person to reply with their preference. Manual coordination. Ship faster, automate later.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## The Introduction Call
|
|
70
|
+
|
|
71
|
+
**Twilio conference bridge** — system calls both people at the scheduled time, bridges them.
|
|
72
|
+
|
|
73
|
+
What happens when both pick up:
|
|
74
|
+
|
|
75
|
+
**Option A — System facilitates opening, then drops:**
|
|
76
|
+
"[Person A], I'm connecting you now with someone I think you'll find really interesting. [Person B], same for you. I'll leave you both to it."
|
|
77
|
+
System drops. Two people talking.
|
|
78
|
+
|
|
79
|
+
**Option B — System drops immediately:**
|
|
80
|
+
Both phones ring at the same time. Both answer. They're connected. No preamble.
|
|
81
|
+
|
|
82
|
+
Option A preferred — gives both people a moment to orient before the AI disappears.
|
|
83
|
+
|
|
84
|
+
**What they know about each other entering the call:**
|
|
85
|
+
- Nothing visual
|
|
86
|
+
- Nothing identifying
|
|
87
|
+
- Just: the AI thought you'd vibe
|
|
88
|
+
|
|
89
|
+
That's it. The call IS the reveal.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Post-Introduction Follow-up
|
|
94
|
+
|
|
95
|
+
24 hours after the introduction call → system sends SMS to both:
|
|
96
|
+
|
|
97
|
+
"How did it go?"
|
|
98
|
+
👍 / 👎
|
|
99
|
+
|
|
100
|
+
One tap. No form. No rating system. No essay.
|
|
101
|
+
|
|
102
|
+
Response stored in `matches` table → `outcome` field.
|
|
103
|
+
|
|
104
|
+
System may send a second SMS 3 days later: "Did you two end up talking again?"
|
|
105
|
+
|
|
106
|
+
These outcomes feed the matching engine feedback loop over time.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## If the Introduction Doesn't Lead Anywhere
|
|
111
|
+
|
|
112
|
+
System does nothing immediately. No re-matching pressure.
|
|
113
|
+
|
|
114
|
+
After 2 weeks with no engagement signal → system may reach out:
|
|
115
|
+
"Ready for another introduction? I've been looking."
|
|
116
|
+
|
|
117
|
+
User says yes → back in the queue, next match surfaced.
|
|
118
|
+
User says no → stays dormant. System waits.
|
|
119
|
+
|
|
120
|
+
No nagging. No gamification. No streak mechanics. This isn't Duolingo.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Declined Pairs
|
|
125
|
+
|
|
126
|
+
`declined_pairs` table:
|
|
127
|
+
- `user_a_id`, `user_b_id` — both directions
|
|
128
|
+
- `declined_by` — A, B, or both
|
|
129
|
+
- `declined_at`
|
|
130
|
+
|
|
131
|
+
Once a pair is declined (by either side), they never appear in each other's match pool again.
|
|
132
|
+
|
|
133
|
+
The person who was not called never knows the pair existed.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Cross-Modality Introduction Framing
|
|
138
|
+
|
|
139
|
+
When the match bridges two different modalities, the consent call framing acknowledges the expansion without exposing the other person's modality:
|
|
140
|
+
|
|
141
|
+
"This person has a different way of approaching connection than you might expect, but there's something here I think is worth exploring. The world opens up in interesting ways sometimes. Would you be curious?"
|
|
142
|
+
|
|
143
|
+
Framed as adventure, not mismatch. The platform's north star — life as exploration — embedded in how we talk about the match.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Layer 7 — Portable Identity
|
|
2
|
+
|
|
3
|
+
**Goal:** The user's identity belongs to them. They built it. They can take it. This is a trust mechanic and a differentiation play — no other platform in this space offers it.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## The Concept
|
|
8
|
+
|
|
9
|
+
Most platforms trap your data. Your matches, your conversations, your behavioral patterns — all locked inside their walls. You leave, you lose everything.
|
|
10
|
+
|
|
11
|
+
meet-the-one inverts this. You built your identity by talking to us. That identity is yours. We give it back to you in a form you can keep, use, and carry to other platforms, verticals, or future systems.
|
|
12
|
+
|
|
13
|
+
Most users won't export it. That doesn't matter. The fact that they *can* is what builds trust.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What Gets Exported
|
|
18
|
+
|
|
19
|
+
Four domain files + modality weights. Human-readable. Not a JSON dump — a real document a person can read and recognize themselves in.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
/my-identity/
|
|
23
|
+
README.md
|
|
24
|
+
relationships.md
|
|
25
|
+
desire.md
|
|
26
|
+
money.md
|
|
27
|
+
health.md
|
|
28
|
+
worldview.md
|
|
29
|
+
modality_weights.md
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Each `.md` file: narrative paragraph first (LLM-generated from the structured identity), then key signals listed below. Something like:
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
# My Relationship Identity
|
|
36
|
+
|
|
37
|
+
You take your time. Real trust for you isn't something that happens in a first conversation
|
|
38
|
+
or even a first few weeks — it's something that builds over time, and you've learned to
|
|
39
|
+
recognize when it's genuine vs when you're being performed at.
|
|
40
|
+
|
|
41
|
+
Once you're in, you're in fully. Your relationships tend to go deep or not at all.
|
|
42
|
+
|
|
43
|
+
**Key signals:**
|
|
44
|
+
- Attachment style: secure-leaning, with early-relationship anxiety
|
|
45
|
+
- Conflict mode: avoidant initially, expressive when safe
|
|
46
|
+
- Depth orientation: strong (0.8/1.0)
|
|
47
|
+
- Long-term track record: 2 relationships, 4+ years each
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`worldview.md` includes VALS type, active Eight Games, bifurcation scores — described in plain language, not framework jargon.
|
|
51
|
+
|
|
52
|
+
`modality_weights.md` shows where they lean, described as orientation not label:
|
|
53
|
+
|
|
54
|
+
```markdown
|
|
55
|
+
# How I Approach Connection
|
|
56
|
+
|
|
57
|
+
Primarily oriented toward long-term, deep romantic connection (strong lean).
|
|
58
|
+
Open to and curious about open relationship structures (moderate lean).
|
|
59
|
+
Some interest in adventure-based friendship connection (light lean).
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## How the User Gets It
|
|
65
|
+
|
|
66
|
+
**Trigger via SMS or web:**
|
|
67
|
+
|
|
68
|
+
User texts the system number: "Send me my identity" or "Export my profile"
|
|
69
|
+
→ System generates files → sends download link via SMS
|
|
70
|
+
|
|
71
|
+
Or: web portal (same URL, phone-verified) → "Download my identity" button → zip file downloads
|
|
72
|
+
|
|
73
|
+
LLM generates the narrative markdown files from the structured JSON identity at export time. Fresh render every time — improves as the identity grows.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Format Choice
|
|
78
|
+
|
|
79
|
+
Markdown files, not JSON, not PDF.
|
|
80
|
+
|
|
81
|
+
Why markdown:
|
|
82
|
+
- Human-readable without any app
|
|
83
|
+
- Portable — opens in any text editor, any platform
|
|
84
|
+
- Machine-readable — can be ingested by future AI systems
|
|
85
|
+
- Version-controllable — user can track how their identity evolves over time if they keep multiple exports
|
|
86
|
+
- Aligns with Law's original vision: "protected markdown files or something"
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Vertical Architecture — One Identity, Multiple Brands
|
|
91
|
+
|
|
92
|
+
The same identity graph powers multiple brand surfaces.
|
|
93
|
+
|
|
94
|
+
Each brand = different Twilio phone number + different system prompt persona + different modality priority in matching.
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
+1-XXX-SOUL-001 → meet-the-one (long-term, soul-level — flagship)
|
|
98
|
+
+1-XXX-OPEN-001 → [open/poly brand — open relationship, polyamory priority]
|
|
99
|
+
+1-XXX-KINK-001 → [kink brand — desire-specific community priority]
|
|
100
|
+
+1-XXX-PLAY-001 → [adventure brand — friends, lifestyle, activity priority]
|
|
101
|
+
+1-XXX-NOW-001 → [casual brand — hookup, low-commitment priority]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**When user calls any number:**
|
|
105
|
+
- System checks if phone number is already in `users` table
|
|
106
|
+
- Yes: loads existing identity, shifts conversation lens to this brand's context
|
|
107
|
+
- No: new intake begins, identity starts fresh, immediately part of the same graph
|
|
108
|
+
|
|
109
|
+
Identity built on the casual brand enriches their profile on the flagship brand. It's the same person — the system knows it.
|
|
110
|
+
|
|
111
|
+
User consents per brand to what their identity is used for. They can be on meet-the-one and the adventure brand but not the kink brand. Consent tracked per vertical.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Privacy & Data Ownership
|
|
116
|
+
|
|
117
|
+
User can request full data deletion at any time. Text "delete my data" or use web portal.
|
|
118
|
+
|
|
119
|
+
What gets deleted:
|
|
120
|
+
- All audio recordings
|
|
121
|
+
- All transcripts
|
|
122
|
+
- Compacted identity profile
|
|
123
|
+
- Modality weights
|
|
124
|
+
- All match records
|
|
125
|
+
- All embeddings
|
|
126
|
+
|
|
127
|
+
What remains briefly: anonymized aggregate signals used for model improvement (no PII, no identifying content — just pattern counts). Deleted within 30 days.
|
|
128
|
+
|
|
129
|
+
GDPR / CCPA compliant by design — data deletion is the default path when requested, not a support ticket.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Future Vision
|
|
134
|
+
|
|
135
|
+
The portable identity becomes a credential.
|
|
136
|
+
|
|
137
|
+
Long-term: the identity file exported from meet-the-one could be imported into other compatible platforms. "Bring your soul x-ray." Skip the intake. The work you did here travels with you.
|
|
138
|
+
|
|
139
|
+
This is the play Law described — an open platform where the identity belongs to the user. Even if they leave meet-the-one entirely, they have something real. That's what builds the relationship. That's what earns the trust.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Tech Stack — meet-the-one.ai
|
|
2
|
+
|
|
3
|
+
**Principle:** Lowest friction to ship. Fewest moving parts to operate. Swap components as scale demands.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Full Stack
|
|
8
|
+
|
|
9
|
+
| Layer | Tool | Role |
|
|
10
|
+
|-------|------|------|
|
|
11
|
+
| **Hosting** | Railway or Fly.io | Simple deploys, no DevOps, global edge |
|
|
12
|
+
| **Database** | Supabase (PostgreSQL + pgvector) | Auth, DB, vector search, storage — one platform |
|
|
13
|
+
| **Job queue** | Inngest | Serverless background jobs, no Redis to manage |
|
|
14
|
+
| **Phone / SMS** | Twilio | PSTN calls, SMS, Media Streams — one vendor |
|
|
15
|
+
| **Voice AI (v0.1)** | Vapi.ai | Abstracts Twilio + STT + LLM + TTS — fastest to ship |
|
|
16
|
+
| **Voice AI (v1)** | Twilio + OpenAI Realtime API | Full control, lower latency, lower cost at scale |
|
|
17
|
+
| **Conversation LLM** | Claude claude-sonnet-4-6 (Anthropic) | Sensitive psychological content, long context |
|
|
18
|
+
| **Analysis LLM** | Claude claude-opus-4-6 (Anthropic) | Accuracy over speed, offline batch |
|
|
19
|
+
| **Matching LLM** | Claude claude-sonnet-4-6 (Anthropic) | Volume + quality balance |
|
|
20
|
+
| **Transcription** | Deepgram Nova-2 | Fast, accurate, cheap, conversational speech |
|
|
21
|
+
| **Embeddings** | OpenAI text-embedding-3-large | Best semantic capture for psychological content |
|
|
22
|
+
| **Audio storage** | Cloudflare R2 | Cheap egress, S3-compatible |
|
|
23
|
+
| **Scheduling** | Cal.com | Open source, self-hostable, no per-booking fees |
|
|
24
|
+
| **Analytics** | PostHog | Self-hostable, full control, no data leaving |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## User Experience Path (zero friction)
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
1. See URL
|
|
32
|
+
2. Enter phone number
|
|
33
|
+
3. Enter SMS code
|
|
34
|
+
4. "We'll call you shortly"
|
|
35
|
+
5. Phone rings (within 2–5 min)
|
|
36
|
+
6. Talk to AI — no time limit
|
|
37
|
+
7. Hang up when done
|
|
38
|
+
|
|
39
|
+
[background: transcript → analysis → embedding → matching]
|
|
40
|
+
|
|
41
|
+
8. Phone rings — "I found someone"
|
|
42
|
+
9. Say yes or no
|
|
43
|
+
10. SMS with scheduling link (if yes + they said yes)
|
|
44
|
+
11. Pick a time
|
|
45
|
+
12. Phone rings at scheduled time
|
|
46
|
+
13. Two people talking
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**User inputs to first AI conversation: 3** (phone, code, answer the call)
|
|
50
|
+
**User inputs to first introduction: 5** (+ consent call answer + time slot pick)
|
|
51
|
+
|
|
52
|
+
No app. No profile. No photos. No swiping. No form longer than one field.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## What We Don't Build (v0.1)
|
|
57
|
+
|
|
58
|
+
- Mobile app (phone browser + calls = already mobile)
|
|
59
|
+
- Photo upload (never — by design)
|
|
60
|
+
- In-app chat (introduction call comes first)
|
|
61
|
+
- Payment (find PMF first)
|
|
62
|
+
- Admin dashboard (logs + Supabase UI for now)
|
|
63
|
+
- User-facing profile page (nothing to look at — intentional)
|
|
64
|
+
- Any social features (not a social network)
|
package/basis/schema.sql
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
-- meet-the-one.ai — Supabase schema
|
|
2
|
+
-- Run this in Supabase SQL editor (or via supabase db push)
|
|
3
|
+
|
|
4
|
+
-- ─── Extensions ───────────────────────────────────────────────────────────────
|
|
5
|
+
create extension if not exists vector;
|
|
6
|
+
|
|
7
|
+
-- ─── users ────────────────────────────────────────────────────────────────────
|
|
8
|
+
create table if not exists users (
|
|
9
|
+
id uuid primary key default gen_random_uuid(),
|
|
10
|
+
phone text unique not null, -- E.164
|
|
11
|
+
brand text not null default 'flagship',
|
|
12
|
+
created_at timestamptz not null default now(),
|
|
13
|
+
last_active timestamptz
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
create index if not exists users_phone_idx on users (phone);
|
|
17
|
+
|
|
18
|
+
-- ─── sessions ─────────────────────────────────────────────────────────────────
|
|
19
|
+
create table if not exists sessions (
|
|
20
|
+
id uuid primary key default gen_random_uuid(),
|
|
21
|
+
user_id uuid not null references users(id) on delete cascade,
|
|
22
|
+
source text not null check (source in ('vapi','twilio_direct','text')),
|
|
23
|
+
audio_r2_key text, -- null for text sessions
|
|
24
|
+
transcript_r2_key text,
|
|
25
|
+
raw_text text, -- for text submissions
|
|
26
|
+
time_of_day text check (time_of_day in ('morning','afternoon','evening','late_night')),
|
|
27
|
+
duration_seconds int,
|
|
28
|
+
analysis_status text not null default 'pending'
|
|
29
|
+
check (analysis_status in ('pending','processing','complete','error')),
|
|
30
|
+
created_at timestamptz not null default now()
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
create index if not exists sessions_user_idx on sessions (user_id);
|
|
34
|
+
create index if not exists sessions_status_idx on sessions (analysis_status);
|
|
35
|
+
|
|
36
|
+
-- ─── identities ───────────────────────────────────────────────────────────────
|
|
37
|
+
create table if not exists identities (
|
|
38
|
+
id uuid primary key default gen_random_uuid(),
|
|
39
|
+
user_id uuid unique not null references users(id) on delete cascade,
|
|
40
|
+
base_profile jsonb not null default '{}',
|
|
41
|
+
modality_weights jsonb not null default '{}',
|
|
42
|
+
signal_completeness_score float not null default 0,
|
|
43
|
+
session_count int not null default 0,
|
|
44
|
+
ready_for_matching boolean not null default false,
|
|
45
|
+
last_updated timestamptz not null default now()
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
create index if not exists identities_user_idx on identities (user_id);
|
|
49
|
+
create index if not exists identities_ready_idx on identities (ready_for_matching);
|
|
50
|
+
|
|
51
|
+
-- ─── identity_modality_embeddings ─────────────────────────────────────────────
|
|
52
|
+
-- One row per (user, modality) — 3072-dim vector
|
|
53
|
+
create table if not exists identity_modality_embeddings (
|
|
54
|
+
id uuid primary key default gen_random_uuid(),
|
|
55
|
+
user_id uuid not null references users(id) on delete cascade,
|
|
56
|
+
modality text not null,
|
|
57
|
+
embedding vector(1536) not null,
|
|
58
|
+
updated_at timestamptz not null default now(),
|
|
59
|
+
unique (user_id, modality)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
-- IVFFlat index (1536 dims fits within pgvector 2000-dim limit)
|
|
63
|
+
create index if not exists ime_embedding_idx
|
|
64
|
+
on identity_modality_embeddings
|
|
65
|
+
using ivfflat (embedding vector_cosine_ops)
|
|
66
|
+
with (lists = 100);
|
|
67
|
+
|
|
68
|
+
-- ─── matches ──────────────────────────────────────────────────────────────────
|
|
69
|
+
create table if not exists matches (
|
|
70
|
+
id uuid primary key default gen_random_uuid(),
|
|
71
|
+
user_a_id uuid not null references users(id),
|
|
72
|
+
user_b_id uuid not null references users(id),
|
|
73
|
+
primary_modality text not null,
|
|
74
|
+
cross_modality boolean not null default false,
|
|
75
|
+
cross_modality_bridge text,
|
|
76
|
+
confidence_score float not null,
|
|
77
|
+
resonances jsonb,
|
|
78
|
+
tensions jsonb,
|
|
79
|
+
tension_fatal boolean not null default false,
|
|
80
|
+
consent_call_framing text,
|
|
81
|
+
status text not null default 'pending_consent'
|
|
82
|
+
check (status in ('pending_consent','a_accepted','b_accepted','both_accepted','a_declined','b_declined','introduced','expired')),
|
|
83
|
+
created_at timestamptz not null default now(),
|
|
84
|
+
updated_at timestamptz not null default now()
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
create index if not exists matches_user_a_idx on matches (user_a_id);
|
|
88
|
+
create index if not exists matches_user_b_idx on matches (user_b_id);
|
|
89
|
+
create index if not exists matches_status_idx on matches (status);
|
|
90
|
+
|
|
91
|
+
-- ─── declined_pairs ───────────────────────────────────────────────────────────
|
|
92
|
+
create table if not exists declined_pairs (
|
|
93
|
+
id uuid primary key default gen_random_uuid(),
|
|
94
|
+
user_a_id uuid not null references users(id),
|
|
95
|
+
user_b_id uuid not null references users(id),
|
|
96
|
+
declined_by uuid not null references users(id),
|
|
97
|
+
reason text,
|
|
98
|
+
created_at timestamptz not null default now(),
|
|
99
|
+
unique (user_a_id, user_b_id)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
-- ─── RPC: upsert_modality_embedding ──────────────────────────────────────────
|
|
103
|
+
create or replace function upsert_modality_embedding(
|
|
104
|
+
p_user_id uuid,
|
|
105
|
+
p_modality text,
|
|
106
|
+
p_embedding vector(1536)
|
|
107
|
+
)
|
|
108
|
+
returns void
|
|
109
|
+
language plpgsql
|
|
110
|
+
as $$
|
|
111
|
+
begin
|
|
112
|
+
insert into identity_modality_embeddings (user_id, modality, embedding, updated_at)
|
|
113
|
+
values (p_user_id, p_modality, p_embedding, now())
|
|
114
|
+
on conflict (user_id, modality)
|
|
115
|
+
do update set
|
|
116
|
+
embedding = excluded.embedding,
|
|
117
|
+
updated_at = now();
|
|
118
|
+
end;
|
|
119
|
+
$$;
|
|
120
|
+
|
|
121
|
+
-- ─── RPC: match_identities (global cosine search) ────────────────────────────
|
|
122
|
+
create or replace function match_identities(
|
|
123
|
+
p_user_id uuid,
|
|
124
|
+
p_modality text,
|
|
125
|
+
p_embedding vector(1536),
|
|
126
|
+
p_limit int default 50
|
|
127
|
+
)
|
|
128
|
+
returns table (
|
|
129
|
+
user_id uuid,
|
|
130
|
+
modality text,
|
|
131
|
+
similarity float
|
|
132
|
+
)
|
|
133
|
+
language plpgsql
|
|
134
|
+
as $$
|
|
135
|
+
begin
|
|
136
|
+
return query
|
|
137
|
+
select
|
|
138
|
+
ime.user_id,
|
|
139
|
+
ime.modality,
|
|
140
|
+
1 - (ime.embedding <=> p_embedding) as similarity
|
|
141
|
+
from identity_modality_embeddings ime
|
|
142
|
+
join identities i on i.user_id = ime.user_id
|
|
143
|
+
where
|
|
144
|
+
ime.user_id <> p_user_id
|
|
145
|
+
and ime.modality = p_modality
|
|
146
|
+
and i.ready_for_matching = true
|
|
147
|
+
order by ime.embedding <=> p_embedding
|
|
148
|
+
limit p_limit;
|
|
149
|
+
end;
|
|
150
|
+
$$;
|
|
151
|
+
|
|
152
|
+
-- ─── RPC: match_identities_by_modality (multi-modality) ──────────────────────
|
|
153
|
+
create or replace function match_identities_by_modality(
|
|
154
|
+
p_user_id uuid,
|
|
155
|
+
p_embeddings jsonb, -- {"long_term": [...], "casual": [...], ...}
|
|
156
|
+
p_limit int default 50
|
|
157
|
+
)
|
|
158
|
+
returns table (
|
|
159
|
+
user_id uuid,
|
|
160
|
+
best_modality text,
|
|
161
|
+
best_similarity float
|
|
162
|
+
)
|
|
163
|
+
language plpgsql
|
|
164
|
+
as $$
|
|
165
|
+
declare
|
|
166
|
+
modality_key text;
|
|
167
|
+
emb vector(1536);
|
|
168
|
+
begin
|
|
169
|
+
create temp table _scores (
|
|
170
|
+
user_id uuid,
|
|
171
|
+
modality text,
|
|
172
|
+
similarity float
|
|
173
|
+
) on commit drop;
|
|
174
|
+
|
|
175
|
+
for modality_key in select jsonb_object_keys(p_embeddings)
|
|
176
|
+
loop
|
|
177
|
+
emb := (select array_agg(v::float)::vector(1536)
|
|
178
|
+
from jsonb_array_elements_text(p_embeddings->modality_key) as v);
|
|
179
|
+
|
|
180
|
+
insert into _scores
|
|
181
|
+
select
|
|
182
|
+
ime.user_id,
|
|
183
|
+
ime.modality,
|
|
184
|
+
1 - (ime.embedding <=> emb)
|
|
185
|
+
from identity_modality_embeddings ime
|
|
186
|
+
join identities i on i.user_id = ime.user_id
|
|
187
|
+
where
|
|
188
|
+
ime.user_id <> p_user_id
|
|
189
|
+
and ime.modality = modality_key
|
|
190
|
+
and i.ready_for_matching = true
|
|
191
|
+
order by ime.embedding <=> emb
|
|
192
|
+
limit p_limit;
|
|
193
|
+
end loop;
|
|
194
|
+
|
|
195
|
+
return query
|
|
196
|
+
select distinct on (s.user_id)
|
|
197
|
+
s.user_id,
|
|
198
|
+
s.modality as best_modality,
|
|
199
|
+
s.similarity as best_similarity
|
|
200
|
+
from _scores s
|
|
201
|
+
order by s.user_id, s.similarity desc;
|
|
202
|
+
end;
|
|
203
|
+
$$;
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAqCA,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqFlF"}
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { writeFile } from "fs/promises";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { Redis } from "ioredis";
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const redis = new Redis(process.env.REDIS_URL ?? "redis://localhost:6379");
|
|
9
|
+
const SYSTEM_PROMPT = "You are a compassionate AI matchmaker helping people find meaningful connections. " +
|
|
10
|
+
"Use the dating tools to manage profiles and matches. " +
|
|
11
|
+
"Be warm, curious, and insightful. Ask about values, life goals, and what matters to them — not just surface traits. " +
|
|
12
|
+
"When a user first arrives, help them create their profile by asking thoughtful questions. " +
|
|
13
|
+
"Proactively look for compatible matches and suggest them with care.";
|
|
14
|
+
async function getMcpConfigPath(userId) {
|
|
15
|
+
const configPath = join(tmpdir(), `mcp-dating-${userId}.json`);
|
|
16
|
+
// In dev use src path; in prod use compiled dist path
|
|
17
|
+
const mcpServerPath = join(__dirname, "../dist/mcp-server.js");
|
|
18
|
+
const config = {
|
|
19
|
+
mcpServers: {
|
|
20
|
+
dating: {
|
|
21
|
+
command: "node",
|
|
22
|
+
args: [mcpServerPath],
|
|
23
|
+
env: {
|
|
24
|
+
USER_ID: userId,
|
|
25
|
+
REDIS_URL: process.env.REDIS_URL ?? "redis://localhost:6379",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
31
|
+
return configPath;
|
|
32
|
+
}
|
|
33
|
+
export async function sendMessage(userId, message) {
|
|
34
|
+
const mcpConfigPath = await getMcpConfigPath(userId);
|
|
35
|
+
const sessionId = await redis.get(`dating:session:${userId}`);
|
|
36
|
+
const args = [
|
|
37
|
+
"--print",
|
|
38
|
+
"--dangerously-skip-permissions",
|
|
39
|
+
"--output-format",
|
|
40
|
+
"stream-json",
|
|
41
|
+
"--mcp-config",
|
|
42
|
+
mcpConfigPath,
|
|
43
|
+
];
|
|
44
|
+
if (sessionId) {
|
|
45
|
+
args.push("--resume", sessionId);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Prepend system context on first message
|
|
49
|
+
args.push("--system-prompt", SYSTEM_PROMPT);
|
|
50
|
+
}
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const proc = spawn("claude", args, {
|
|
53
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
54
|
+
env: { ...process.env },
|
|
55
|
+
});
|
|
56
|
+
let outputText = "";
|
|
57
|
+
let resultSessionId = "";
|
|
58
|
+
let stderr = "";
|
|
59
|
+
proc.stdout.on("data", (chunk) => {
|
|
60
|
+
for (const line of chunk.toString().split("\n")) {
|
|
61
|
+
if (!line.trim())
|
|
62
|
+
continue;
|
|
63
|
+
try {
|
|
64
|
+
const event = JSON.parse(line);
|
|
65
|
+
if (event.type === "assistant") {
|
|
66
|
+
const msg = event.message;
|
|
67
|
+
if (msg?.content) {
|
|
68
|
+
for (const block of msg.content) {
|
|
69
|
+
if (block.type === "text" && block.text) {
|
|
70
|
+
outputText += block.text;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (event.type === "result") {
|
|
76
|
+
resultSessionId = event.session_id ?? "";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// non-JSON line — ignore
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
proc.stderr.on("data", (chunk) => {
|
|
85
|
+
stderr += chunk.toString();
|
|
86
|
+
});
|
|
87
|
+
proc.on("close", async (code) => {
|
|
88
|
+
if (resultSessionId) {
|
|
89
|
+
await redis.set(`dating:session:${userId}`, resultSessionId).catch(console.error);
|
|
90
|
+
}
|
|
91
|
+
if (!outputText && code !== 0) {
|
|
92
|
+
console.error(`[agent:${userId}] exited ${code}: ${stderr}`);
|
|
93
|
+
reject(new Error(`Claude exited with code ${code}`));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
resolve(outputText || "I'm here to help you find love! What would you like to explore?");
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
proc.on("error", (err) => {
|
|
100
|
+
reject(new Error(`Failed to spawn claude: ${err.message}`));
|
|
101
|
+
});
|
|
102
|
+
// Send user message via stdin in stream-json format
|
|
103
|
+
const input = JSON.stringify({
|
|
104
|
+
type: "user",
|
|
105
|
+
message: { role: "user", content: message },
|
|
106
|
+
}) + "\n";
|
|
107
|
+
proc.stdin.write(input, (err) => {
|
|
108
|
+
if (err)
|
|
109
|
+
console.error(`[agent:${userId}] stdin write error:`, err);
|
|
110
|
+
});
|
|
111
|
+
proc.stdin.end();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|