onboarding · diff

git:20260412.506d714 to git:20260416.dba95ac

26 added, 7 removed. Audit B to B.

---
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
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 overlapping paths. List Phase 1 paths first (longest tenure first), then Phase 2 paths. Final count: 3-8 paths.
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)
- All integrations set to `false`
- Email fields left as placeholders
#### File 2: `job-search/references/criteria.md`
- Generate with this structure:
+ 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
[3-8 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 personalized yes/no scoring dimensions]
+ 10 scoring dimensions, each scored 0-10. Total score = sum (0-100).
+
### 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.
+ - **Score anchors** for 0, 3, 5, 7, 9 — each anchor is a concrete observable example, not an adjective. "Score 9 = public Levels.fyi data showing $200K+ TC at this level" is good. "Score 9 = very high comp" is bad.
+ - **Evidence required for 7+.** Named source (job posting, employee review, funding announcement, public statement, etc.). If evidence is unavailable, score 5 or below — not 7+ by default.
+ - **Default when unknown:** 5 (middle-of-band neutral), not 7.
+
+ Emphasize in the rubric itself: "If you cannot find specific evidence, default to 5 rather than assuming the best case. A company without disclosed comp is a 5, not a 9."
+
## Scoring Guide
- - 9-10 yes = Pursue aggressively
- - 7-8 yes = Strong fit, apply and investigate
- - 5-6 yes = Moderate fit, consider or watchlist
- - <4 yes = Skip
- **Handling unknowns:** Mark as unknown rather than no. Score = (yes count / evaluated count) * 100. If fewer than 5 dimensions evaluable, flag as "needs_research".
+ 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:** If fewer than 5 of 10 dimensions are assessable, set `llm_flags: "needs_research"` and omit the score. Otherwise, unknown dimensions default to 5, not the top of the scale.
+
+ **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 anchors.
```
#### 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`
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)`
- Minimum: 3+ paths, 3+ queries per pack
- `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
- 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.