peregrine/docs/reference/tier-system.md
pyr0ball 41c7954b9d docs: mkdocs wiki — installation, user guide, developer guide, reference
Adds a full MkDocs documentation site under docs/ with Material theme.

Getting Started: installation walkthrough, 7-step first-run wizard guide,
Docker Compose profile reference with GPU memory guidance and preflight.py
description.

User Guide: job discovery (search profiles, custom boards, enrichment),
job review (sorting, match scores, batch actions), apply workspace (cover
letter gen, PDF export, mark applied), interviews (kanban stages, company
research auto-trigger, survey assistant), email sync (IMAP, Gmail App
Password, classification labels, stage auto-updates), integrations (all 13
drivers with tier requirements), settings (every tab documented).

Developer Guide: contributing (dev env setup, code style, branch naming, PR
checklist), architecture (ASCII layer diagram, design decisions), adding
scrapers (full scrape() interface, registration, search profile config,
test patterns), adding integrations (IntegrationBase full interface, auto-
discovery, tier gating, test patterns), testing (patterns, fixtures, what
not to test).

Reference: tier system (full FEATURES table, can_use/tier_label API, dev
override, adding gates), LLM router (backend types, complete() signature,
fallback chains, vision routing, __auto__ resolution, adding backends),
config files (every file with field-level docs and gitignore status).

Also adds CONTRIBUTING.md at repo root pointing to the docs site.
2026-02-25 12:05:49 -08:00

5.2 KiB

Tier System

Peregrine uses a three-tier feature gate system defined in app/wizard/tiers.py.


Tiers

free < paid < premium
Tier Description
free Core discovery pipeline, resume matching, and basic UI — no LLM features
paid All AI features: cover letters, research, email, integrations, calendar, notifications
premium Adds fine-tuning and multi-user support

Feature Gate Table

Features listed here require a minimum tier. Features not in this table are available to all tiers (free by default).

Wizard LLM generation

Feature key Minimum tier Description
llm_career_summary paid LLM-assisted career summary generation in the wizard
llm_expand_bullets paid LLM expansion of resume bullet points
llm_suggest_skills paid LLM skill suggestions from resume content
llm_voice_guidelines premium LLM writing voice and tone guidelines
llm_job_titles paid LLM-suggested job title variations for search
llm_keywords_blocklist paid LLM-suggested blocklist keywords
llm_mission_notes paid LLM-generated mission alignment notes

App features

Feature key Minimum tier Description
company_research paid Auto-generated company research briefs pre-interview
interview_prep paid Live reference sheet and practice Q&A during calls
email_classifier paid IMAP email sync with LLM classification
survey_assistant paid Culture-fit survey Q&A helper (text + screenshot)
model_fine_tuning premium Cover letter model fine-tuning on personal writing
shared_cover_writer_model paid Access to shared fine-tuned cover letter model
multi_user premium Multiple user profiles on one instance

Integrations (paid)

Feature key Minimum tier Description
notion_sync paid Sync jobs to Notion database
google_sheets_sync paid Sync jobs to Google Sheets
airtable_sync paid Sync jobs to Airtable
google_calendar_sync paid Create interview events in Google Calendar
apple_calendar_sync paid Create interview events in Apple Calendar (CalDAV)
slack_notifications paid Pipeline event notifications via Slack

Free integrations (not gated)

The following integrations are free for all tiers and are not in the FEATURES dict:

  • google_drive_sync — upload documents to Google Drive
  • dropbox_sync — upload documents to Dropbox
  • onedrive_sync — upload documents to OneDrive
  • mega_sync — upload documents to MEGA
  • nextcloud_sync — upload documents to Nextcloud
  • discord_notifications — pipeline notifications via Discord webhook
  • home_assistant — pipeline events to Home Assistant REST API

API Reference

can_use(tier, feature) -> bool

Returns True if the given tier has access to the feature.

from app.wizard.tiers import can_use

can_use("free", "company_research")   # False
can_use("paid", "company_research")   # True
can_use("premium", "company_research") # True

can_use("free", "unknown_feature")    # True — ungated features return True
can_use("invalid", "company_research") # False — invalid tier string

tier_label(feature) -> str

Returns a display badge string for locked features, or "" if the feature is free or unknown.

from app.wizard.tiers import tier_label

tier_label("company_research")  # "🔒 Paid"
tier_label("model_fine_tuning") # "⭐ Premium"
tier_label("job_discovery")     # ""  (ungated)

Dev Tier Override

For local development and testing without a paid licence, set dev_tier_override in config/user.yaml:

tier: free
dev_tier_override: premium    # overrides tier locally for testing

UserProfile.tier returns dev_tier_override when set, falling back to tier otherwise.

!!! warning dev_tier_override is for local development only. It has no effect on production deployments that validate licences server-side.


Adding a New Feature Gate

  1. Add the feature to FEATURES in app/wizard/tiers.py:
FEATURES: dict[str, str] = {
    # ...existing entries...
    "my_new_feature": "paid",   # or "free" | "premium"
}
  1. Guard the feature in the UI:
from app.wizard.tiers import can_use, tier_label
from scripts.user_profile import UserProfile

user = UserProfile()
if can_use(user.tier, "my_new_feature"):
    # show the feature
    pass
else:
    st.info(f"My New Feature requires a {tier_label('my_new_feature').replace('🔒 ', '').replace('⭐ ', '')} plan.")
  1. Add a test in tests/test_tiers.py:
def test_my_new_feature_requires_paid():
    assert can_use("free", "my_new_feature") is False
    assert can_use("paid", "my_new_feature") is True
    assert can_use("premium", "my_new_feature") is True

Future: Ultra Tier

An ultra tier is reserved for future use (e.g. enterprise SLA, dedicated inference). The tier ordering in TIERS = ["free", "paid", "premium"] can be extended without breaking can_use(), since it uses list.index() for comparison.