Definition, not as a sentence but as a function
Accountability Partner Definition: Seven Predicates, One Integer, Sixty Minutes
Every top-ranking page defines the term as 'a person who supports your goals.' That is a role, not a definition. This page is about the operational definition the matcher actually uses to decide whether two strangers become partners or do not.
TL;DR
Dictionary says an accountability partner is someone who supports your goals. The matcher in src/app/api/auto-match/route.ts says it is whoever clears seven predicates: not unsubscribed, fewer than 2 prior intros, more than 24 hours since signup, no prior match with you, no active session slot already taken, UTC session time within 60 minutes of yours, and the greedy scorer picks the pair. That is the full content of the word on this site.
An accountability partner is a function, not a sentence
Every top-10 SERP defines the term as a role. The matcher here defines it as a boolean returned from seven predicates. The key number is sixty.
- Dictionary layer: 'a person who helps you keep a commitment.' True, unfalsifiable, not useful to a runtime.
- Operational layer: isAccountabilityPartner(a, b) returns true if seven concrete checks pass.
- Six of the checks are booleans. One is an integer comparison: timeDiff(a.utc, b.utc) ≤ 60 minutes.
- Sort by readyScore, bothOld, sessionMatch, smallest diff. Greedily pick the first non-conflicting pair.
- Survive all seven. You are somebody's accountability partner now, per this site's definition of the word.
predicates to evaluate
max UTC drift per pair
session slots per person
file where the definition lives
If you ran the Google search that brought you here, you already read the answer. An accountability partner is someone who helps you keep a commitment. Wikipedia says it, BetterUp paraphrases it, Indeed and MentorLoft and the Columbia GSAS PDF repeat the same sentence in different words. The sentence is not wrong. It is a definition of the role, and a role is a thing a human reader can nod at. The problem is that if you try to ask a program “is X an accountability partner for Y,” the sentence gives you nothing to evaluate. There is no predicate to run. Every top search result is a role description with no function signature.
The operational definition, drawn as a beam
Two candidate humans enter on the left. Seven gates in the middle. One output on the right: the relation “accountability partner.” If any gate returns false, the pair never reaches the output. This is how the matcher in src/app/api/auto-match/route.ts actually thinks about the word.
partner
If any one gate returns false, the pair drops out. The word “accountability partner” is what the algorithm calls whoever survives every gate.
Every predicate the matcher actually evaluates
Pulled from the live source, in the order they execute. Each card carries the filename and line range so the claim is checkable by anyone with a clone of the repo.
Dictionary sentence vs. operational function
Two tabs. The left tab is the answer Google surfaces for this keyword, reduced to a comment block. The right tab is a pseudocode reading of the real matcher, faithful to src/app/api/auto-match/route.ts, lines 74 through 203. The difference is not depth, it is category. One is a role description. The other is a boolean function.
// The operational definition from
// src/app/api/auto-match/route.ts
function isAccountabilityPartner(a, b) {
// (1) both subscribed to the thread
if (a.unsubscribed || b.unsubscribed) return false;
// (2) neither has burned two intros already
if (a.contact_count >= 2) return false;
if (b.contact_count >= 2) return false;
// (3) at least 24 hours since signup
// (unless status = 'ready')
if (now - a.created_at < DAY_MS && a.status !== "ready")
return false;
if (now - b.created_at < DAY_MS && b.status !== "ready")
return false;
// (4) they have not already been matched
if (priorMatchedIds(a).includes(b.id)) return false;
// (5) neither session slot is already in use
if (activeMatchForSession(a, slotA)) return false;
if (activeMatchForSession(b, slotB)) return false;
// (6) UTC session times agree within 60 minutes
if (timeDiff(a.utc, b.utc) > 60) return false;
// (7) the greedy scorer has not allocated
// either slot to a better candidate
return scorer.picks(a, b);
}/api/auto-match/route.ts, lines 74 through 203.Seven predicates, with source line numbers
The anchor predicate is the one with a concrete number. Six of the seven are booleans; the one that names a threshold is the UTC tolerance, and the threshold is 60.
anchor predicate
The ±60 minute UTC tolerance is the one concrete number nobody else names
At src/app/api/auto-match/route.ts:164, the line if (diff > 60) continue; throws out any candidate pair whose scheduled sit times drift more than an hour apart in UTC. That number, 60, is the operational edge of what the word “accountability partner” means on this site. Sit at 6:30 in London and 7:10 in Paris? Partner. Sit at 5:00 and 7:01? Not a partner. The word collapses to a single integer and a comparison operator.
One person can occupy two slots, morning and evening, and gets a fresh 60-minute window for each. That is why the matcher iterates over slots, not users (line 151).
predicate 2
Two-strike rule on prior intros
Line 81: if (c.contact_count >= 2) continue;. Anyone who has received two intros without replying is out of the pool. The comment in the source calls this the serial ghoster threshold.
predicate 3
24-hour cooldown from signup
Lines 88 to 90. A fresh row has to sit in waitlist_entries for 24 hours before the matcher considers it, so the operator can spot-check the signup. Status "ready" jumps the cooldown.
predicate 4
No prior match between the pair
Line 242: if (priorIds.includes(personB.id)) skips anyone A has already been paired with, including matches that expired. Accountability is a new-bond relation, not a retry.
predicate 5
No active match on that session slot
Lines 229 to 238 call getActiveMatchForSession(personA.id, slotA.session). A person sitting twice a day can have a different partner for morning and evening; each slot carries its own accountability relation.
predicate 6 and 7
Unsubscribed filter, plus the scorer that breaks ties
Line 74 drops anyone whose unsubscribed flag is true before the outer loop even sees them; by the time the pair is evaluated, both have an open relationship with the email thread. Lines 183 to 189 then rank the surviving pairs in priority order: status=ready > both old students > matching session duration > smallest UTC diff. The greedy loop at 195 to 203 locks in the top-ranked pair, burns both session slots, and moves on. The word “accountability partner” is whoever the scorer admitted first.
The predicates in evaluation order
The matcher runs them roughly top-down inside the eligibility loop, then again inside the per-pair loop. An early false-return short-circuits everything that follows.
route.ts:74
Filter out unsubscribed accounts
The outer SQL at the top of the handler only selects rows where unsubscribed=false. Every downstream predicate assumes both humans are still talking to the thread. Silence in the inbox stops the relation at the query level.
route.ts:81
Drop anyone who has already burned two intros
contact_count stores how many confirmation emails this row has received. At 2 without a reply, the entry exits the eligible set. The source comment calls this the 'serial ghoster' threshold.
if (c.contact_count >= 2) continue;
route.ts:88-90
Enforce the 24-hour signup cooldown
Fresh rows without a 'ready' status have to age for a day before the matcher touches them. Gives the operator time to eyeball the signup. 'ready' status bypasses.
if (now - createdAt > DAY_MS) eligible.push(c);
route.ts:242
Skip any prior pairing, including expired
getPriorMatchedIds returns every person this candidate has ever been matched to, regardless of status. The relation is strictly new-bond. If you two already failed once, the matcher does not retry.
route.ts:229-238
Require both session slots to be free
Each (personId, session) pair can hold exactly one active match. A twice-a-day meditator can be in two simultaneous partnerships, one per session, but not two in the same slot.
route.ts:162-164
Require UTC time agreement within 60 minutes
The single most important integer on the page. If the two scheduled UTC minute values differ by more than 60, the pair is dropped. Computed wrap-around so 23:50 and 00:10 are 20 minutes apart, not 1420.
const diff = timeDiff(sa.utcMinutes, sb.utcMinutes); if (diff > 60) continue;
route.ts:183-203
Let the greedy scorer pick the surviving pair
Sort remaining candidates by: both 'ready' first, then both are Goenka old students, then matching session_duration, then smallest UTC diff. Walk top-down and take the first non-conflicting pair. Both slots get burned.
Dictionary vs operational, attribute by attribute
Same concept, two definitions. The columns line up on seven attributes where the dictionary sentence goes silent and the code gives a specific answer.
Why collapse the word to a predicate at all
The reason is not pedantry. A role description is fine for someone already in the relation, who wants to check in with the partner they have. It is not fine for a matcher that has to decide, every 30 minutes, whether to create a new row in the matches table between two strangers. The runtime needs a boolean. The boolean has to come from somewhere. If the product owner does not name the predicates, the code will invent them silently.
Making the predicates explicit has a second effect. It forces a commitment to the boundary conditions. At 59 UTC minutes apart, you two are partners. At 61 UTC minutes apart, you are not. The product makes that call. The dictionary cannot.
The general rule the page is arguing for is: when a word names a relation a program must evaluate, the only definition that does any work is the one that returns a boolean. Everything else is a role description that is true but unusable.
Who this definition is for, and who it is not for
- Is for: anyone writing or running a matcher that needs to decide when two people count as an accountability pair. The structure generalizes; the constants are specific to meditation.
- Is for: anyone trying to understand why they were or were not matched on vipassana.cool, because the seven predicates are the full story.
- Is not for: someone looking for a motivational definition to print on a poster. The dictionary sentence works better for that.
- Is not for: accountability in contexts that are not time-bound or not paired. A coach with ten clients is not a partner under this definition, because the slot model assumes exactly two people.
Where the definition actually lives
For anyone who wants to audit the seven predicates directly, everything on this page comes from one file and a handful of line ranges.
- →
src/app/api/auto-match/route.ts:74unsubscribed filter at the SQL layer - →
src/app/api/auto-match/route.ts:81serial ghoster threshold (contact_count >= 2) - →
src/app/api/auto-match/route.ts:88-9024-hour signup cooldown - →
src/app/api/auto-match/route.ts:162-164±60 minute UTC tolerance (the anchor) - →
src/app/api/auto-match/route.ts:183-189scorer priority (ready > old > duration > diff) - →
src/app/api/auto-match/route.ts:229-238per-session active-match guard - →
src/app/api/auto-match/route.ts:242prior-match anti-reflexive check - →
vercel.json:12-13the whole function runs every 30 minutes
Frequently asked questions
What is the shortest accurate definition of an accountability partner on this page?
The shortest accurate definition is a function, not a sentence. An accountability partner, for the purposes of the matcher on this site, is any other person whose scheduled session time in UTC differs from yours by no more than 60 minutes, who is not unsubscribed, who has received fewer than 2 prior intros without replying, whose account is more than 24 hours old (or whose status is 'ready'), who has never been matched with you before, whose same-side session slot (morning or evening) is not already occupied, and whom the greedy pair-scoring loop picks before it picks anyone else. Whoever satisfies all seven of those predicates becomes your accountability partner. Everyone who fails any one of them does not. That is the whole operational content of the word. If a SERP article tells you an accountability partner is 'someone who supports your goals,' it is describing a role. The matcher at src/app/api/auto-match/route.ts describes a relation, and a relation is what the runtime cares about.
Where exactly does the 60-minute UTC window come from in the code?
Line 164 of src/app/api/auto-match/route.ts: 'if (diff > 60) continue;'. The variable 'diff' is computed by the 'timeDiff' helper at lines 49 to 52, which takes two UTC minute values and returns the smaller of their signed difference or its 1440-minute complement (so 23:50 and 00:10 are correctly 20 minutes apart, not 1420). The 60 is a hardcoded integer. It was chosen because real humans who say they sit at 6:30 AM almost always actually sit somewhere in the 6:00 to 7:00 band once they are awake, and because Google Meet does not care if both participants join inside a 60-minute envelope around a single scheduled start. A 30-minute tolerance was rejected during design because it threw out otherwise ideal pairs in adjacent timezones; 90 minutes was rejected because at that range people stop thinking of the other as 'showing up at the same time.'
Why is an accountability partner defined per session slot and not per user?
Because the matcher at lines 105 to 122 iterates over 'slots,' not people. A user with 'frequency = Once a day' generates a single slot (morning_utc). A user with 'frequency = Twice a day' generates two slots (morning_utc and evening_utc). A slot is uniquely identified by (personId, session), where session is either 'morning' or 'evening.' When the greedy loop at 195 to 203 consumes a slot, it adds the key 'personId:session' to the usedSlots set, which means that user can still be paired for the other session with a different person. The practical consequence is that 'your accountability partner' is ambiguous in the singular on this site. A twice-a-day meditator can have two accountability partners simultaneously, one for the morning relation and one for the evening relation, and the two partners do not need to know about each other.
What is the serial ghoster threshold, and is it the same across everyone?
Line 81: 'if (c.contact_count >= 2) continue;'. Every waitlist entry carries a contact_count column that increments whenever the matcher sends that person a confirmation email. If the count reaches 2 without a reply, the entry is ejected from the pool. The threshold is the same across everyone. It is a heuristic that says: the matcher will spend exactly two chances on you, and if both times you do not click through the confirmation link, the pool closes. The operator can manually reset the count, but the automated matcher cannot. This is part of the operational definition because it determines whether you are in the candidate set at all. An accountability partner, in this product, is a person the matcher has not already given up on.
Why does the 24-hour cooldown exist, and what bypasses it?
Lines 88 to 90. A brand new row with contact_count=0 is only eligible for auto-matching if its created_at is more than 24 hours in the past. The bypass is the 'ready' status: if a person has already engaged with the product in some way that sets their waitlist_entries.status to 'ready,' they skip the cooldown and enter the eligible pool on the next 30-minute cron tick. The reason for the delay is human review. The operator (me) eyeballs every signup once before the automated pipeline touches it, and the 24-hour cooldown is wide enough to catch signups made by humans while narrow enough that nobody waits multiple days. The effect on the definition of 'accountability partner' is that a first-time signup has to wait one calendar day before becoming a candidate at all.
Does the prior-match check include matches that failed or expired?
Yes. 'getPriorMatchedIds' (called at line 241) returns every person_b_id from any matches row where person_a_id equals the current person, regardless of status. A match that reached status='expired' or status='ended' still counts as a prior match. The consequence is that the accountability partner relation is strictly anti-reflexive and one-shot. If you and another meditator matched last quarter and both went silent, the matcher will not re-pair the two of you even if your UTC windows overlap perfectly. This is intentional: if the first bond did not produce replies, there is no obvious reason a second attempt will, and the pool has better candidates to spend its slot on. If the users want to retry, they can signal it to the operator manually, who can clear the prior pairing.
How does the scorer decide which candidate pair to admit when multiple are viable?
Lines 183 to 189. The 'allViable' array holds every surviving (slotA, slotB) candidate after the six filter predicates have run. The sort key, in order of decreasing priority, is: both people have status='ready' first, then both are Goenka old students (is_old_student='Yes' on both rows), then session_duration matches (a 30-minute sitter paired with another 30-minute sitter, or a 60 with a 60), then smallest absolute UTC diff. The greedy loop at 195 to 203 walks the sorted list top-down and takes the first non-conflicting pair. A slot is consumed the moment a pair wins it. Two humans who would have been an equally good match on lower-priority keys do not get a second chance if a higher-priority candidate already burned one of their slots. An accountability partner, operationally, is whoever the sort put in front of you first without clashing with an already-taken slot.
Is the operational definition only valid for meditation, or does it generalize?
The concrete parameters are tuned for meditation: 60-minute UTC tolerance assumes an activity that takes 30 to 60 minutes and starts at a rough wall time, not a precise instant; the is_old_student tie-breaker assumes the Goenka tradition's single branch point matters; the 24-hour cooldown assumes a human operator is reviewing signups. But the shape of the definition generalizes. Any accountability product that matches two humans to a shared time-bound activity has to answer the same questions: how close is 'same time,' how long ago did signup have to be, how many prior intros count as a burned candidate, what prior failures disqualify, what breaks ties. Changing the constants (60, 2, 24, 'is_old_student') changes the activity. Keeping the structure fixed keeps the definition operational. Any article that defines accountability partner as 'someone who holds you accountable' has erased exactly the part a program has to fill in to produce a match.
What happens the moment a pair becomes an accountability partner in the database?
A row is inserted into the 'matches' table with status='confirming' and per-person confirmation tokens (src/lib/db.ts:248). Both people receive a confirmation email that either opens an intro thread (if both were already in 'ready' status) or asks for a yes/no click. The token-based confirm endpoint at /api/confirm-match flips person_a_confirmed or person_b_confirmed. Once both confirm, createMeetEvent provisions a permanent Google Meet URL, writes the event ID to the match, stores the meet URL in the meet_links table, and the status flips to 'pending.' From that moment, the word 'accountability partner' applies: it is the other email address on the match row, reachable through a shared thread, scheduled for a single UTC time, with a Meet URL that never changes. The definition is not a belief or a feeling. It is one row in Postgres and one calendar invite.
Is the definition on this page a general-purpose answer or specific to this product?
Both, in layers. The top layer, 'accountability partner is a person who supports your goals,' is the general-purpose answer and is what every other page on the internet gives. It is true and also unfalsifiable and so does not do any work for someone actually trying to get paired with one. The middle layer, 'an accountability partner is a person who occupies a specific time, shows up at it, and notices when you do not,' is where most serious essays on the topic stop. The bottom layer, the one this page is about, is the concrete set of predicates that must all evaluate true for two specific humans to become a partner on a specific product, with specific numbers. The thing nobody else names is the number 60. It takes the vague middle-layer answer and turns it into a boolean. Whether the exact integer is universal is another question; the technique of collapsing the definition to a predicate is.
Want to see the operational definition run on you?
Fill the form; the matcher at /api/auto-match will evaluate the seven predicates against your UTC session time next time it fires. If you clear all seven, you get an accountability partner. Free, in the dana tradition.
Find a Practice Buddy