Mentorship Programme Operations Guide#

This handbook is the operational reference for the people who run a mentorship programme: typically platform admins or staff with the mentorship.match capability. It covers programme setup, mentor onboarding, the match lifecycle, quality signals from the dashboard, and escalation paths when things go wrong.

Who this is for: anyone with the platform admin role (and by extension mentorship.match). The mentor and mentee guides are at documentation/role-guides/alumni/mentorship-as-mentor.md and documentation/role-guides/student/mentorship-as-mentee.md.

Source of truth: backend/internal/mentorship/, webapp/src/routes/admin/AdminMentorshipPage.tsx and the AdminProgramme* admin pages. RPC contract: protos/bits/mentorship/v1/mentorship.proto.


1. Lifecycle of a programme#

A programme is a state machine (backend/internal/mentorship/types.go:38):

draft  → open       (start accepting mentor + mentee applications)
open   → matching   (admin closes applications and runs matching)
matching → active   (admin confirms the cohort; matches in flight)
active → completed  (cohort closed)

State transitions are admin-only and explicit — they do not fire on a clock. RunMatching only succeeds when the programme is in matching state (service.go:472).

2. Creating a programme#

/admin/mentorship/programmes/new. Required fields:

  • Name (≤ 200 chars).
  • Description (≤ 4 000 chars).
  • Audience scope — JSON shape, e.g. {"batch_year_min": 2018, "departments": ["CS", "EEE"]}. Used by the matcher’s audience_fit axis. Leave empty for open programmes.
  • Starts at / ends at — informational; the matcher does not enforce.
  • Mentor capacity default — applied to mentors who don’t override their own max_mentees.

Programme is created in draft. Move it to open when you’re ready to accept applications.

3. Mentor onboarding#

By default a mentor application is created in applied state. To approve a mentor for matching, an admin flips the state to approved via the admin grid. Only approved mentors are eligible for the matcher (service.goListEligibleMentorsAndMentees).

The vetting workload is the admin’s; the system does not auto-approve. A typical workflow:

  1. Mentor applies via /app/mentorship/programmes/<id>/apply-mentor.
  2. Admin reviews the bio, expertise tags, availability on the admin programme page.
  3. Admin approves (state → approved) or pauses (state → paused) — paused mentors do not match.

Mentors can withdraw at any time (state → withdrawn).

4. Mentee applications#

Mentee applications are created in applied and stay there until matching produces a Match row, at which point the mentee state flips to matched. There is no separate vetting step for mentees in V2 — eligibility is enforced by the programme’s audience_scope at apply time.

5. Running matching#

When the programme is in matching:

  1. Click Run matching on /admin/mentorship/programmes/<id>.
  2. The service enqueues a mentorship_matching job onto the runner queue.
  3. The handler (backend/internal/mentorship/job_handler.go:49) loads eligible mentors + mentees, runs the matcher, and bulk-inserts Match rows in proposed state.
  4. Each match fires a mentorship.match.proposed notification per side.
  5. Audit row written.

The matcher is deterministic and pure — re-running with the same inputs produces the same output. Score formula: 2*skills_overlap + 1*audience_fit + 0.5*capacity_remaining. Tie-breaking is (mentor_id ASC, mentee_id ASC).

If there are no eligible mentors, the synchronous helper returns ErrNoEligibleMentors; via the job queue path, the handler logs the warning and exits successfully (idempotent — re-running once you have eligible mentors will produce matches).

Re-running matching#

You can re-run matching while the programme is still in matching state. Existing proposed matches that have not been accepted are not deleted; the matcher inserts new candidates with ON CONFLICT DO NOTHING on the (programme_id, mentor_user_id, mentee_user_id) natural key, so re-runs are additive, not destructive. Already-accepted matches are untouched.

6. Match SLAs and quality signals#

The admin dashboard (/admin/mentorship/programmes/<id> and GetAdminDashboard RPC) surfaces:

  • Match counts — proposed / accepted_by_mentor / accepted / declined / terminated, broken down by programme.
  • Session completion rate — sessions in completed state divided by total scheduled.
  • Pending-too-long alerts — matches that have been in proposed state for more than 7 days (backend/internal/mentorship/service.go:984). The query returns up to 50, ordered oldest-first.

7 days is the implicit SLA. There is no automatic escalation — the alerts are a dashboard signal for admins to follow up manually.

Quality signals to watch#

  • High decline rate from mentees — usually means audience scoping is too loose, or focus tags don’t reflect what mentees actually want. Tighten the programme audience or add more mentors with broader expertise.
  • High no-show rate on sessions — surface in the session-state breakdown. Often correlates with mismatched modality (e.g. mentor preferring video, mentee defaulting to chat). Encourage explicit modality selection at scheduling.
  • Low session-per-match count — matches that activated but never scheduled. Reach out to both sides; sometimes the mentor accepted but never followed up.

7. Escalation paths#

  • Abuse / inappropriate behaviour by a mentor or mentee — terminate the match (admin can call TerminateMatch regardless of role), then suspend the offender’s account via /admin/users. Both parties get a notification.
  • A match is stuck — mentor accepted, mentee non-responsive for >2 weeks — terminate the match with terminated_reason = "mentee_non_responsive". Re-add the mentor’s capacity by re-running matching once the next mentee is approved.
  • Programme audience needs to change mid-cohortUpdateProgramme accepts a new audience_scope_json. New applications honour it immediately; existing matches are not retroactively invalidated.

8. Reminders and session lifecycle#

Session reminders are written by the cron in backend/internal/mentorship/reminders.go:

  • 24 hours before scheduled_atmentorship.session.reminder_24h.
  • 1 hour before scheduled_atmentorship.session.reminder_1h.

Sessions transition through scheduled → completed | cancelled | no_show. Mentors and admins can update; mentees can read but not write.

9. Notes visibility#

Progress notes (mentorship_progress_note) carry a notes_visible_to tag scoped on the parent session: both | mentor | admin. The default depends on what the mentor selected when scheduling. Notes scoped to admin only ever appear in the admin dashboard, never to the mentee.

10. CSV export#

The admin grid offers CSV export of match + session rows. Use it for offline reporting; the live dashboard is the single source of truth for in-platform numbers.

11. Performance budget#

Per the Stage 23 §8.3 perf gate:

  • Matching job for 1k mentors × 5k mentees completes in under 60 s.
  • Admin dashboard p95 < 300 ms.

If you see substantially longer, file a perf ticket — the matcher itself is O(M*N) in the inner loop (no spatial indexing) and is sized for those numbers. Linear blowup beyond the budget likely indicates a regression.

12. Common operational issues#

  • “Run matching button is greyed out” — programme must be in matching state. Move it from open first.
  • “Job ID returned but no matches inserted” — check the structured logs for the matching job; common causes are zero approved mentors or all mentees still in non-applied state. The handler is idempotent — re-enqueue once preconditions are met.
  • “Pending-too-long list never empties” — those matches require either side to accept/decline. Reach out to both parties; if neither responds, terminate the match manually.
  • “Mentee can’t see my note” — note visibility is per-session, not per-match. Re-issue the note with notes_visible_to = both.