Skip to content

Accountability Partners -- Feature Exploration & PRD

Product: Objectuve (codename: Enkidu) Feature: Accountability Partners (Roadmap #19) Status: Shipped — May 2026 Date: April 2026


1. Problem Statement

Solo goal-tracking suffers from a single, fundamental failure mode: the only person who notices when you stop is you. And you are the easiest person to negotiate with. Objectuve already provides streaks, XP, and coach check-ins to address motivation decay, but all of these are one-directional -- the app talks to the user, and the user can ignore the app. There is no human cost to dropping the ball.

Research on goal adherence consistently shows that having another person who is aware of your commitment and who will notice a lapse produces the strongest retention effect outside of financial stakes. This is not about "being social" -- it is about making the act of not showing up slightly harder, because someone specific will notice.

Three problems:

  1. Invisible lapses. A user can silently abandon a goal for days before a system reminder fires. By then, momentum is already lost.
  2. Motivation is solo. Streaks and badges lose potency over time. A real human who shares a similar struggle creates durable, adaptive motivation.
  3. No reciprocity loop. The existing ally system is view-only. You can see someone's activity in the feed, but there is no structured "we are doing this together" commitment.

Accountability Partners turns a passive social graph (allies) into an active accountability structure -- a single partner who sees your check-ins, gets alerted when you miss, and earns shared rewards when both show up.


2. Goals & Success Metrics

Primary Goals

  1. Increase D30 and D60 retention for users who form a partnership vs. solo users
  2. Create a lightweight social commitment that reinforces daily habit usage without requiring significant time in-app

Success Metrics

MetricTargetMeasurement
Partnered user D30 retention2x solo rateCohort analysis, partnered vs. unpaired
Active users who pair within 60 days30%+Accepted partnerships / total active users
Mutual streak average length14+ daysMean mutual streak across all active partnerships
Partner-driven re-engagement15%+ of lapsed users return after partner notificationUsers who open app within 24h of missed-day alert
Session time deltaNo increase > 1 minPartnered users should not spend meaningfully more time in-app

Guardrails

  • One partner maximum at launch (MVP). Multiple partners creates social overhead. Expand later if data supports it.
  • No chat. This is not a messaging feature. Partners communicate through the act of showing up, not through conversation.
  • No public leaderboard between partners. The dynamic must be collaborative, not competitive.
  • Missed-day alerts max 1/day per partner. Never stack or nag.

3. User Stories

  1. As a user with existing allies, I want to invite one of my allies as my accountability partner, so I can deepen our commitment to shared progress.
  2. As a user receiving a partner invitation, I want to see who is inviting me and what the partnership entails before I accept, so I can make an informed decision.
  3. As a partnered user, I want to see my partner's daily check-in status on my dashboard, so I know at a glance whether they showed up today.
  4. As a partnered user, I want to hide specific goals from my partner, so I can maintain privacy on sensitive objectives while still being accountable on others.
  5. As a partnered user who missed a check-in day, I want my partner to receive a gentle alert, so they can encourage me to get back on track.
  6. As a partnered user, I want to earn bonus XP when my partner and I both check in on the same day, so we are rewarded for mutual consistency.
  7. As a user without allies, I want the app to suggest potential partners from my communities who have similar goal types, so I can find someone to pair with.
  8. As a partnered user, I want to see our mutual streak count, so I can feel the shared momentum building.
  9. As a partnered user, I want to end the partnership without drama or notification guilt, so the feature does not feel like a social trap.
  10. As a partnered user, I want Coach to reference my partner's activity in check-in prompts, so accountability feels integrated, not bolted on.

4. Feature Design

4a. Partnership Model (Hybrid Global + Goal Opt-Out)

When a user accepts a partnership, the partner gains visibility into all of their non-private, non-opted-out goals by default. This is the "global" part -- you do not need to select which goals are shared. You just pair up and go.

However, any goal can be individually hidden from the partner via a toggle on the goal detail page. This is the "opt-out" part. The default is visible; the user pulls specific goals out of partner visibility.

Key design decisions:

  • Partnership is a state on UserAlly, not a separate table. A user can only become your partner if they are already your accepted ally. Partnership is an upgrade of an existing relationship, represented by a boolean accountability_partner flag and associated metadata on the UserAlly record.
  • One active partner at a time (MVP). Enforced at the model level. A user can have many allies but only one where accountability_partner: true and status is accepted.
  • Bidirectional. When User A partners with User B, both the A-to-B and B-to-A UserAlly records are marked. The partnership is symmetric.
  • Inactive partner handling. If a partner has not signed in for 14 consecutive days, the system sends a single notification and auto-pauses the mutual streak counter. After 30 days, a follow-up suggests finding a new partner. Partnership is NOT auto-dissolved.

4b. Mutual Visibility

What partners CAN see:

  • Goal names and descriptions (for non-private, non-opted-out goals)
  • Check-in events and habit completion status for today (binary: done / not done)
  • Current streak count per goal
  • Completion rate (last 30 days) per habit goal
  • Whether they checked in today

What partners CANNOT see:

  • Private goals (private: true) -- invisible regardless of partnership
  • Goals the user has explicitly opted out of partner visibility
  • Goal event details/notes/media -- only the fact that a check-in occurred
  • Mood logs
  • XP totals, level, or badges (visible on profile already, not highlighted in partner context)

Goal-level opt-out: On the goal detail page, if the user has an active partner, a toggle appears: "Share with Partner" (default: on). Stored in a accountability_partner_goal_exclusions join table mapping user_ally_id and goal_id. Private goals never show the toggle -- they're hidden by default.

4c. Missed-Day Alerts

When they fire: A missed-day alert fires when a user with an active partnership has at least one habit goal visible to their partner, that habit was due today, and the user did NOT check in by end of day. Generated by a nightly Crono job.

Critical design: The alert goes to the PARTNER, not the person who missed. The person who missed already knows (existing reminder system handles that). The partner notification gives the partner agency to reach out.

Notification examples:

  • "[Partner name] hasn't checked in today. A quick encouragement could help them get back on track."
  • "[Partner name]'s streak on [Goal Name] is at risk. Want to send an encouragement?"

The notification includes an "Encourage" action button that deep-links to the partner's at-risk goal for the existing encouragement mechanism (ToggleGoalEventEncouragement).

What the feature does NOT do:

  • Does not send the missed-day alert TO the person who missed
  • Does not auto-send any message on behalf of either user
  • Does not fire more than once per partner per day
  • Does not fire on non-habit goals

4d. Bonus XP & Mutual Streaks

Mutual Streak: Counts consecutive days where BOTH partners checked in on at least one partner-visible habit goal. Breaks if either partner misses a full day. Tracked on the UserAlly record.

XP Bonuses:

EventXP AwardNotes
Both partners check in same day+15 XP each"Mutual Check-in Bonus" -- stacks with existing 25 XP from CheckInHabit
Mutual streak hits 7 days+100 XP eachOne-time per streak instance
Mutual streak hits 14 days+200 XP eachOne-time per streak instance
Mutual streak hits 30 days+500 XP eachOne-time per streak instance
Forming a partnership+50 XP eachOne-time per partnership

New Badges:

Badge KeyTriggerDisplay Name
accountability_partnerAccept first partnership"Stronger Together"
mutual_streak_77-day mutual streak"In Sync"
mutual_streak_3030-day mutual streak"Unstoppable Duo"
mutual_streak_100100-day mutual streak"Legendary Partners"

4e. Partner Discovery & Suggestions

Matching signals (ranked):

  1. Already an accepted ally (strongest signal)
  2. Shared community membership count
  3. Shared GoalCategory overlap
  4. Both have active daily habits
  5. Similar activity level (both active in last 7 days)

Where suggestions appear:

  • Partner setup screen ("Find a Partner")
  • AI Insight card on Communities or Dashboard page
  • Coach-driven suggestion during check-in prompts

5. UX Flow

5a. Inviting a Partner

  1. From ally list: each accepted ally card shows "Partner Up" icon
  2. Tap opens half-sheet modal: "Invite [Name] as your Accountability Partner?"
  3. Brief explanation + "Send Invite" CTA + "Not now" dismiss
  4. Ally receives push + in-app notification

From community: requires existing allyship first. From suggestion card: same invite modal flow.

5b. Accepting/Declining

  1. Notification tap navigates to partner request detail
  2. Shows: partner name, photo, shared communities, goal categories, current streak
  3. "Accept" (primary) / "Decline" (ghost button)
  4. On accept: both records updated, +50 XP each, confirmation notification
  5. On decline: requester gets NO notification (avoids social pressure)

5c. Partner Dashboard Card

Compact card (max 80px collapsed) on Dashboard between habits and unified feed:

  • Partner avatar (32px) + name
  • Mutual streak count with chain-link icon
  • Two status circles: one per user (filled = checked in, empty = not yet)
  • When both checked in: card border highlights with accent color, shows "+15 XP"
  • Tap to expand: partner-visible goals with per-goal check-in status
  • Expanded: "Encourage" quick-action if partner hasn't checked in

5d. Goal Opt-Out

Standard toggle in goal settings: "Visible to Partner" (default: ON). Subtext: "[Partner name] can see your check-ins for this goal." Toggle off removes immediately, no notification to partner. Does not appear on private goals.

5e. Ending a Partnership

  1. Partner Card overflow menu > "End Partnership"
  2. Confirmation: "Your individual streaks and XP are safe. Mutual streak resets. [Name] stays as your ally."
  3. Partner receives neutral notification. No XP lost. No badges revoked.

6. Data Model Changes

6a. Extensions to user_allies Table

ruby
add_column :user_allies, :accountability_partner, :boolean, default: false
add_column :user_allies, :accountability_partner_since, :datetime, null: true
add_column :user_allies, :mutual_streak_count, :integer, default: 0
add_column :user_allies, :mutual_streak_last_date, :date, null: true
add_column :user_allies, :longest_mutual_streak, :integer, default: 0
add_column :user_allies, :partner_ended_at, :datetime, null: true

add_index :user_allies, [:user_id, :accountability_partner],
          where: "accountability_partner = true AND status = 'accepted'",
          name: 'index_active_accountability_partner'

6b. New Table: accountability_partner_goal_exclusions

ruby
create_table :accountability_partner_goal_exclusions do |t|
  t.references :user_ally, foreign_key: true, null: false
  t.references :goal, foreign_key: true, null: false
  t.timestamps
  t.index [:user_ally_id, :goal_id], unique: true,
          name: 'idx_partner_goal_exclusions_unique'
end

6c. New Notification Kinds

Add to UserNotification::KIND_ENUMS:

ruby
partner_request: 10,
partner_accepted: 11,
partner_missed_day: 12,
partner_mutual_streak: 13,
partner_inactive: 14,
partner_ended: 15

6d. New Badge Entries

Add to UserAction action enum:

ruby
accountability_partner: 22,
mutual_streak_7: 23,
mutual_streak_30: 24,
mutual_streak_100: 25

6e. GraphQL Changes

New Type: AccountabilityPartnerType -- partner info, mutual streak, check-in status, visible goals

New Queries:

  • accountability_partner(user_id: ID!) -- returns partner or null
  • partner_suggestions(user_id: ID!, limit: Int) -- ranked list

New Mutations:

  • send_partner_request(ally_public_id: ID!)
  • accept_partner_request(ally_public_id: ID!)
  • decline_partner_request(ally_public_id: ID!)
  • end_partnership(ally_public_id: ID!)
  • toggle_goal_partner_visibility(goal_id: ID!, visible: Boolean!)

Prerequisite mutations (currently missing):

  • send_ally_request(user_public_id: ID!)
  • accept_ally_request(ally_public_id: ID!)
  • decline_ally_request(ally_public_id: ID!)
  • block_ally(ally_public_id: ID!)

7. Notification Design

TypePush ContentAction
Partner Request"[Name] wants to be your Accountability Partner. Tap to review."Accept / Decline
Partner Accepted"[Name] accepted! Your accountability journey starts now."Navigate to dashboard
Missed-Day Alert"[Name] hasn't checked in today. A quick encouragement could help."Encourage
Mutual Streak Milestone"7-day mutual streak with [Name]! +100 XP earned."Navigate to partner card
Partner Inactive (14d)"[Name] has been quiet for a while. Your mutual streak is paused."--
Partner Inactive (30d)"It's been a month since [Name] was active. Want to find a new partner?"Find New Partner
Partner Ended"Your partnership with [Name] has ended. Find a new partner anytime."--

Tone guidelines: Never say "failed" or "let down." Never imply obligation. Frame a partner's miss as a chance to help, not a failure to report. The word "accountability" is used sparingly in user-facing copy; prefer "partner" or "together."


8. How Partnership Affects Existing Features

Coach Behavior

Pass partner_checked_in_today and mutual_streak_count as context to AI prompt builder:

  • If partner checked in: "Your partner [Name] already showed up today -- your turn."
  • If mutual streak at risk: "Don't break the streak -- [Name] already checked in."
  • Coach NEVER shames, only motivates via social proof.

Unified Feed

Partner activity becomes a distinct partner_activity feed type with higher sort weight than regular ally_activity in Social::BuildUnifiedFeed.

Goal Detail Page

  • "Shared with [Partner Name]" indicator beneath goal title
  • Partner's corresponding goal in same category shown as linked card
  • "Visible to Partner" toggle in goal settings

Existing Notifications

Existing reminder and past_due types continue independently. The partner missed-day alert is an additional layer: reminders nudge the USER; partner alerts nudge the PARTNER.


9. Edge Cases & Open Questions

Edge Cases

#ScenarioResolution
EC-1Both users send partner requests simultaneouslyDetect cross-request and auto-accept
EC-2User has a partner and receives another requestRequest receivable but acceptance blocked: "You already have an active partner"
EC-3Partner blocks the userPartnership auto-dissolved, mutual streak zeroed, minimal notification
EC-4User hides all goals from partnerPartnership remains, partner sees nothing. Gentle prompt: "Share at least one goal"
EC-5User deletes accountSoft-delete applies, partnership dissolves, partner gets "ended" notification
EC-6Timezone mismatches for "same day"MVP uses UTC. Future: use device timezone
EC-7Habit due on specific days (weekly)Missed-day alerts respect due_today? logic
EC-8Goal completedExcluded from partner visibility and missed-day calculations

Open Questions

  1. Should mutual streak survive streak-freeze usage? Propose yes -- if one partner uses a freeze, mutual streak preserved.
  2. Partner messaging? Strong lean toward NO. Anti-social app. Users have external channels.
  3. Right cadence for missed-day alerts? MVP: once per day via nightly job. Future: partner-configurable timing.
  4. Multiple partners (future)? Test whether multiple partners multiply or dilute the effect before expanding.
  5. Should partner activity count toward daily engagement? Yes -- viewing partner card satisfies "daily touch."

10. Implementation Phases

Phase 1: MVP -- Core Partnership (2-3 weeks)

Prerequisite: Build missing ally request/accept/decline/block mutations and interactions.

Core:

  • Migration: add accountability partner fields to user_allies, create accountability_partner_goal_exclusions
  • Model: UserAlly scopes, User#accountability_partner method
  • Interactions: Social::SendPartnerRequest, AcceptPartnerRequest, DeclinePartnerRequest, EndPartnership
  • GraphQL: partner mutations and accountability_partner query
  • Notifications: partner_request, partner_accepted, partner_ended
  • Frontend: Partner invite modal, accept/decline screen, Partner Card on Dashboard
  • XP: +50 XP for forming partnership

Phase 2: Visibility & Alerts (1-2 weeks)

  • Goal opt-out toggle on Goal Detail
  • toggle_goal_partner_visibility mutation
  • Partner visibility logic in feed and goal queries
  • Social::GeneratePartnerMissedDayAlertsJob (nightly Crono job)
  • partner_missed_day notification with "Encourage" action
  • "Shared with Partner" indicator on goal detail

Phase 3: Mutual Streaks & Gamification (1-2 weeks)

  • Mutual streak calculation inline in GoalTracking::CheckInHabit
  • Nightly reconciliation job
  • XP bonuses via GamificationService.award_xp
  • New badges: accountability_partner, mutual_streak_7, mutual_streak_30, mutual_streak_100
  • Frontend: mutual streak display, milestone celebrations
  • Coach integration: partner context in AI prompts

Phase 4: Discovery & Suggestions (1 week)

  • partner_suggestions query with matching algorithm
  • AI Insight card for partner suggestions
  • "Partner Up" button on ally list
  • Coach-driven suggestion during check-ins

Phase 5: Polish & Edge Cases (1 week)

  • Inactive partner detection job (14-day and 30-day thresholds)
  • partner_inactive notifications
  • Mutual streak pause logic
  • Streak freeze compatibility
  • Edge case handling
  • partner_activity feed type with priority sorting

11. Design Suggestions

Partner Card (Dashboard)

Compact (max 80px collapsed), visually distinct from other dashboard cards:

  • Layout: Horizontal. Partner avatar (32px) left, mutual streak (large number + chain-link icon) center, two status circles right
  • Color: Warm amber/gold accent for mutual streak. Both-checked-in state: card border glows with accent color (300ms animation)
  • Expanded: Partner-visible goals as compact list, "Encourage" quick-action

Mutual Streak Visual

Chain-link metaphor (two interlocking rings), distinct from the individual streak fire icon:

  • 1-6 days: single chain link, default color
  • 7-13 days: double chain link, bronze tint
  • 14-29 days: triple chain link, silver tint
  • 30+ days: full chain, gold tint with subtle shimmer

Partner Badges

  • "Stronger Together" -- two overlapping shields or clasped hands
  • "In Sync" (7-day) -- synchronized chain links
  • "Unstoppable Duo" (30-day) -- golden interlocking chains
  • "Legendary Partners" (100-day) -- platinum chain with glow

Partner Invite Modal

Half-sheet modal (consistent with existing CreateCommunityModal pattern):

  • Partner avatar + name at top
  • One-line partnership description
  • Shared communities and goal categories as chips
  • "Send Invite" primary CTA + "Not now" dismiss

Critical Files

FileChange
rails_api/app/models/user_ally.rbAdd accountability partner scopes and fields
rails_api/app/models/user.rbAdd accountability_partner convenience method
rails_api/app/models/user_notification.rbAdd partner notification kinds
rails_api/app/graphql/types/mutation_type.rbRegister partner + ally mutations
rails_api/app/interactions/social/New partner interactions + prerequisite ally interactions
rails_api/app/interactions/social/build_unified_feed.rbAdd partner_activity feed type
rails_api/app/services/ai/coach_service.rbAccept partner context in prompts
rails_api/config/cronotab.rbSchedule missed-day alert job
ionic_frontend/src/views/Dashboard.vueAdd Partner Card component
ionic_frontend/src/views/Goal.vueAdd partner visibility toggle and indicator
ionic_frontend/src/composables/useCommunity.tsAdd partner query hooks and mutations
ionic_frontend/src/constants/graphql/New partner queries and mutations

Last updated: 2026-07-15

Loading…