Thinking hard...
Done!

Semantic match.

How Matching Works

We split what you're looking for into individual desires, estimate how important each one is, and score each desire against the sentences in another person's profile.

The Pipeline

Step 1: Normalize the candidate's about_me

When someone signs up, we rewrite their about_me as a list of short sentences in a fixed format: "I am...", "I enjoy...", "I value...". We also infer implied traits from what the person wrote.

Raw profile text (Sol)
"I'm a cinephile I loooooove watching movies at the cinema, home too with at least a great sound haha. Love series and anime too. And I often go to the theater (should definitely go more). Fun is also having drinks with friends and going out, video games and I wanna do more board games!! Communication and respect is key to everything, the more you say honestly the best it is even if it hurts. I value honesty, respect and silliness."
After normalization (showing a selection)
"I am a cinephile"
"I enjoy watching movies at the cinema"
"I enjoy watching series"
"I enjoy anime"
"I often go to the theater"
"I enjoy having drinks with friends"
"I enjoy going out"
"I enjoy playing video games"
"I value communication"
"I value respect"
"I believe honesty is key to everything"
"I value silliness"
"I believe in not taking yourself too seriously"
"I am social" ← inferred from going out with friends
"I am fun-loving"
"I am straightforward" ← inferred from "say honestly even if it hurts"
…

The normalizer splits compound phrases ("honesty, respect and silliness" becomes three sentences) and infers implied traits ("straightforward" from "say honestly even if it hurts"). Each sentence gets its own embedding, so a desire like "honest" is compared with the sentence about honesty and isn't diluted by the ones about movies and board games.

Step 2: Decompose and normalize your "looking for"

The looking_for text is split into 5–10 individual desires, each with an importance weight. Every desire is rephrased as "a person who...". The rephrasing disambiguates vague words ("fit" → "physically fit and active") and gives every desire the same grammatical shape.

Input
"I like people who are pretty, entertaining, decent at holding a conversation, very open-minded, no more than moderately opinionated, smart but not overly intellectual, a tiny bit dommy"
Decomposed and normalized desires
0.4 a person who is pretty
0.4 a person who is entertaining
0.4 a person who is decent at holding a conversation
0.7 a person who is very open-minded
0.4 a person who is no more than moderately opinionated
0.4 a person who is smart but does not have their entire identity built around intelligence
0.4 a person who is at least a tiny bit dominant in a dating context

Weights reflect how strongly the text expresses each desire:

WeightMeaningSignal in text
1.0Must-have / dealbreaker"must", "only", "essential", "I need"
0.7Strong preference"really", "very important", repeated, clearly core
0.4Preference (default)mentioned once, no special emphasis
0.2Nice-to-have"ideally", "bonus", "a plus"
Step 3: Match each desire against best sentence

For each desire, we compare its embedding against every sentence in the candidate's about_me. The best-matching sentence wins. A desire like "patient with people" finds the one sentence about temperament and isn't diluted by 21 unrelated sentences about hobbies and routines.

Before comparing, we apply structural debiasing. Sentences that start with "I am..." or "I enjoy..." share a grammatical prefix that inflates cosine similarity regardless of meaning. For example, "I am average" and "I am athletic" both start with "I am", which gives them a baseline similarity to any desire about personality traits. We measure this baseline as the cosine similarity between the desire and the bare prefix ("I am" or "I enjoy" with no content) and subtract it. Only the similarity above that baseline counts.

The desires above, scored against this candidate
33%pretty→"I have a sunny vibe" (no appearance info, closest match)
43%entertaining→"I believe in having fun"
51%decent at holding a conversation→"I am a good listener"
70%very open-minded→"I am open"
19%no more than moderately opinionated→"I believe curiosity broadens the mind"
55%smart but not identity-built-around-it→"I value curiosity as a form of intelligence"
21%at least a tiny bit dominant→"I have a sunny vibe" (weak match)
Step 4: Weighted average

Each desire's match score is multiplied by its weight, then averaged.

raw = (0.4×33 + 0.4×43 + 0.4×51 + 0.7×70 + 0.4×19 + 0.4×55 + 0.4×21) / (0.4+0.4+0.4+0.7+0.4+0.4+0.4)

= (13.2 + 17.2 + 20.4 + 49 + 7.6 + 22 + 8.4) / 3.1

= 137.8 / 3.1 = 44.5% (raw) → displayed as 61%

The "very open-minded" desire (weight 0.7) contributes the most, accounting for 49 of the 138 weighted points. "Pretty" scored 33% against "I have a sunny vibe". Sol wrote nothing about appearance, and that was the closest sentence. "Smart but not identity-built-around-it" matched at 55% against "I value curiosity as a form of intelligence". The raw 44.5% is compressed to 61% for display using a power curve, which reduces the gap between good and great matches.

Step 5: Length adjustment

Profiles under 15 words are penalized. A 5-word profile can score well on one desire by accident, and there isn't enough text to tell.

AND vs OR Grouping

By default all desires go into one group (AND), and a candidate must match most of them.

Some looking_for texts describe two different types of person:

A looking_for with two alternatives
"Focused, high on desire/ambition/drive. Intelligent. I like mathematics/theoretical CS, linguistics, neuroscience, or physics. OR a tennis coach."

