| name | career-ops-workflow |
| description | Career Ops module workflow enforcing 3-phase Gmail audit/classify/execute pipeline, dry-run safety gates, Marcus check-in protocol, and label taxonomy standards. |
| user-invocable | false |
Career Ops Workflow
Phase-gated workflow for Bob's Career Ops module: Gmail inbox audit, HuggingFace classification, and approved execution with dry-run safety gates at every boundary.
Description
Defines the non-negotiable workflow for the Career Ops module integrated into Bob-AICompanion. Enforces the 3-phase pipeline (Audit -> Classify -> Execute), dry-run safety gates, Marcus check-in protocol, label taxonomy, and data layer contracts. Every session working on Career Ops must follow these rules.
Prerequisites
- Python 3.11+
- FastAPI + Pydantic
- MongoDB Atlas (GitHub Student Pack free tier)
- Gmail API OAuth2 credentials (read-only scope for Phase 1-2)
- HuggingFace transformers (Phase 2+)
Usage
These rules are auto-loaded context for any Career Ops development session. Follow the phase-gated workflow and never skip safety gates.
Prompt Pattern
I'm working on Phase [N] of the Career Ops module.
Current branch: phase/[N]-[name]
Follow the career-ops-workflow skill:
1. Check phase safety gates
2. Verify label taxonomy compliance
3. Ensure dry-run report before any execution
3-Phase Pipeline
Phase 1 -- Audit (Read-Only)
Branch: phase/1-audit
Safety Gate: ZERO Gmail write operations. Read-only OAuth scope enforced.
| Feature | Description |
|---|
| Gmail OAuth2 | Read-only scope, OAuth2 flow runner in scripts/ |
| Inbox Scanner | Count emails by sender domain |
| Category Detector | Identify top 10 senders by volume |
| Unsubscribe Finder | Flag marketing emails with unsubscribe links |
| Terminal Report | Rich library colored table output |
| MongoDB Snapshot | Save audit data before any action |
Definition of Done:
- Gmail OAuth2 connects with read-only scope
- Terminal report shows email categories and counts
- Audit snapshot saved to MongoDB
- Zero Gmail write operations executed
- All tests passing
Phase 2 -- Classify
Branch: phase/2-classify
Safety Gate: Classification output goes to MongoDB only. No Gmail calls.
| Feature | Description |
|---|
| HuggingFace Pipeline | foduucom/contact-form-spam-detection model |
| Batch Classification | Classify all audited emails |
| Recruiter Scoring | Company domain, personalization, role specificity |
| Salary Extraction | Compensation data from email content |
| Dry-Run Report | Proposed labels shown, no execution |
| Portfolio Tracker | Portfolio link click detection |
Definition of Done:
- HuggingFace classifier labels all audited emails
- Recruiter scoring produces ranked list
- Dry-run report shows proposed labels without execution
- MongoDB stores classification results
Phase 3 -- Execute
Branch: phase/3-execute
Safety Gate: Every action requires dry-run report reviewed by Marcus first.
| Feature | Description |
|---|
| Gmail MCP Integration | Label, archive, delete via MCP server |
| Label Application | Apply approved labels from Phase 2 |
| Application Tracker | Log applied jobs, detect reply status |
| Follow-Up Reminders | Flag no-response after configurable days |
| Weekly Digest | Summary builder + delivery |
| Azure Deployment | Cloud Function via Student Pack credits |
Definition of Done:
- Marcus approves dry-run report
- Labels applied to inbox
- Application tracker logging jobs
- Weekly digest running
- Azure deployment live
Label Taxonomy (Single Source of Truth)
All Gmail label constants live in career_ops/utils/label_taxonomy.py -- nowhere else. Never hardcode label strings in service or API code.
LABELS = {
"RECRUITER_DIRECT": "Job/Recruiter-Direct",
"APPLICATION_CONFIRM": "Job/Application-Confirm",
"INTERVIEW": "Job/Interview",
"AUTOMATED_BLAST": "Job/Automated-Blast",
"REJECTED": "Job/Rejected",
"KEEP_PROFESSIONAL": "Keep/Professional",
"KEEP_FINANCIAL": "Keep/Financial",
"ARCHIVE_SCHOOL": "Archive/School",
"ARCHIVE_MARKETING": "Archive/Marketing",
"DELETE_SPAM": "Delete/Spam-Confirmed",
}
Rules:
- Import from
label_taxonomy.py only -- never duplicate strings
- New labels require an ADR in
tasks/adrs/
- Label changes require Marcus approval
Dry-Run Safety Gate
The dry-run report is the safety mechanism that prevents unreviewed actions on Marcus's inbox.
Phase 1: Terminal report (Rich table) -- shows what was found
Phase 2: Classification report -- shows proposed labels, no execution
Phase 3: Execution plan -- shows exact actions, waits for Marcus "APPROVED"
Rules:
- Every phase produces a dry-run report before completion
- No Gmail mutations without Marcus reviewing the report first
- Reports saved to MongoDB for audit trail
- Reports must include: action, target email, confidence score, reasoning
Layer Architecture
Follow strict layer separation. No shortcuts.
API Layer (career_ops/api/)
- FastAPI route handlers
- Transport layer ONLY -- no business logic
- Validates input with Pydantic models
- Delegates to service layer
Service Layer (career_ops/services/)
- Business logic lives here
- No HTTP concerns (no Request/Response objects)
- No direct DB calls -- uses repository layer
- Dependency injection for all external dependencies
Repository Layer (career_ops/repositories/)
- Data access ONLY (MongoDB via Motor)
- No business logic
- Returns Pydantic models, not raw dicts
- All queries defined here
Model Layer (career_ops/models/)
- Pydantic BaseModel for all data contracts
- Shared between layers
- Validation at model level
ML Layer (career_ops/ml/)
- HuggingFace model wrappers
- Isolated from business logic
- Returns typed results, not raw model output
Anti-Patterns:
- DB calls in API handlers (use repository)
- HTTP concerns in service layer (keep transport-agnostic)
- Raw dict passing between layers (use Pydantic models)
- Business logic in repository layer (keep it data-access only)
- Hardcoded labels in any layer (use label_taxonomy.py)
Marcus Check-In Protocol
Gate 1 -- Start Gate (every new phase branch)
Ask 5 questions, wait for ALL answers:
- GOAL: Single most important thing this phase must accomplish
- BOUNDARY: What is explicitly OUT OF SCOPE
- RISK: What is most likely to go wrong
- DEFINITION OF DONE: How Marcus knows the phase is complete
- PRIORITY: If only one thing gets finished, what should it be
Gate 2 -- Scope Boundary (after Gate 1 answers)
Write IN-SCOPE / OUT-OF-SCOPE / ASSUMPTIONS document.
Wait for Marcus to type "APPROVED" before writing code.
Gate 3 -- Completion Gate (before marking phase done)
Present Definition of Done checklist with proof for every item.
Wait for Marcus to type "SHIPPED" before merging.
MongoDB Collections
| Collection | Phase | Purpose |
|---|
| email_audits | 1 | Raw audit snapshots |
| classifications | 2 | HuggingFace classification results |
| job_applications | 3 | Applied job tracking |
| recruiter_scores | 2 | Recruiter signal analysis results |
| weekly_digests | 3 | Generated digest history |
Rules:
- All collections defined in
repositories/
- Index creation via
scripts/setup_mongodb.py
- Motor (async driver) for all operations
- Never access DB directly from service layer
Configuration
| Parameter | Default | Description |
|---|
| gmail_scope | readonly | Gmail API OAuth scope (readonly for Phase 1-2) |
| classifier_model | foduucom/contact-form-spam-detection | HuggingFace model for email classification |
| followup_days | 7 | Days before flagging no-response |
| dry_run_default | true | Always produce dry-run report first |
| digest_frequency | weekly | How often to generate digest |
File Structure
career-ops-workflow/
SKILL.md # This skill definition
Notes
- Phase safety gates are NON-NEGOTIABLE. Phase 1 code must never write to Gmail.
- The label taxonomy is the single source of truth. Duplicating label strings anywhere else is a critical violation.
- Dry-run reports are the trust mechanism. Marcus must see what will happen before it happens.
- All config via environment variables. No hardcoded connection strings, API keys, or URLs.
- This module lives inside the Bob-AICompanion repo, not as a separate project.