onboarding · diff
git:20261001.74cc868 to git:20261001.280c16b
2 added, 0 removed. Audit B to C.
---
name: onboarding
description: "Set up your personalized career-manager pipeline. Reads your resume, asks 1 question, then generates all config files so the pipeline is ready to run. Use when first setting up the pipeline or when your career goals change."
---
# Onboarding
Personalize the career-manager pipeline for a new user through a focused setup.
## When to Use
- First time setting up the pipeline after cloning
- Career goals, target roles, or compensation targets have changed
- Want to regenerate search queries or scoring criteria
## Flow
### Step 0: Ensure Local Python Environment
+ If `uv` is not installed, install it first (official installer: macOS/Linux `curl -LsSf https://astral.sh/uv/install.sh | sh`, Windows `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"`), then make sure it is on PATH for subsequent commands. A system Python is not required — `uv` downloads a managed Python automatically when none is available.
+
Before reading the resume or generating files, create a repo-local virtual environment if one doesn't already exist:
```bash
uv venv
uv pip install -r requirements.txt
```
Run all subsequent Python commands through `uv run` (e.g., `uv run python3 scripts/smoke_test.py`) so dependencies resolve against the repo-local `.venv` and never install into a global Python, Conda, or system environment.
### Step 1: Get the Resume
Ask: "Where's your resume? Give me the file path (e.g., ~/Desktop/resume.docx)"
When the user provides a path:
1. Verify the file exists and is `.docx` or `.pdf`
2. Copy it to `cv-tailor/data/CV/Master CV/` (create directory if needed)
3. Read the resume and extract: work history, skills, industries, seniority, education, accomplishments
If the file doesn't exist or isn't a supported format, ask again with guidance.
**Do NOT ask the user to copy files manually.** You handle the copy.
### Step 2: One Question
Ask this single combined question:
"Based on your resume, I can see your background. I need four things your resume doesn't tell me:
1. **Target roles** - What roles are you going after? Be specific about seniority (Senior PM, Director of Engineering, Solutions Architect) and domain.
2. **Salary floor** - What's the minimum base salary you'd accept?
3. **Location** - Remote, hybrid, or onsite? Open to relocation?
4. **Job markets** - Which countries should I search for jobs in? (Default: United States)"
### Step 3: Confirm Profile
Present the full profile combining resume inference + user answers:
"Here's your profile:
**Background:** [inferred from resume - key roles, industries, years of experience, strongest skills]
**Target roles:** [from answer]
**Salary floor:** [from answer]
**Location:** [from answer]
**Career paths I'd search:**
Derive paths in two phases:
1. **From resume:** For each sector or employer type the user spent 2+ years in, derive a career path. Group similar employers (e.g., multiple Big 4 firms = one "Professional Services / Advisory" path, not four). If no employer meets the 2-year threshold, use the user's longest-tenured roles regardless.
2. **From targets:** Add paths from the user's stated target roles that aren't already covered by Phase 1.
Merge only paths that are truly redundant (same companies, same roles). Prefer granular paths over merged ones. When in doubt, keep them separate. List Phase 1 paths first (longest tenure first), then Phase 2 paths. Final count: 4-5 paths. Every path adds agents, tokens, and wall-clock time to each pipeline run, so default to the smallest set that covers the user's strongest angles. If more than 5 strong candidates emerge, keep the 4-5 strongest now and save the rest.
When presenting the paths, tell the user explicitly: "I'm starting with 4-5 focused paths so each search run stays fast. You can add more paths later by re-running onboarding whenever your goals expand."
Anything you'd change?"
Wait for confirmation. If the user tweaks anything, update and re-confirm.
### Step 4: Generate Files
After confirmation, generate all files **silently**. No overwrite warnings. No "about to write..." messages. Just write them.
**IMPORTANT:** Before writing each file, read the existing file first (Write tool requirement). Do this silently. Do NOT tell the user you're reading before writing. Do NOT warn about overwriting.
**Never touch pipeline data files** (target-companies.csv, applications.csv, etc.).
#### File 1: `config.yaml`
Generate from `config.yaml.example` template with:
- `paths.cv_base` set to the directory where the user's resume was found (parent of the file they provided)
- Credential-based integrations (`todoist_enabled`, `gmail_enabled`, `tavily_enabled`) set to `false`; keep `jobspy_enabled: true` so the first discovery run can scrape job boards
- Email fields left as placeholders
#### File 2: `job-search/references/criteria.md`
Generate with this structure. The rubric must be CALIBRATED (tight, evidence-based) rather than loose; a well-calibrated rubric produces a spread of scores, not a cluster at the top. The average company the user's agents encounter should score around 50, not 70.
```
## Unique Value Proposition
[2-3 paragraphs: what makes this person uniquely valuable]
## Target Roles
[Role categories with descriptions]
## Target Company Types
[4-5 career paths. Each path has:]
### Path N: [Path Name]
- **Description:** [What kind of companies]
- **Example Companies:** [2-3 well-known examples for context, not monitored]
- **Role Types:** [Specific roles that fit]
- **Why You Fit:** [Connection to background]
- **Compensation Notes:** [Range based on salary floor]
## Evaluation Framework
10 scoring dimensions, each assessed yes (fits), no (does not fit), or unknown (cannot determine). Unknown dimensions are null, never 0, and are excluded from the score. Total score = (yes count / evaluated count) * 100, rounded down. Canonical implementation: `job-search/scripts/core/scoring.py`.
### Group 1: Core Fit (4 dimensions)
### Group 2: Compensation & Career (3 dimensions)
### Group 3: Culture & Growth (3 dimensions)
For EACH dimension you generate, produce:
- **Name** and 1-line purpose.
- **Yes condition.** A concrete observable test, not an adjective. "Yes = public Levels.fyi data showing $200K+ TC at this level" is good. "Yes = pays well" is bad.
- **No condition.** The concrete observable that rules the dimension out.
- **Evidence required for yes.** Named source (job posting, employee review, funding announcement, public statement, etc.). If evidence is unavailable, the dimension is unknown (null), never a yes by default.
Emphasize in the rubric itself: "If you cannot find specific evidence, mark the dimension unknown rather than assuming the best case. A company without disclosed comp is unknown (null), not a yes, and not a 0."
## Scoring Guide
Thresholds align with `pipeline.action_list.*_min_score` in `config.yaml`:
- **85-100 (HIGH):** Pursue aggressively. Cold outreach or immediate apply.
- **70-84 (MED):** Strong fit with current evidence. Apply if role is open.
- **60-69 (LOW):** Moderate fit. Watch list or skip unless specific role matches.
- **<60:** Skip.
**Handling unknowns:** Unknown dimensions are null, never 0. They are excluded from the ratio, so missing information never penalizes (or inflates) a company. If fewer than 5 of 10 dimensions are assessable, set `llm_flags: "needs_research"` and omit the score. The keyword scorer is a fallback only, used for rows with no `llm_score`; it never overrides one.
**Distributional check:** When this rubric is applied to the user's existing target list, expect roughly:
- ~20% score ≥85 (genuine A-tier)
- ~30% score 70-84 (B-tier, apply-worthy)
- ~35% score 60-69 (C-tier, watch)
- ~15% score <60 (skip)
If the distribution is heavily skewed high, the rubric is too loose. Tighten the yes conditions and evidence requirements.
```
#### File 3: `job-search/references/background-context.md`
Generate with this structure:
```
## Personal Information
## Technical Capabilities
## Project Portfolio
## Professional Experience
## Education & Certifications
## Positioning Angles
## Travel & Location Preferences
```
#### File 4: `job-search/data/search-config.json`
The repo ships this file as a placeholder stub with `"setup_required": true` (a full neutral template lives at `job-search/data/search-config.json.example`). Overwrite the entire stub with the generated config and do not carry over the `setup_required` flag (or set it to `false`); the pipeline refuses to run while it is `true`.
Generate valid JSON matching this exact structure. **Pay close attention to types. `query_packs` and `path_check_instructions` are dicts, not lists.**
```json
{
"search_locations": ["United States"],
"query_packs": {
"<snake_case_path_key>": {
"label": "Human-Readable Path Name",
"queries": ["search query 1", "search query 2", "..."],
"locations": ["Remote", "United States"],
"job_type": "fulltime"
}
},
"role_include_patterns": ["regex_pattern", "..."],
"role_exclude_patterns": ["regex_pattern", "..."],
"employer_exclude_patterns": ["regex_pattern", "..."],
"location_exclude_patterns": ["regex_pattern", "..."],
"keywords": {
"domain": ["keyword", "..."],
"ai": ["keyword", "..."],
"tech": ["keyword", "..."]
},
"path_check_instructions": {
"1": "Multi-sentence discovery strategy for path 1...",
"2": "Multi-sentence discovery strategy for path 2..."
},
"role_patterns": ["human-readable role title", "..."],
"scoring": {
"domain_keywords": {"keyword": 8},
"ai_keywords": {"keyword": 6},
"role_keywords": {"keyword": 7},
"comp_indicators": {"keyword": 5},
"growth_indicators": {"keyword": 4}
},
"path_aliases": {
"fuzzy variant": "Exact Query Pack Label",
"another variant": "Exact Query Pack Label"
},
"display_groups": {
"Group Name": ["Query Pack Label 1", "Query Pack Label 2"],
"Another Group": ["Query Pack Label 3"]
}
}
```
**Critical rules:**
- `query_packs` is a **dict keyed by snake_case name**, NOT a list. Each value has `label`, `queries`, `locations`, `job_type`
- `path_check_instructions` is a **dict keyed by path number as string** ("1", "2", etc.), NOT a single string
- One query pack per career path (for JobSpy)
- All regex patterns must be valid Python regex, no inline flags like `(?i)`
- 4-5 paths (one pack each), 3+ queries per pack. Users add more paths later by re-running onboarding
- `display_groups` groups related query pack labels into 3-4 dashboard display categories. Each label must match a query pack's `label` field exactly. Paths not mapped go to "Other"
**Search locations:**
- `search_locations` is the user's answer to "Which countries should I search for jobs in?"
- Default: `["United States"]` if the user doesn't specify
- If user says "Europe", expand to specific countries: Ireland, United Kingdom, Germany, Netherlands, France
- Populate each query pack's `locations` field from `search_locations` (plus "Remote")
- Auto-generate `location_exclude_patterns` to exclude countries NOT in `search_locations`. Do not add exclude patterns for countries the user wants to search in.
**Scoring keywords:** Each scoring dict maps keyword strings to integer weights. Higher weights = stronger signal for that category. Generate weights based on how central each term is to the user's target roles.
**Path aliases:** Map fuzzy LLM path variants to canonical query_pack labels. The LLM sometimes returns shortened or rephrased path names during evaluation. This map catches common variants so they normalize correctly. Generate 3-5 aliases per career path based on likely LLM rephrasings. Keys are lowercase variants, values are exact query_pack labels.
**Path check instructions:** Generate 3-4 sentence discovery strategies per path. Each instruction must include:
- What signals indicate a good fit for this path
- What to look for on careers pages
- What disqualifies a company
- Location requirements (reference `search_locations`)
See `onboarding/references/example-output.md` for a complete working example.
### Step 5: Verify Setup
Run smoke test checks **inline** (do not ask the user to run a separate command):
1. Verify all 4 generated files exist and are non-empty
2. Run `uv run python3 scripts/smoke_test.py` and verify all checks pass (this validates search-config schema, query_packs is a dict, path_check_instructions is a dict, dependencies, and config.yaml)
If all checks pass: "Setup complete."
If any check fails: report the specific issue and fix it.
### Step 6: Start First Search
Automatically hand off to the job-search skill and run the full pipeline. Do not ask the user for confirmation; they just completed setup, so the next step is always discovery.
## Re-run Behavior
If the user has already run onboarding (files exist):
- Ask: "You already have a profile set up. What's changed?"
- Only regenerate files that need updating
- This is also how users add career paths beyond the initial 4-5: extend `query_packs`, `path_check_instructions`, `path_aliases`, and `display_groups` in search-config.json and add matching path sections to criteria.md
- Never touch pipeline data (target-companies.csv, applications.csv)
## Boundaries
- This skill generates config files only. It does not run the pipeline.
- It does not modify pipeline scripts.
- It does not create or modify the Master CV content. It only copies the user's file.