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.
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.
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.
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.
Weights reflect how strongly the text expresses each desire:
| Weight | Meaning | Signal in text |
|---|---|---|
1.0 | Must-have / dealbreaker | "must", "only", "essential", "I need" |
0.7 | Strong preference | "really", "very important", repeated, clearly core |
0.4 | Preference (default) | mentioned once, no special emphasis |
0.2 | Nice-to-have | "ideally", "bonus", "a plus" |
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.
Each desire's match score is multiplied by its weight, then averaged.
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.
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.
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:
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.
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:
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.
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.
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.
First, we map the raw cosine range to 0–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.
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:
| Cosine | Linear | Displayed | Meaning |
|---|---|---|---|
| ≤ 0.15 | 0% | 0% | Unrelated |
| 0.25 | 25% | 38% | Vaguely related |
| 0.35 | 50% | 66% | Moderate match |
| 0.45 | 75% | 81% | Strong match |
| ≥ 0.55 | 100% | 100% | Near-exact match |
The previous algorithm compared one embedding of your entire looking_for against one embedding of their entire about_me. It had two problems.
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.
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.
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.
❀