This is decomposed into two groups. Each group is scored on its own (weighted average within the group). The final score is the best group's score. A tennis coach who knows nothing about theoretical CS can still score perfectly.

Scored against a tennis coach's profile
Candidate sentences: "I am a certified tennis coach", "I enjoy teaching tennis to beginners", "I enjoy teaching tennis to advanced players", "I am patient", "I am encouraging", "I am athletic", "I am disciplined", "I value dedication", "I value practice"
Group A (academic): focused/driven (70%), intelligent (4%), math (0%), CS (9%), linguistics (14%), neuroscience (14%), physics (4%) = 20% raw → 38% displayed
Group B (tennis): tennis coach (100%) = 100% raw → 100% displayed

Final = max(38%, 100%) = 100%

Structural Debiasing

Embedding models encode grammar as well as meaning. Two sentences that share a prefix like "I enjoy..." have inflated cosine similarity even when the activities are unrelated. Without correction, "I enjoy taxes" would get a non-zero score against a desire for "a person who enjoys exploring". Taxes and exploring are unrelated. The score comes from the shared "I enjoy" prefix.

To fix this, for each desire we measure the structural floor: the cosine similarity between the desire embedding and the bare prefix embedding ("I am" or "I enjoy" with no content word). That similarity comes from shared grammar alone.

If the desire "a person who is physically fit" has a cosine similarity of 0.25 against the bare prefix "I am", we know that 0.25 is noise. Any "I am..." candidate sentence needs to score above 0.25 for its match to mean anything. We subtract the floor and rescale:

debiased_score = (raw_similarity − floor) / (max_similarity − floor) × 100

Each sentence shape has its own floor: one for "I enjoy..." sentences, another for "I am...". Sentences that don't match any known pattern (like "I rebuild vintage motorcycles") use the global minimum as their floor, since they don't have prefix inflation to correct for.

Desire Normalization

Raw desires like "fit" or "smart" are ambiguous. The word "fit" could mean physically fit, a good fit for something, or a clothing fit. Embedded as-is, the vector covers all three meanings at once and matches each of them weakly.

By rephrasing every desire as "a person who [verb phrase]", the LLM uses context from your full looking_for text to pick the right meaning. "Fit" in "someone outdoorsy who is fit" becomes "a person who is physically fit and active". That scores 80% against "I am athletic", where the bare word "fit" scored 35%.

Every desire also ends up with similar grammar, so debiasing floors are more predictable and the cosine similarity scale is consistent across desires.

Scaling

Raw cosine similarity between short text embeddings typically ranges from 0.15 (unrelated) to 0.55 (very similar). The difference between a decent match and a perfect match is about 0.20 in cosine space. We scale this band to 0–100%, then apply a power curve.

Step 1: Linear scaling

First, we map the raw cosine range to 0–100:

linear_score = (cosine_similarity − 0.15) / (0.55 − 0.15) × 100

With structural debiasing, the floor is subtracted before this scaling. The effective range shrinks per sentence shape, so high scores are harder to reach.

Step 2: Display compression

Linear scaling exaggerates gaps. A cosine difference of 0.04 (e.g., 0.35 vs 0.39) produces a 10-point gap on the linear scale, but both embeddings capture similar meaning. One happens to share a few more tokens with the query. A "good match" at 50% and a "great match" at 70% are closer in quality than a 20-point gap suggests.

We compress the displayed score with a power curve:

displayed_score = (linear_score / 100)0.6 × 100
CosineLinearDisplayedMeaning
≤ 0.150%0%Unrelated
0.2525%38%Vaguely related
0.3550%66%Moderate match
0.4575%81%Strong match
≥ 0.55100%100%Near-exact match

Why Not Compare Whole Texts?

The previous algorithm compared one embedding of your entire looking_for against one embedding of their entire about_me. It had two problems.

Problem 1: Profile dilution

Take an about_me that covers astronomy, board games, baking sourdough, distance running, and learning Japanese. The single embedding averages all of those topics together, so a search for "astronomy" gets only a moderate match.

Chunk matching fixes this. The "astronomy" desire is matched against the one sentence about stargazing.

Problem 2: Long looking_for inflation

If your looking_for mentions philosophy, fitness, cooking, programming, and hiking, the single embedding becomes a generic "has lots of interests" vector, and almost everyone scores 70–90% against it.

Weighted desire matching fixes this. Each desire is scored on its own. A candidate who matches philosophy but not fitness, cooking, programming, or hiking gets credit only for philosophy.

Known Limitations

Negation blindness. Embeddings encode topic, and negation barely moves them. "Does not smoke" and "I smoke heavily" both contain the word "smoke" and produce similar embeddings. The dealbreaker filters (smoking, drinking, etc.) handle this separately from the match percentage.

Role confusion. "Dominant" and "submissive" are both power dynamics terms, so embeddings see them as similar. A submissive person can score well on a "dominant" desire. Kink role could be added as a dealbreaker filter.

Weight inference limits. The LLM infers importance weights from phrasing cues ("must", "ideally", emphasis). A flat list like "smart, funny, kind" gives everything the same weight (0.4) because nothing in the text signals emphasis.

Normalization artifacts. The LLM that normalizes profiles occasionally infers traits the text doesn't support. "Tech background" might produce "I am detail-oriented", for example. An over-inferred trait creates a false match when it happens to sit close to a desire.

❀

Privacy Policy