Build an AI Job Application Pipeline

Build a 12-agent pipeline that audits your resume and prepares applications.

Introduction

30 Second Summary

Job searching as a senior engineer feels like a second full-time job. The administrative load of tailoring resumes, writing cover letters, and tracking applications across dozens of companies consumes the exact time you need for upskilling and interview prep.

In this project, you will build a 12-agent pipeline that discovers jobs, prepares tailored application packages, fact-checks every claim with dual-model validation, and tracks what converts across four category buckets. You will operate it daily through a high-fidelity command-center dashboard.

What You'll Build

A production-grade agentic pipeline powered by Claude Desktop and Codex CLI with 12 specialized agents, dual-model fact-checking, a multi-perspective professional presence audit, and a 7-view Next.js dashboard for daily operation.

By the end of this project, you'll have:

  • A 12-agent system across 4 teams (Source of Truth, Discovery, Synthesis, Quality) orchestrated by Claude Desktop with filesystem MCP, processing jobs through discovery, scoring, synthesis, and quality-checking across 4 category buckets.
  • A multi-perspective professional presence audit scored from Recruiter, Hiring Manager, and Lead Engineer viewpoints, continuously improving your resume, LinkedIn, and GitHub to pass ATS filters and human reviewers at each target bucket.
  • A command-center dashboard built with Next.js and Recharts, featuring 7 operational views (Home, Queue, Job Detail, Funnel, Variants, Weekly Review, Profile Evolution) backed by SQLite and Gmail API response tracking.
  • Secret Mission: Build a weekly Adversarial Council where Codex CLI challenges Claude's recommendations and a monthly Profile Evolution workflow for continuous self-improvement.

Are there any prerequisites?

This project requires an active Claude Max 20x subscription ($200/month) and an active ChatGPT Plus subscription ($20/month) for Codex CLI access. You also need a Google account for Gmail API integration.

Before We Start

Building a 12-agent pipeline requires a foundation of tools working together. Node.js powers the dashboard and the filesystem MCP server. Python handles scripting and data processing. Obsidian becomes the vault that Claude reads and writes through MCP.

In this step, you will install every tool the pipeline depends on, wire Claude Desktop to the vault filesystem, and confirm that Codex CLI can act as the adversarial fact-checker from a different model family.

In this step, get ready to:
  • Install Node.js, Python, Git, and Obsidian on Windows.
  • Configure the filesystem MCP server in Claude Desktop so Claude can read and write vault files.
  • Verify Codex CLI is authenticated and can access your working directory.
Install Node.js, Python, and Obsidian

The pipeline uses Node.js for the command-center dashboard and for running the filesystem MCP server. Python handles automation scripts. Obsidian provides the vault UI for browsing agent outputs.

  • Press the Windows key to open your search bar.
  • Type PowerShell and press Enter to open it.

Check whether Node.js is already installed by running this command:

node --version

✔️ I see v22 or v24 or higher

Node.js is ready. Move on to checking Python below.

ⓧ I see an older version

You need Node.js v22 or newer (LTS). Download and run the Windows installer from the official site.

  • Open https://nodejs.org in your browser.
  • Click the LTS download button (this will be v24 Krypton or v22 Jod).
  • Run the .msi installer, accept the defaults, and click through to finish.
  • Close and reopen PowerShell, then run node --version again.

ⓧ Command not found

Node.js is not installed. Download it from the official site.

  • Open https://nodejs.org in your browser.
  • Click the LTS download button.
  • Run the .msi installer, accept the defaults, and click through to finish.
  • Close and reopen PowerShell, then run node --version again to confirm it shows a version number.

Now check whether Python 3.12+ is installed by running:

python --version

✔️ I see Python 3.12 or higher

Python is ready. Move on to checking Git below.

ⓧ I see a version lower than 3.12

You need Python 3.12 or newer. Windows sometimes ships with an older version or a Microsoft Store alias that opens the Store instead of running Python.

  • Open https://www.python.org/downloads/ in your browser.
  • Download the latest Python 3.12+ installer for Windows.
  • Run the installer. Check the box Add python.exe to PATH before clicking Install Now.
  • Close and reopen PowerShell, then run python --version again.

ⓧ Command not found or Microsoft Store opens

Python is not installed (or Windows is redirecting to the Microsoft Store).

  • Open https://www.python.org/downloads/ in your browser.
  • Download the latest Python 3.12+ installer for Windows.
  • Run the installer. Check the box Add python.exe to PATH before clicking Install Now.
  • Close and reopen PowerShell, then run python --version to confirm it shows 3.12 or higher.

Check whether Git is installed by running:

git --version

✔️ I see a version number

Git is ready. Move on to installing Obsidian below.

ⓧ Command not found

Git is not installed.

  • Open https://git-scm.com/download/win in your browser.
  • Download the 64-bit installer.
  • Run the installer and accept all default settings.
  • Close and reopen PowerShell, then run git --version to confirm.
  • Download Obsidian from https://obsidian.md and run the installer.
  • Open Obsidian after it installs.
  • When prompted to create or open a vault, click Create new vault.
  • Name the vault vault and set the location to C:\Users\Roy (or your user home folder).
  • Click Create.

Why Obsidian as the vault?

Obsidian stores everything as plain markdown files on your local filesystem. Claude Desktop's filesystem MCP can read and write these files directly. No plugins or sync services are needed.

This means your agents output markdown that instantly appears in the vault, and anything you add to the vault is immediately available to Claude.

Having trouble with any installer?

Make sure you close and reopen PowerShell after each installation. PATH changes only take effect in new terminal sessions.

If Python still shows a version below 3.12 after installing, Windows may be resolving to an older copy. Run where python in PowerShell to see which paths are registered and ensure the new install path comes first.

Help me troubleshoot my tool installations

Configure Claude Desktop filesystem MCP

The filesystem MCP server gives Claude Desktop direct read/write access to your Obsidian vault. This is how agents will store their outputs, read client data, and update application materials.

  • Open Claude Desktop.
  • Click the Claude menu icon in the system tray (bottom-right corner of Windows).
  • Select Settings... from the menu.
  • In the Settings window, click the Developer tab in the left sidebar.
  • Click Edit Config to open the configuration file in your text editor.

Where is this config file?

On Windows, Claude Desktop looks for the config at %APPDATA%\Claude\claude_desktop_config.json. The Edit Config button opens this file directly.

  • Replace the contents of the file with the following JSON (or merge it into any existing config):
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "C:\\Users\\Roy\\vault"
      ]
    }
  }
}

What does this config do?

  • `"command": "npx"` tells Claude Desktop to use Node.js's npx tool to run the server.
  • `"-y"` automatically confirms the package download without prompting.
  • `"@modelcontextprotocol/server-filesystem"` is the official Anthropic filesystem server package.
  • The last argument is the vault directory path. Replace Roy with your actual Windows username if different.
  • Save the file and close the text editor.
  • Fully quit Claude Desktop (right-click the system tray icon and select Quit).
  • Reopen Claude Desktop.

After Claude Desktop restarts, verify the MCP connection is active.

  • Open a new Claude conversation.
  • Type: "List the files in my vault directory" and send the message.
  • Claude will ask for permission to use the filesystem tool. Click Allow.

Claude should respond with a list of files in your vault folder (which may be empty or contain a default Obsidian file). This confirms the MCP server is connected.

Claude doesn't show a filesystem tool?

Make sure you saved the JSON with correct syntax (no trailing commas, double backslashes in Windows paths). A single missing comma breaks the whole config.

Confirm Node.js is installed by running node --version in PowerShell. The MCP server needs npx (which comes with Node.js) to launch.

Check that the vault path exists on disk. If your Windows username is not Roy, update the path to match your actual user folder.

Help me fix my Claude Desktop MCP configuration

Verify Codex CLI access

Codex CLI is OpenAI's coding agent that runs locally from your terminal. In this pipeline, it acts as the adversarial fact-checker from a different model family (GPT) than Claude (Anthropic). This cross-model validation catches hallucinations and drift that a single model cannot self-detect.

  • Return to your PowerShell window.
  • Install Codex CLI by running this command:
powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"

What does this command do?

This downloads and runs the official Codex CLI installer script from OpenAI. It installs the codex binary on your system. Codex CLI is included with your ChatGPT Plus subscription at no extra cost.

  • Once installation completes, close and reopen PowerShell.
  • Verify Codex CLI is installed by running:
codex --version

✔️ I see a version number

Codex CLI is installed and ready.

ⓧ Command not found

The installer may not have added Codex to your PATH.

  • Close all PowerShell windows and open a fresh one.
  • Try running codex --version again.
  • If it still fails, try installing via npm as an alternative:
npm install -g @openai/codex
  • Run codex --version once more to confirm.
  • Now launch Codex interactively to verify authentication by running:
codex

Codex should launch its interactive terminal UI (TUI). If this is your first time, it will prompt you to sign in with your ChatGPT account. Follow the on-screen instructions to authenticate.

  • Once authenticated, type a quick test prompt like "What files are in the current directory?" and press Enter.
  • Codex will request permission to run a command. Allow it.
  • Confirm that Codex responds with a file listing from your current directory.
  • Type /exit or press Ctrl+C to quit the Codex session.

Why two AI models?

Claude (Anthropic) generates all application materials. Codex CLI (OpenAI, GPT family) independently verifies them. Because they are different model families with different training data and failure modes, one model catches what the other misses.

The key constraint in this pipeline: Claude cannot override a Codex FAIL. Only Roy can approve sending a package that Codex has flagged.

Codex won't authenticate?

Make sure you have an active ChatGPT Plus subscription. Codex CLI is included with Plus, Pro, Business, Edu, and Enterprise plans.

If the browser-based auth flow does not open, try running codex from a fresh PowerShell window with no VPN or proxy active.

Help me get Codex CLI authenticated on Windows

Your environment is ready. Every tool the pipeline depends on is installed and verified. Next up, you will design the architecture that coordinates all 12 agents across 4 teams.

Design the Architecture Blueprint

Your tools are installed, Claude Desktop is connected to your vault, and Codex CLI is verified. Now it is time to design the system before building it.

Good architecture documentation serves two purposes here. First, it becomes the implementation roadmap that guides which agents to build and how they connect. Second, it is portfolio evidence that proves Roy can think architecturally about multi-agent systems.

In this step, get ready to:
  • Write Architecture Decision Records documenting five key design choices.
  • Create a Mermaid system diagram showing the full pipeline flow.
  • Define the agent dependency graph for orchestration.
Write Architecture Decision Records

An Architecture Decision Record (ADR) captures why a design choice was made, not just what was chosen. Each ADR follows a standard format: Context (the problem), Decision (what you chose), Consequences (tradeoffs), and Status.

  • In Obsidian, right-click in the **File explorer** sidebar on the left.
  • Click **New folder**.
  • Type architecture and press **Enter**.

You should see the architecture folder appear in your vault's File explorer.

  • Right-click the architecture folder and click **New note**.
  • Name the note adr-001-dual-model-factcheck and press **Enter**.
  • Paste the following content into the note:
# ADR-001: Dual-Model Fact-Check Architecture

## Status

Accepted

## Context

A single AI model generating application materials introduces hallucination risk. Claude may over-generalize claims, inflate metrics, or produce statements that drift from the evidence in Roy's vault. Self-checking within the same model family shares the same blind spots. A fabricated claim in a resume or cover letter destroys credibility if caught by a hiring manager.

## Decision

Use Claude (Anthropic, Claude family) as the orchestration and generation model. Use Codex CLI (OpenAI, GPT family) as an independent adversarial fact-checker from a different model family. Codex CLI reads the vault filesystem and verifies every generated claim against cited evidence. Claude cannot override a Codex FAIL verdict. Only Roy can approve flagged packages.

## Consequences

- Higher accuracy: cross-model-family validation catches hallucinations that same-model checks miss.
- Every claim traces to vault evidence or gets flagged for Roy to supply proof or remove.
- Added cost: ChatGPT Plus subscription ($20/mo) required alongside Claude Max.
- Added latency: each package goes through two model passes before reaching the dashboard queue.
- Roy retains final authority over all flagged items.

What does this ADR capture?

The dual-model pattern is the pipeline's integrity backbone. Claude generates all materials, but a completely separate model family (Codex CLI using GPT) validates them independently.

This catches the blind spots that self-checking within the same model misses. The key constraint: Claude cannot approve its own work. Only Roy can override a Codex FAIL.

  • Obsidian auto-saves your note after 2 seconds of inactivity. Confirm the note appears under the architecture folder in the File explorer.

Note not appearing in the architecture folder?

Make sure you right-clicked the architecture folder specifically before clicking **New note**. If the note landed in the vault root, drag it into the architecture folder.

help me move a note into a different folder in Obsidian

  • Right-click the architecture folder and click **New note**.
  • Name the note adr-002-filesystem-mcp and press **Enter**.
  • Paste the following content:
# ADR-002: Filesystem MCP Over API-Based Approaches

## Status

Accepted

## Context

The 12-agent pipeline needs persistent shared state across runs. Agents read from and write to a knowledge base (resume claims, job data, variants, weekly metrics). Options considered: vector database API, custom REST API, cloud storage API, or local filesystem via MCP.

## Decision

Use Claude Desktop's filesystem MCP server to give Claude direct read/write access to the Obsidian vault on local disk. All agent state is stored as markdown and YAML files in a structured folder hierarchy.

## Consequences

- Zero API costs: no external database or storage service needed.
- Claude reads and writes files directly through MCP, no middleware required.
- Local persistence: data survives restarts, no cloud dependency.
- Obsidian provides a human-readable interface to the same files Claude operates on.
- Limitation: not suitable for multi-user or cloud-deployed scenarios (acceptable for single-operator use).
  • Right-click the architecture folder and click **New note**.
  • Name the note adr-003-zero-linkedin-automation and press **Enter**.
  • Paste the following content:
# ADR-003: Zero LinkedIn Automation Policy

## Status

Accepted

## Context

LinkedIn User Agreement Section 8.2 prohibits "software, devices, scripts, robots or any other means or processes (such as crawlers, browser plugins and add-ons or any other technology) to scrape or copy the Services." Accounts that violate this risk restriction or shutdown. Many job-search automation tools violate this policy and put users at ban risk.

## Decision

Zero automation touches LinkedIn. The pipeline achieves compliance by architectural construction, not by mitigation:
- Job discovery: Roy browses LinkedIn manually and copies URLs to a text file.
- Job submission: Roy clicks Apply manually and pastes prepared materials.
- Response tracking: LinkedIn sends email notifications to Gmail. Gmail API reads those emails.
- Profile updates: Roy executes all LinkedIn changes manually based on pipeline recommendations.

## Consequences

- 100% LinkedIn ToS compliance by construction. Zero ban risk.
- No Playwright, Puppeteer, or browser automation dependencies in the stack.
- Slightly more manual effort for Roy during daily 10-minute browse session.
- Response tracking still works via Gmail notification emails.
  • Right-click the architecture folder and click **New note**.
  • Name the note adr-004-four-bucket-categories and press **Enter**.
  • Paste the following content:
# ADR-004: Four Category Buckets With Separate Variants

## Status

Accepted

## Context

Roy's target companies span distinct hiring cultures and evaluation criteria. Anthropic evaluates differently from AWS, which evaluates differently from Palantir or Booz Allen. A single resume cannot optimize for all four contexts simultaneously.

## Decision

Define four category buckets, each with its own resume variant:
- AI Leaders (Anthropic, OpenAI, DeepMind): emphasize ML infrastructure, model serving, AI platform work.
- Big Tech (AWS, Google, Microsoft): emphasize scale, distributed systems, cloud architecture.
- Government Contractors (Palantir, Booz Allen, Anduril): emphasize compliance, secure infrastructure, FedRAMP-adjacent work.
- Other (startups, mid-market): generalist baseline variant.

## Consequences

- Tailored materials per hiring context maximize relevance.
- Four variants to maintain instead of one (managed by Agent A2).
- Funnel metrics tracked per bucket reveal which category converts best.
- Cover letters and Q&A prep also adapt per bucket context.
  • Right-click the architecture folder and click **New note**.
  • Name the note adr-005-sqlite and press **Enter**.
  • Paste the following content:
# ADR-005: SQLite Over Cloud Database

## Status

Accepted

## Context

The dashboard needs a structured data store for applications, claims, variants, weekly metrics, and activity logs. Options: PostgreSQL (requires Docker or cloud hosting), cloud databases (ongoing cost, network dependency), or SQLite (local file).

## Decision

Use SQLite as the sole database. Store the database file on local disk alongside the vault.

## Consequences

- Free: zero hosting or infrastructure cost.
- Zero configuration: no Docker, no connection strings, no migrations server.
- Portable: single file, easy to back up or move.
- Sufficient scale: handles thousands of applications over 52+ weeks without issue.
- Limitation: single-writer, not suitable for concurrent multi-user access (acceptable for single-operator use).

Why five ADRs?

Each ADR captures a decision that has alternatives a reviewer could reasonably question. Why not a single model? Why not a cloud database? Why not automate LinkedIn?

When Roy discusses this system in interviews, each ADR gives him a concrete talking point about architectural tradeoffs. That is what separates an operator from a builder.

  • Confirm all five ADR notes appear under the architecture folder in Obsidian's File explorer.
  • Click each note to verify the content saved correctly.

Missing an ADR or see a blank note?

Obsidian auto-saves after you stop typing. If a note is blank, you may have created it but not pasted the content. Click the note, paste the content, and wait 2 seconds for auto-save.

help me troubleshoot missing files in my Obsidian vault

Create the Mermaid system diagram

A system diagram shows how data flows through the entire pipeline. This becomes both the implementation roadmap and a visual artifact Roy can reference in interviews.

  • Right-click the architecture folder and click **New note**.
  • Name the note system-diagram and press **Enter**.
  • Paste the following content into the note:
# System Pipeline Diagram

## Full Pipeline Flow

```mermaid
flowchart TD
    %% Discovery Channels
    subgraph Discovery["Discovery Channels"]
        LC[Human LinkedIn Browse]
        GA[Google Alerts via Gmail]
        CP[Direct Career Pages]
    end

    %% Team B: Discovery Processing
    LC --> B1[B1: Job Intake Processor]
    GA --> B1
    CP --> B1
    B1 --> B2[B2: Categorizer + Fit Scorer]

    %% Team C: Synthesis - Parallel
    subgraph Synthesis["Synthesis - Parallel"]
        C1[C1: Resume Tailoring]
        C2[C2: Cover Letter Drafter]
        C3[C3: Q&A Generator]
    end

    B2 -->|score >= threshold| C1
    B2 -->|score >= threshold| C2
    B2 -->|score >= threshold| C3

    %% Team D: Quality Gates - Sequential
    C1 --> D1[D1: Codex Fact-Checker]
    C2 --> D1
    C3 --> D1
    D1 -->|PASS| D2[D2: Standards Gate]
    D1 -->|FAIL| FLAG[Flag for Roy]

    %% Dashboard and Tracking
    D2 -->|PASS| DQ[Dashboard Queue]
    D2 -->|FAIL| FLAG
    DQ --> ROY[Roy Reviews + Approves]
    ROY --> SUB[Manual Submission]
    SUB --> GMAIL[Gmail API Response Scanner]
    GMAIL --> DB[(SQLite Database)]
    DB --> D3[D3: Continuous Improvement]
    D3 -->|weekly| B2

    %% Monthly Profile Evolution
    subgraph Evolution["Monthly Profile Evolution"]
        AUDIT[3-Lens Re-Audit]
        UPDATE[Profile Update Recommendations]
        EXECUTE[Roy Executes Updates]
    end

    DB -->|monthly| AUDIT
    AUDIT --> UPDATE
    UPDATE --> EXECUTE
    EXECUTE -->|updated evidence| B2
```

## Agent Teams

| Team | Agents | Cadence |
|------|--------|--------|
| A (Source of Truth) | A1 Resume Anatomist, A2 Variant Curator | Once at setup, monthly refresh |
| B (Discovery) | B1 Job Intake, B2 Categorizer | Daily |
| C (Synthesis) | C1 Resume Tailoring, C2 Cover Letter, C3 Q&A | Per job (parallel) |
| D (Quality) | D1 Fact-Checker, D2 Standards Gate, D3 Improvement | Per package + weekly |

What does this diagram show?

The diagram maps the full data flow: 3 discovery channels feed into B1/B2 scoring, which feeds C1/C2/C3 synthesis in parallel, then through D1/D2 quality gates into the dashboard queue.

Two feedback loops close the system. The weekly D3 loop analyzes funnel data and adjusts filters. The monthly Profile Evolution loop re-audits Roy's materials and recommends updates as he gains new experience.

  • Confirm system-diagram appears under the architecture folder.
  • Click the note and scroll through it to confirm the Mermaid code block and the agent teams table both rendered.

Preview the Mermaid diagram

Obsidian renders Mermaid diagrams natively in Reading view. Press **Ctrl+E** to toggle between editing and reading view. In reading view, the code block becomes a visual flowchart.

Define the agent dependency graph

The orchestration prompt needs to know which agents can run in parallel and which must wait for another agent's output. This dependency graph becomes the rulebook for execution order.

  • Right-click the architecture folder and click **New note**.
  • Name the note agent-dependencies and press **Enter**.
  • Paste the following content into the note:
# Agent Dependency Graph

## Execution Order

### Team A: Source of Truth (Setup + Monthly)

- **A1 Resume Anatomist** runs first, decomposes resume into atomic claims.
- **A2 Variant Curator** runs after A1, generates bucket-specific variants from claims.

### Team B: Discovery (Daily, Sequential)

- **B1 Job Intake Processor** runs first, parses raw job URLs into structured data.
- **B2 Categorizer + Fit Scorer** runs after B1, assigns bucket and scores fit against claims.

### Team C: Synthesis (Per Job, Parallel)

- **C1 Resume Tailoring** runs in parallel with C2 and C3.
- **C2 Cover Letter Drafter** runs in parallel with C1 and C3.
- **C3 Q&A Generator** runs in parallel with C1 and C2.
- All three read from: B2 output (job data + score) + A2 output (variant for this bucket).

### Team D: Quality (Per Package, Sequential + Weekly)

- **D1 Codex Fact-Checker** runs after C1+C2+C3 complete, validates all claims.
- **D2 Standards Gate** runs after D1 PASS, checks EEOC + EU AI Act + formatting.
- **D3 Continuous Improvement** runs weekly, analyzes funnel data and proposes changes.

## Parallelization Rules for Orchestration

```
PARALLEL:    C1, C2, C3 (no dependencies between them)
SEQUENTIAL:  B1 then B2 (B2 needs B1 output)
SEQUENTIAL:  D1 then D2 (D2 only runs on D1-passed packages)
INDEPENDENT: Team A runs on its own schedule (setup + monthly)
INDEPENDENT: D3 runs weekly on its own schedule
BLOCKING:    D1 FAIL blocks the package until Roy resolves
```

## Visual Dependency Map

```
A1 --> A2 (setup, then monthly refresh)

B1 --> B2 (daily)
       |
  [C1 | C2 | C3] (parallel per job)
       |
      D1 --> D2 --> Dashboard Queue (per package)

D3 <-- SQLite metrics (weekly, feeds back into B2 filters)
```

Why does parallelization matter?

C1, C2, and C3 can all run at the same time because they read the same inputs independently. This means a single job goes from scored to fully-packaged in one parallel pass instead of three sequential ones.

D1 and D2 must be sequential because D2 (Standards Gate) should only run on packages that already passed D1 (fact-check). Running them in parallel would waste tokens checking standards on packages that contain fabricated claims.

  • Confirm agent-dependencies appears under the architecture folder.
  • You should now see 7 notes in the architecture folder: the 5 ADRs, the system diagram, and the agent dependency graph.

Seeing fewer than 7 files?

Click the arrow next to the architecture folder to expand it. If a file ended up in the vault root instead of inside the folder, drag it into architecture using the File explorer sidebar.

help me organize files in my Obsidian vault

✔️ Awesome, I've got everything!

Great. Double check you have all 7 notes saved in the architecture folder.

ⓧ I'd like to double check the full file list

Your architecture folder should contain exactly these 7 files:

  • adr-001-dual-model-factcheck.md
  • adr-002-filesystem-mcp.md
  • adr-003-zero-linkedin-automation.md
  • adr-004-four-bucket-categories.md
  • adr-005-sqlite.md
  • system-diagram.md
  • agent-dependencies.md

Each file should contain the full markdown content from the code blocks above. If any file is missing or empty, right-click the architecture folder, create a new note with the correct name, and paste the content.

Your architecture blueprint is complete. These documents define the pipeline's structure, constraints, and execution rules. Next up, you will run a multi-perspective audit of Roy's professional presence through three expert lenses.

Audit Roy's Professional Presence

Your architecture documentation gives the pipeline its blueprint. Now it needs something equally important: a clear-eyed assessment of the raw materials it will work from.

A pipeline can only produce application packages as strong as the source material it draws on. In this step, you will run Roy's resume, LinkedIn profile, and GitHub through three expert lenses that mirror the actual hiring pipeline: recruiter screening, hiring manager evaluation, and senior technical review.

In this step, get ready to:
  • Run a Recruiter lens audit to assess ATS compatibility and first-pass screening readiness per bucket.
  • Run a Hiring Manager lens audit to evaluate leadership signal and impact framing per bucket.
  • Run a Lead Engineer lens audit to assess technical credibility, GitHub presence, and gap-closing project ideas per bucket.
Run the Recruiter lens (ATS + first-pass screening)

The first gate every application passes through is an ATS (Applicant Tracking System). These systems scan for keyword matches against the job description before a human ever sees the resume. A recruiter then gives the surviving resumes a 6-second scan for red flags.

  • Switch to Claude Desktop.
  • Paste Roy's current resume into the chat along with the following audit prompt:
# Recruiter Lens Audit

You are an experienced technical recruiter who screens 200+ resumes per week.
Evaluate the resume I just provided through these 5 filters:

1. **ATS keyword matching** - Will this resume surface for Senior Cloud Engineer, Cloud Architect, Engineering Manager, and Staff Engineer roles at each of these company categories?
   - AI Leaders (Anthropic, OpenAI, DeepMind)
   - Big Tech (Google, AWS, Microsoft, Meta)
   - Government Contractors (Palantir, Anduril, Booz Allen)
   - Other (generalist baseline)

2. **Headline/summary signal** - Does the headline immediately communicate relevant seniority?

3. **Consistency check** - Are job titles, dates, and scope claims internally consistent?

4. **Red flag scan** - Employment gaps unexplained? Title inflation? Formatting issues?

5. **6-second scan test** - Is the most important information visible in the top third?

## Output Format

For each of the 4 buckets, provide:
- ATS compatibility score (0-100)
- Top 5 keyword gaps (terms that target job descriptions use but this resume lacks)
- Formatting fixes needed
- One-line verdict: would this pass your first screen for that bucket?

Write the full output to `/profile-evolution/baseline-audit.md` under a section titled `## Recruiter Lens`. Include today's date at the top of the file as the baseline snapshot date.

What does this prompt do?

The prompt puts Claude into the mindset of a recruiter who processes hundreds of resumes weekly. It forces evaluation against each of the 4 category buckets you defined in ADR-004, because the keywords that surface a resume for Anthropic are different from those that surface one for Palantir.

Claude writes the results directly to your vault using the filesystem MCP connection you configured in Step 1. The /profile-evolution/ directory will be created automatically if it does not exist yet.

  • Wait for Claude to finish processing. Review the output in the chat to confirm it includes all 4 bucket scores.
  • Verify that Claude wrote the file by checking the chat for a confirmation message mentioning /profile-evolution/baseline-audit.md.

Claude didn't write the file?

If Claude says it cannot access the filesystem, confirm that the filesystem MCP server is running in Claude Desktop settings. The server must point to your vault's root directory.

If Claude produced the audit in chat but did not save it, ask Claude directly: "Please write that full output to /profile-evolution/baseline-audit.md in my vault."

Help me troubleshoot my filesystem MCP connection in Claude Desktop.

Run the Hiring Manager lens (leadership + impact signal)

Once a resume passes the ATS and recruiter screen, a hiring manager evaluates it for a different set of signals. They want to see increasing scope, business outcomes, and evidence that this person leads and does not just execute.

  • In the same Claude Desktop conversation, paste the following prompt to run the second lens:
# Hiring Manager Lens Audit

You are a hiring manager screening for engineering-management hybrid roles
(Senior Cloud Engineer stepping into Tech Lead, Cloud Architect with people
management, Engineering Manager with deep technical background).

Using the same resume from the previous evaluation, assess:

1. **Scope progression** - Does the resume show increasing responsibility over time? Are there clear transitions from IC contributor to leadership?

2. **Business impact framing** - Are achievements framed as outcomes (revenue, cost savings, team velocity, reliability improvements) or just tasks ("implemented X", "maintained Y")?

3. **Cross-functional evidence** - Is there evidence of mentoring, stakeholder management, roadmap ownership, or hiring?

4. **LinkedIn About positioning** - Based on what you know from the resume, would this person's LinkedIn About section position them as someone who builds AND leads?

5. **Interview pull** - Would you say "I want to talk to this person" after reading this? Why or why not?

## Output Format

For each of the 4 buckets (AI Leaders, Big Tech, Government Contractors, Other):
- Leadership signal score (0-100)
- Top 3 specific rewrite recommendations for impact framing
- Verdict: does this resume signal leadership readiness for that bucket?

Append this output to `/profile-evolution/baseline-audit.md` under a new section titled `## Hiring Manager Lens`.

Why a separate lens instead of one combined audit?

Each stage of the hiring pipeline catches different problems. A recruiter misses weak impact framing because they screen for keywords. A hiring manager misses ATS failures because they only see resumes that already passed.

Running separate lenses ensures Roy's materials pass every gate. A single combined prompt would blur these distinct failure modes together and produce weaker recommendations.

  • Wait for Claude to finish. Confirm the output includes leadership signal scores for all 4 buckets.
  • Verify Claude appended the new section to baseline-audit.md (not a new file). You should now have two sections: Recruiter Lens and Hiring Manager Lens.

Claude created a separate file instead of appending?

If Claude wrote to a new file instead of appending, ask it: "Please read /profile-evolution/baseline-audit.md and append the Hiring Manager Lens section to the end of that file, keeping the existing Recruiter Lens content intact."

Help me get Claude to append to an existing file via filesystem MCP.

Run the Lead Engineer lens (technical depth + credibility)

The final lens simulates a Staff or Principal Engineer reviewing Roy's materials. This is the deepest technical evaluation. It assesses whether Roy's resume demonstrates system design fluency at the scale these companies operate, and whether his GitHub presence backs up the claims.

  • In the same Claude Desktop conversation, paste the following prompt for the final lens:
# Lead Engineer Lens Audit

You are a Staff/Principal Engineer conducting a technical screen. You have
seen thousands of resumes and you know the difference between someone who
has operated at scale and someone who has only read about it.

Using the same resume, assess:

1. **System design fluency** - Does this resume demonstrate architectural thinking at the target company's scale? Are there specific numbers (requests/sec, data volume, team size, uptime SLA)?

2. **Technical specificity** - Are choices opinionated and specific ("chose Kafka over SQS because X") or generic ("used AWS")?

3. **GitHub credibility** - Based on what you can infer from the resume, would you expect this person's GitHub to show real engineering work (infrastructure modules, open-source contributions, system design docs) or just tutorial repos?

4. **Decision-making evidence** - Is there evidence of choosing X over Y because Z? Technical trade-offs documented?

5. **Level calibration** - Would a Staff Engineer at each target bucket say "this person can operate at our level"?

## Output Format

For each of the 4 buckets (AI Leaders, Big Tech, Government Contractors, Other):
- Technical credibility score (0-100)
- Top 3 GitHub improvement recommendations (specific repos or contributions that would strengthen the profile)
- 2-3 project ideas that would close the biggest gap for that bucket
- Verdict: does this resume pass a senior technical screen for that bucket?

Append this output to `/profile-evolution/baseline-audit.md` under a new section titled `## Lead Engineer Lens`.

Finally, add a section at the very top of the file titled `## Audit Metadata` with today's date and a note: "Baseline snapshot for Monthly Profile Evolution tracking. Re-run monthly to measure improvement."

What does this prompt do?

This lens catches the problems the other two miss. A resume can pass ATS and impress a hiring manager while still failing a technical screen because the claims are too vague or the scale indicators are missing.

The project ideas output is especially valuable. These become Roy's upskilling roadmap. Each project closes a specific credibility gap for a specific bucket.

  • Wait for Claude to finish. Confirm the output includes technical credibility scores, GitHub recommendations, and project ideas for all 4 buckets.
  • Open Obsidian and navigate to /profile-evolution/baseline-audit.md.
  • Verify the file contains all four sections: Audit Metadata, Recruiter Lens, Hiring Manager Lens, and Lead Engineer Lens.

What should I see?

Your baseline-audit.md file should have a date stamp at the top, followed by three major sections. Each section contains scores for all 4 buckets (AI Leaders, Big Tech, Government Contractors, Other) plus actionable recommendations.

This file becomes the baseline for the Monthly Profile Evolution workflow. Every month, the pipeline re-runs these same three lenses and compares results against this snapshot to measure improvement.

Missing a section in the audit file?

If one of the three lens sections is missing, Claude may have hit a context length limit mid-response. Ask Claude: "Please read /profile-evolution/baseline-audit.md and tell me which sections are present. Then add any missing section."

If scores seem inconsistent across buckets (e.g., identical scores for all 4), ask Claude to re-evaluate a specific bucket with more detail about how that bucket's hiring bar differs from the others.

Help me fix my baseline audit file.

Why this approach over a single combined prompt?

Each lens corresponds to a real stage in the hiring pipeline. Recruiters, hiring managers, and technical interviewers filter for different signals. A resume that passes ATS keyword matching can still fail a hiring manager's impact test, and a resume with great impact framing can still fail a technical depth check.

Running them as separate prompts in one conversation gives Claude focused context for each evaluation. The alternative (one mega-prompt) produces shallower analysis because the model tries to balance all three perspectives simultaneously.

Your professional presence now has a clear, scored baseline. You know exactly where Roy's materials are strong and where the gaps are per bucket. Next up, you will build the vault architecture and define Roy's client identity so the agents know who they are working for and what targets to optimize toward.

Build the Vault and Client Identity

Your architecture is documented and your professional presence is audited. Now you need the operational infrastructure where all 12 agents will read and write data.

The Obsidian vault is the shared filesystem that connects every agent through Claude Desktop's filesystem MCP. In this step, you will create the full directory structure, define Roy's search parameters in a structured YAML file, and configure the three discovery channels that feed jobs into the pipeline.

In this step, get ready to:
  • Create the vault directory structure with all required folders.
  • Write client-identity.yaml with role families, bucket definitions, and hard exclusions.
  • Configure the 3-channel job discovery protocol (Google Alerts, LinkedIn browse, direct career pages).
Create the vault directory structure

The 12 agents need dedicated folders to read inputs and write outputs. Each folder serves a specific team or function in the pipeline.

  • Open PowerShell by pressing the Windows key, typing PowerShell, and pressing Enter.
  • Navigate to your vault folder (the one your filesystem MCP is configured to use) by running:
cd [[VAULT_PATH="path to your vault folder, e.g. C:\Users\Roy\Desktop\job-pipeline"]]
  • Create the remaining vault directories by running these commands:
# Create directories for each agent team and workflow stage
mkdir clients\roy -Force
mkdir agents -Force
mkdir intake -Force
mkdir applications -Force
mkdir responses -Force
mkdir variants -Force
mkdir evidence -Force
mkdir weekly-reviews -Force

What do these folders do?

  • clients\roy holds Roy's identity config and search parameters.
  • agents stores the 12 agent system prompts you will write in the next step.
  • intake receives raw job URLs from all three discovery channels.
  • applications holds completed application packages (resume variant + cover letter + Q&A).
  • responses stores parsed Gmail responses (recruiter replies, rejections, interview invites).
  • variants contains the 4 category-specific resume variants.
  • evidence holds documentation that backs up resume claims (offer letters, commit histories, certs).
  • weekly-reviews captures improvement metrics and filter changes each week.
  • Confirm the directories exist by running:
ls

You should see all folders listed: agents, applications, architecture, clients, evidence, intake, profile-evolution, responses, variants, and weekly-reviews.

Missing some folders?

Make sure you ran the commands from inside your vault folder. Run pwd to confirm your current directory matches the path configured in your Claude Desktop filesystem MCP settings.

How do I verify my current directory and re-run the mkdir commands?

Write client-identity.yaml

The client identity file defines everything the pipeline needs to know about Roy's job search. Agents B1 and B2 use it to categorize and score jobs against Roy's preferences.

  • Create the client-identity.yaml file inside clients\roy\ by running:
New-Item -Path clients\roy\client-identity.yaml -ItemType File
  • Open client-identity.yaml in your text editor and paste the following content:
# Client Identity Configuration
# Defines Roy's target parameters for the 12-agent pipeline

role_families:
  - Senior Cloud Engineer
  - Cloud Architect
  - Engineering Manager
  - Staff Engineer

geo_preferences:
  primary: United States
  open_to:
    - Remote (US-based)
    - Hybrid (major tech hubs)

remote_preference: preferred

buckets:
  ai_leaders:
    companies:
      - Anthropic
      - Palantir
      - OpenAI
      - DeepMind
    hiring_bar: >
      Expects demonstrated ML infrastructure or AI platform experience.
      Values system design at scale, opinionated technical choices,
      and evidence of building production AI systems.

  big_tech:
    companies:
      - Google
      - AWS
      - Microsoft
      - Meta
    hiring_bar: >
      Expects deep distributed systems knowledge and cloud architecture
      at massive scale. Values cross-team leadership, operational
      excellence, and quantified business impact.

  government_contractors:
    companies:
      - Booz Allen Hamilton
      - Leidos
      - SAIC
      - Palantir Federal
    hiring_bar: >
      Expects FedRAMP-adjacent infrastructure experience, security
      clearance eligibility, and compliance-first architecture.
      Values documentation rigor and audit trail thinking.

  other:
    description: Generalist baseline for companies outside the top 3 buckets
    hiring_bar: >
      Standard senior engineering bar. Values breadth of cloud
      experience, leadership signal, and clear communication.

hard_exclusions:
  - No relocation required (remote or hybrid only)
  - No non-tech industries (finance-tech and health-tech are acceptable)
  - No contract-only positions (contract-to-hire is acceptable)
  - No roles requiring active security clearance (clearance-eligible is acceptable)

Why define buckets with hiring bars?

Each bucket has a fundamentally different hiring bar. An AI Leaders company cares about ML infrastructure experience. A Government Contractor cares about FedRAMP compliance patterns.

Without separate hiring bar descriptions, the scoring agent would apply a generic rubric that undervalues the signals each bucket actually looks for. The pipeline uses these descriptions to score fit (Agent B2) and to select the correct resume variant (Agent C1) for each application.

  • Save client-identity.yaml.
  • Verify the file exists by running:
cat clients\roy\client-identity.yaml

You should see the full YAML content printed in the terminal.

Seeing an empty file?

If cat shows nothing, the file was created empty and the content was not pasted. Open the file in your editor, paste the YAML, and save again.

How do I open and edit this file in my text editor on Windows?

Set up job intake channels

The pipeline discovers jobs through three channels. Two are passive (Google Alerts and direct career pages). One is active (Roy's daily LinkedIn browse). All three feed URLs into the same intake file.

  • Open your browser and navigate to google.com/alerts.
  • Sign in with your Google account (the same one connected to your Gmail).
  • In the search field at the top, type the first keyword phrase:
"Senior Cloud Engineer" remote
  • Click Show options to expand the settings panel.
  • Set How often to At most once a day.
  • Leave Sources on Automatic.
  • Set Region to United States.
  • Set How many to All results.
  • Confirm Deliver to shows your Gmail address.
  • Click Create Alert.
  • Repeat the process for each of these keyword phrases (one alert per phrase):
"Cloud Architect" remote
"Engineering Manager" cloud infrastructure
"Staff Engineer" cloud OR platform

Why 4 alerts instead of one broad search?

Each alert maps to one of Roy's role families. Separate alerts prevent results from drowning each other. A single broad alert like "cloud engineer" returns too much noise at the junior level.

Google Alerts delivers results to Gmail for free. The Gmail API integration you build later will automatically scan these emails for job URLs.

Now create the intake template file that captures URLs from Roy's daily LinkedIn browse.

  • Switch back to your PowerShell terminal in the vault folder.
  • Create the daily URLs template by running:
@"
# Daily Job URL Intake
# Paste URLs from LinkedIn browse, Google Alerts, or direct career pages
# Format: URL | date found | source | notes
#
# Example:
# https://careers.anthropic.com/role/12345 | 2026-06-01 | direct | Senior Cloud Engineer, remote
# https://linkedin.com/jobs/view/98765 | 2026-06-01 | linkedin | Staff Eng at Google, hybrid
#
"@ | Out-File -FilePath intake\daily-urls.txt -Encoding utf8

What does this template do?

Roy fills this file during his daily 10-minute LinkedIn browse. He copies job URLs he finds interesting and adds the date, source channel, and a brief note. Agent B1 (Job Intake Processor) reads this file to parse and process new URLs into the pipeline.

Finally, document the 3-channel discovery protocol so the system has a single reference for how jobs enter the pipeline.

  • Create the discovery protocol document by running:
@"
# 3-Channel Discovery Protocol

## Channel 1: Human LinkedIn Browse
- Roy manually browses LinkedIn for 10 minutes daily.
- Copies interesting job URLs into intake/daily-urls.txt with date, source, and notes.
- Zero automation touching LinkedIn. Human browse only (ADR-003).

## Channel 2: Google Alerts
- Configured for target role keywords, delivered to Gmail once daily.
- Roy copies relevant URLs from alert emails into intake/daily-urls.txt.
- Gmail API (built in Step 8) will automate URL extraction from these emails.

## Channel 3: Direct Career Pages
- Roy checks these career pages weekly:
  - Anthropic: https://www.anthropic.com/careers
  - Palantir: https://www.palantir.com/careers
  - Google: https://careers.google.com
  - AWS: https://www.amazon.jobs
- Copies relevant URLs into intake/daily-urls.txt.

## Flow
All three channels feed into intake/daily-urls.txt.
Agent B1 (Job Intake Processor) reads this file daily.
Agent B2 (Categorizer + Fit Scorer) scores and buckets each job.
"@ | Out-File -FilePath architecture\discovery-protocol.md -Encoding utf8
  • Verify both files were created by running:
cat intake\daily-urls.txt
cat architecture\discovery-protocol.md

You should see the template content and the 3-channel protocol printed in the terminal.

Seeing encoding errors or garbled characters?

If special characters look wrong, open the file in your editor and re-save with UTF-8 encoding. PowerShell's Out-File -Encoding utf8 should handle this, but some editors default to a different encoding.

How do I fix PowerShell file encoding to UTF-8?

✔️ Awesome, I've got everything!

Great. Double check you have saved all three new files: clients/roy/client-identity.yaml, intake/daily-urls.txt, and architecture/discovery-protocol.md.

ⓧ I'd like to double check the full code

Here is the full content of each file created in this step.

client-identity.yaml:

# Client Identity Configuration
# Defines Roy's target parameters for the 12-agent pipeline

role_families:
  - Senior Cloud Engineer
  - Cloud Architect
  - Engineering Manager
  - Staff Engineer

geo_preferences:
  primary: United States
  open_to:
    - Remote (US-based)
    - Hybrid (major tech hubs)

remote_preference: preferred

buckets:
  ai_leaders:
    companies:
      - Anthropic
      - Palantir
      - OpenAI
      - DeepMind
    hiring_bar: >
      Expects demonstrated ML infrastructure or AI platform experience.
      Values system design at scale, opinionated technical choices,
      and evidence of building production AI systems.

  big_tech:
    companies:
      - Google
      - AWS
      - Microsoft
      - Meta
    hiring_bar: >
      Expects deep distributed systems knowledge and cloud architecture
      at massive scale. Values cross-team leadership, operational
      excellence, and quantified business impact.

  government_contractors:
    companies:
      - Booz Allen Hamilton
      - Leidos
      - SAIC
      - Palantir Federal
    hiring_bar: >
      Expects FedRAMP-adjacent infrastructure experience, security
      clearance eligibility, and compliance-first architecture.
      Values documentation rigor and audit trail thinking.

  other:
    description: Generalist baseline for companies outside the top 3 buckets
    hiring_bar: >
      Standard senior engineering bar. Values breadth of cloud
      experience, leadership signal, and clear communication.

hard_exclusions:
  - No relocation required (remote or hybrid only)
  - No non-tech industries (finance-tech and health-tech are acceptable)
  - No contract-only positions (contract-to-hire is acceptable)
  - No roles requiring active security clearance (clearance-eligible is acceptable)

daily-urls.txt:

# Daily Job URL Intake
# Paste URLs from LinkedIn browse, Google Alerts, or direct career pages
# Format: URL | date found | source | notes
#
# Example:
# https://careers.anthropic.com/role/12345 | 2026-06-01 | direct | Senior Cloud Engineer, remote
# https://linkedin.com/jobs/view/98765 | 2026-06-01 | linkedin | Staff Eng at Google, hybrid
#

discovery-protocol.md:

# 3-Channel Discovery Protocol

## Channel 1: Human LinkedIn Browse
- Roy manually browses LinkedIn for 10 minutes daily.
- Copies interesting job URLs into intake/daily-urls.txt with date, source, and notes.
- Zero automation touching LinkedIn. Human browse only (ADR-003).

## Channel 2: Google Alerts
- Configured for target role keywords, delivered to Gmail once daily.
- Roy copies relevant URLs from alert emails into intake/daily-urls.txt.
- Gmail API (built in Step 8) will automate URL extraction from these emails.

## Channel 3: Direct Career Pages
- Roy checks these career pages weekly:
  - Anthropic: https://www.anthropic.com/careers
  - Palantir: https://www.palantir.com/careers
  - Google: https://careers.google.com
  - AWS: https://www.amazon.jobs
- Copies relevant URLs into intake/daily-urls.txt.

## Flow
All three channels feed into intake/daily-urls.txt.
Agent B1 (Job Intake Processor) reads this file daily.
Agent B2 (Categorizer + Fit Scorer) scores and buckets each job.

Your vault is now fully structured and Roy's search parameters are defined. Next up, you will write the 12 agent system prompts that bring this pipeline to life.

Write the 12-Agent Library

Your vault is structured, your client identity is defined, and your architecture is documented. Now you need the workers that operate inside this system.

Each agent in the pipeline has a single job: read specific inputs from the vault, produce a defined output, and hand off to the next agent. In this step, you will write all 12 agent system prompts as markdown files with clear input/output contracts, then wire them into a Claude Project that coordinates their execution.

In this step, get ready to:
  • Write system prompts for Team A (Source of Truth) and Team B (Discovery).
  • Write system prompts for Team C (Synthesis) and Team D (Quality).
  • Create a Claude Project with the master orchestration prompt and upload knowledge files.
Write Team A and Team B system prompts

Team A establishes the source of truth. Agent A1 decomposes Roy's resume into verifiable atomic claims. Agent A2 uses those claims to generate bucket-specific variants. Team B handles discovery. Agent B1 parses raw job URLs into structured data. Agent B2 scores each job against Roy's claims.

  • Create the file A1-resume-anatomist.md in the /agents/ folder by pasting the following system prompt:
# A1: Resume Anatomist

## Role
You are the Resume Anatomist. You read Roy's master resume and decompose every experience bullet into atomic claims with evidence citation requirements.

## Input
- Master resume file (provided via filesystem MCP)

## Output
- `/evidence/atomic-claims.md` with each claim tagged to source evidence
- Flags unsupported claims for Roy to supply evidence or remove

## Process
1. Read the master resume end to end
2. For each experience bullet, extract the core claim (what was done, at what scale, with what outcome)
3. Tag each claim with required evidence type (offer letter, commit history, metrics dashboard, peer reference)
4. Mark each claim as SUPPORTED or UNSUPPORTED based on whether evidence is cited
5. Output the complete atomic claims file

## Constraints
- Never modify or embellish claims
- Every claim must map to exactly one evidence type
- Ambiguous claims get flagged, not assumed
  • Create A2-variant-curator.md in the same /agents/ folder:
# A2: Variant Curator

## Role
You are the Variant Curator. You read atomic claims and client-identity.yaml, then generate 4 resume variants (one per bucket). You never fabricate. You reorder and emphasize only.

## Input
- `/evidence/atomic-claims.md`
- `/clients/roy/client-identity.yaml`

## Output
- `/variants/ai-leaders.md` — emphasizes ML infrastructure, model serving, AI platform work
- `/variants/big-tech.md` — emphasizes scale, distributed systems, cloud architecture
- `/variants/government.md` — emphasizes compliance, secure infrastructure, FedRAMP-adjacent work
- `/variants/other.md` — generalist baseline

## Process
1. Read atomic claims and bucket definitions from client-identity.yaml
2. For each bucket, select and reorder claims that best demonstrate fit
3. Adjust bullet emphasis (not wording) to highlight bucket-relevant keywords
4. Preserve Roy's existing resume structure and voice
5. Evaluate each variant against the 3-lens audit criteria before finalizing

## Constraints
- Never fabricate claims or experience
- Maintain consistent dates, titles, and facts across all variants
- Each variant must pass ATS keyword matching for its target bucket

What do these prompts do?

A1 turns Roy's resume into a database of verifiable facts. Every bullet becomes an atomic claim that needs evidence backing it up. A2 then recombines those atoms into four variants optimized for different employer categories.

The key constraint: A2 never invents new experience. It reorders and emphasizes existing claims per bucket.

  • Create B1-job-intake-processor.md in /agents/:
# B1: Job Intake Processor

## Role
You are the Job Intake Processor. You read intake/daily-urls.txt and Google Alert emails, then parse job postings into structured JSON.

## Input
- URL list from `intake/daily-urls.txt`
- Google Alert email content (forwarded via Gmail)

## Output
- `/intake/parsed-jobs/` — one JSON file per job with structured fields

## JSON Schema
```json
{
  "job_id": "unique identifier",
  "title": "role title",
  "company": "company name",
  "url": "source URL",
  "location": "location or remote",
  "requirements": ["list of requirements"],
  "responsibilities": ["list of responsibilities"],
  "qualifications": ["list of qualifications"],
  "date_found": "ISO date",
  "source": "LinkedIn | Google Alert | direct"
}
```

## Constraints
- Extract only what the posting states; do not infer missing fields
- If a URL is unreachable, log it and skip
- Deduplicate by company + title combination
  • Create B2-categorizer-fit-scorer.md in /agents/:
# B2: Categorizer + Fit Scorer

## Role
You are the Categorizer and Fit Scorer. You read parsed job JSON and atomic claims, assign each job to a bucket, and score fit 0-100 with explained gaps.

## Input
- `/intake/parsed-jobs/` — parsed job JSON files
- `/evidence/atomic-claims.md`
- `/clients/roy/client-identity.yaml` — bucket definitions and exclusions

## Output
- `/intake/scored-jobs/` — JSON per job with bucket assignment, fit score, and gap analysis

## Scoring Criteria
- Skills match (0-30): How many required skills does Roy have evidence for?
- Experience match (0-30): Do Roy's years and scope meet the requirement?
- Seniority match (0-20): Is this the right level (not too junior, not too senior)?
- Bucket alignment (0-20): How well does this job fit the bucket's hiring bar?

## Process
1. Read job JSON and match company against bucket definitions
2. Check hard exclusions from client-identity.yaml
3. Score each dimension against atomic claims
4. Produce gap analysis listing specific missing qualifications
5. Flag jobs scoring below 40 as low-priority

## Constraints
- Every score must cite specific claims (or absence of claims) as justification
- Never inflate scores; missing evidence means points deducted
- Hard exclusions result in automatic score of 0

What does Team B do?

B1 is the intake funnel. It turns raw URLs and email alerts into clean, structured data that the rest of the pipeline can process. B2 applies Roy's identity against each job to determine which bucket it belongs to and whether it is worth pursuing.

The gap analysis is critical. It tells Roy exactly what is missing for a specific role so he can decide whether to apply anyway or skip it.

  • Save all four files in the /agents/ folder.
  • Confirm the files exist by running:
ls agents/

You should see A1-resume-anatomist.md, A2-variant-curator.md, B1-job-intake-processor.md, and B2-categorizer-fit-scorer.md listed.

Files not showing up?

Make sure you are running ls from the vault root directory. If you created the files elsewhere, move them into the /agents/ folder.

help me find where my agent files ended up

Write Team C and Team D system prompts

Team C runs in parallel for each job that passes the fit threshold. It produces the application package: tailored resume, cover letter, and interview Q&A prep. Team D validates everything before Roy sees it.

  • Create C1-resume-tailoring.md in /agents/:
# C1: Resume Tailoring

## Role
You select the correct variant for the job's bucket and tailor bullet emphasis to the specific JD keywords.

## Input
- Scored job JSON from `/intake/scored-jobs/`
- Bucket-specific variant from `/variants/[bucket].md`

## Output
- `/applications/[job-id]/resume-tailored.md`

## Process
1. Read scored job JSON to determine bucket and key requirements
2. Load the corresponding variant (ai-leaders, big-tech, government, or other)
3. Reorder bullets within each section to front-load JD-matching experience
4. Bold or emphasize keywords that appear in both the JD and Roy's claims
5. Ensure the final resume is ATS-parseable (no tables, no columns, standard headings)

## Constraints
- Never add experience not in the variant
- Maintain consistent formatting across all tailored outputs
- If the gap analysis shows critical missing skills, note them in a comment block
  • Create C2-cover-letter-drafter.md in /agents/:
# C2: Cover Letter Drafter

## Role
You draft a cover letter citing vault-evidenced wins, tailored to the specific job and bucket.

## Input
- Scored job JSON from `/intake/scored-jobs/`
- `/evidence/atomic-claims.md`
- `/clients/roy/client-identity.yaml`

## Output
- `/applications/[job-id]/cover-letter.md`

## Process
1. Identify the 2-3 strongest claims that directly address the job's top requirements
2. Open with a hook specific to the company and role (not generic)
3. Build each body paragraph around one claim with quantified evidence
4. Close with a forward-looking statement tied to what Roy would accomplish in the role
5. Keep total length under 350 words

## Constraints
- Every claim cited must exist in atomic-claims.md with SUPPORTED status
- Never use generic openers ("I am excited to apply for...")
- Match the company's communication tone (formal for government, direct for tech)
- For EU-based employers: include AI disclosure per EU AI Act Art 50
  • Create C3-qa-generator.md in /agents/:
# C3: Q&A Generator

## Role
You generate likely interview questions and answers sourced from the claims database and client-identity.yaml.

## Input
- Scored job JSON from `/intake/scored-jobs/`
- `/evidence/atomic-claims.md`

## Output
- `/applications/[job-id]/qa-prep.md`

## Process
1. Analyze the job requirements to predict likely interview questions
2. Generate 8-12 questions across categories: behavioral, technical, system design, leadership
3. For each question, draft a STAR-format answer grounded in Roy's atomic claims
4. Include follow-up questions the interviewer might ask
5. Note which claims provide the strongest evidence for each answer

## Constraints
- Every answer must trace to at least one atomic claim
- Never fabricate scenarios or metrics
- Flag questions where Roy lacks strong evidence (preparation gaps)

Why do C1, C2, and C3 run in parallel?

Each agent reads from the same scored job JSON and claims database, but produces independent outputs. There are no dependencies between a tailored resume, a cover letter, and Q&A prep. Running them in parallel means a single Claude session can produce the full application package for one job in one pass.

  • Create D1-codex-fact-checker.md in /agents/:
# D1: Codex Fact-Checker

## Role
You are run via Codex CLI (a different model family from Claude). You independently verify every claim in an application package traces to cited evidence.

## Input
- `/applications/[job-id]/` — full application package (resume, cover letter, Q&A)
- `/evidence/atomic-claims.md` — the source of truth

## Output
- Fact-check report with PASS/FLAG per claim

## Process
1. Extract every factual claim from the application package
2. For each claim, search atomic-claims.md for a matching entry
3. Verify the claim's scope and metrics match the evidence (no inflation)
4. Check for contradictions between resume, cover letter, and Q&A answers
5. Output a line-by-line report: PASS (evidence found) or FLAG (unsupported/inflated)

## Constraints
- You cannot override a FLAG; only Roy can approve sending flagged content
- Treat absence of evidence as FLAG, not as acceptable
- Check for metric inflation (e.g., "200+ microservices" in the claim vs "150" in the evidence)
  • Create D2-standards-gate.md in /agents/:
# D2: Standards Gate

## Role
You check the complete application package for compliance, legal, and quality standards before it reaches Roy's queue.

## Input
- `/applications/[job-id]/` — full application package
- D1 fact-check report

## Output
- PASS or BLOCK with specific reasons

## Checks
1. **Fact-check status**: Any D1 FLAGs that Roy has not explicitly approved = BLOCK
2. **EEOC compliance**: No language revealing protected characteristics (age, race, religion, disability, etc.)
3. **EU AI Act Art 50**: For EU-based employers, verify AI disclosure is included in cover letter
4. **No fabrication**: Cross-reference all claims against atomic-claims.md one final time
5. **Format standards**: Resume is ATS-parseable, cover letter under 350 words, all files properly formatted

## Constraints
- A single unresolved FLAG = BLOCK (no exceptions)
- EU employer detection based on company HQ location in job JSON
- Cannot self-approve; output goes to Roy's dashboard queue
  • Create D3-continuous-improvement.md in /agents/:
# D3: Continuous Improvement

## Role
You run weekly to analyze funnel data and propose filter changes and prompt improvements.

## Input
- `/weekly-reviews/` — previous week's metrics
- SQLite metrics export (response rates per bucket, per variant, per cover approach)

## Output
- `/weekly-reviews/[week]/recommendations.md`

## Analysis Dimensions
1. Response rate per bucket — which category is converting?
2. Response rate per variant — which resume emphasis works?
3. Response rate per cover approach — which voice/angle gets replies?
4. Filter effectiveness — are we filtering too aggressively or too loosely?
5. Time-to-response patterns — are certain days/times better for submissions?

## Process
1. Pull metrics from the weekly export
2. Compare against previous 4 weeks for trend detection
3. Identify statistically meaningful changes (not noise)
4. Propose specific changes with expected impact
5. Flag any recommendations that require Codex CLI validation before implementation

## Constraints
- Never implement changes autonomously; all recommendations require Roy's approval
- Recommendations must cite specific data points, not intuitions
- Codex CLI must validate recommendations before they are enacted

What makes Team D special?

D1 uses Codex CLI (GPT-5.x family) instead of Claude. This cross-model-family validation catches inflation and drift that a single model might miss. D2 is the compliance gate that prevents any flagged package from reaching Roy's queue. D3 closes the feedback loop by learning from real response data.

  • Save all six files and confirm they exist:
ls agents/

You should now see all 10 files: the 4 from Team A/B plus C1-resume-tailoring.md, C2-cover-letter-drafter.md, C3-qa-generator.md, D1-codex-fact-checker.md, D2-standards-gate.md, and D3-continuous-improvement.md.

Missing some files?

Double-check that each filename matches exactly (case-sensitive). The orchestration prompt references these filenames directly, so a typo here will break agent routing later.

help me verify all my agent files are named correctly

Create the Claude Project with agent orchestration

The orchestration prompt is the conductor that tells Claude which agents to run, in what order, and how to route outputs between them. You will store this as a markdown file in your vault and also set it as the project instructions in a new Claude Project.

  • Create orchestration.md in the /agents/ folder:
# Master Orchestration Prompt

## Role
You are the pipeline orchestrator for Roy's job application system. You coordinate all 12 agents per the dependency graph, manage parallel execution where possible, route outputs to correct vault paths, and surface blockers to Roy.

## Agent Dependency Graph
- **Setup (run once):** A1 → A2 (sequential)
- **Daily discovery:** B1 → B2 (sequential)
- **Per-job synthesis (parallel):** C1 + C2 + C3 (all read from same scored job)
- **Per-package quality (sequential):** D1 → D2
- **Weekly improvement:** D3 (runs independently on accumulated data)

## Execution Rules
1. Never run C-team agents until B2 has scored the job above the fit threshold
2. Run C1, C2, and C3 in parallel for each qualifying job
3. Never run D2 until D1 has completed its fact-check report
4. Surface any D1 FLAGs to Roy immediately; do not proceed to D2 until resolved
5. Log every agent execution to the activity log with timestamp and outcome

## File Routing
- A1 output → `/evidence/atomic-claims.md`
- A2 output → `/variants/[bucket].md`
- B1 output → `/intake/parsed-jobs/[job-id].json`
- B2 output → `/intake/scored-jobs/[job-id].json`
- C1 output → `/applications/[job-id]/resume-tailored.md`
- C2 output → `/applications/[job-id]/cover-letter.md`
- C3 output → `/applications/[job-id]/qa-prep.md`
- D1 output → `/applications/[job-id]/fact-check-report.md`
- D2 output → `/applications/[job-id]/standards-gate-result.md`
- D3 output → `/weekly-reviews/[week]/recommendations.md`

## Blocker Protocol
- If A1 flags unsupported claims: pause pipeline, notify Roy, wait for evidence or removal
- If D1 flags a claim: pause that package, notify Roy, wait for approval or fix
- If D2 blocks: package does not enter the queue; Roy must resolve and re-run D1+D2

## MCP Integration
- Read from vault via filesystem MCP for all agent inputs
- Write all outputs to vault via filesystem MCP
- Reference client-identity.yaml for bucket definitions and exclusions

What does the orchestration prompt do?

This prompt acts as the "main function" of your pipeline. When you start a chat in the Claude Project, Claude reads this orchestration prompt and knows exactly which agent to invoke, what files to read, and where to write the output. The dependency graph prevents agents from running out of order.

The blocker protocol ensures nothing gets sent to Roy's queue without passing both fact-checking and standards gates.

Now create the Claude Project that uses this orchestration prompt as its system instructions.

  • Open Claude Desktop.
  • Click Projects in the left sidebar.
  • Click + New Project in the upper right corner.
  • Name the project Roy Pipeline Orchestrator.
  • Click Set project instructions.
  • Copy the entire contents of agents/orchestration.md and paste it into the project instructions field.
  • Click Save instructions.

Why paste the orchestration prompt as project instructions?

Project instructions load automatically at the start of every conversation. This means every chat you open in this project already knows the full agent execution plan, the file routing, and the blocker protocol. You never need to re-explain the pipeline structure.

  • Upload knowledge files to the project. On the right side of the project's main page, click the + button to add content.
  • Upload client-identity.yaml from /clients/roy/.
  • Upload agent-dependencies.md from /architecture/.
  • Create a placeholder file called master-resume.md with Roy's resume content (or a placeholder noting it will be populated in Step 6), and upload it.

Now verify that Claude can access the vault through the filesystem MCP and execute agents in the correct order.

  • Start a new chat inside the Roy Pipeline Orchestrator project.
  • Type a test message asking Claude to confirm it can read files from the vault path. For example: "Can you read the contents of /agents/A1-resume-anatomist.md via MCP?"
  • Verify that Claude returns the file contents successfully.
  • Ask Claude to describe the agent execution order for processing a batch of job URLs. It should reference the dependency graph: B1 then B2 (sequential), then C1+C2+C3 (parallel), then D1 then D2 (sequential).

Claude cannot read vault files?

Confirm that the filesystem MCP server is configured in Claude Desktop settings with the correct vault directory path. The MCP configuration was set up in Step 1. If the path has changed, update it in Claude Desktop's settings JSON.

help me debug why Claude cannot access my vault files through MCP

Your 12-agent library is complete and wired into a Claude Project. The orchestration prompt knows exactly which agents run when, what they read, and where they write. Next up, you will feed Roy's master resume through Agent A1 and generate the atomic claims that power the entire pipeline.

Ingest Resume and Generate Variants

Your 12 agents are written and the orchestration prompt knows how to coordinate them. The pipeline has a brain. Now it needs fuel.

The pipeline is only as good as the source material it starts from. In this step, you will feed Roy's master resume into Agent A1, decompose every experience bullet into atomic claims with evidence citations, generate four category-specific resume variants, and then run Codex CLI as an adversarial fact-checker from a different model family to validate everything.

In this step, get ready to:
  • Extract atomic claims with evidence citations from Roy's master resume.
  • Generate 4 category-specific resume variants (one per bucket).
  • Validate all claims with Codex CLI cross-model fact-checking.
Extract atomic claims with evidence

Every bullet on Roy's resume makes a claim. "Led migration of 200+ microservices to Kubernetes" is a claim that needs evidence (an offer letter confirming the title, a GitHub commit history showing the work). Agent A1 decomposes the entire resume into these atomic claims and flags anything that lacks supporting evidence.

Before running A1, Roy's master resume needs to be in the vault where Claude can access it via filesystem MCP.

  • In Obsidian, create a new file at /clients/roy/master-resume.md.
  • Paste Roy's full master resume into this file and save it.

What goes in the master resume?

This is the unfiltered, complete version of Roy's resume. Every role, every bullet, every certification. The variants will be subsets of this. Nothing should be left out here because Agent A1 needs the full picture to decompose claims accurately.

  • Switch to the Claude Project you created in the previous step.
  • Type the following message to run Agent A1 (Resume Anatomist):
Run Agent A1 (Resume Anatomist).

Read Roy's master resume from /clients/roy/master-resume.md.

For every experience bullet, decompose it into an atomic claim with:
1. The claim text (exactly as stated)
2. Evidence citation required (offer letter, GitHub commit, project doc, performance review, etc.)
3. Support status: SUPPORTED (if evidence is clearly available) or FLAGGED-NEEDS-EVIDENCE (if Roy needs to supply proof)

Save the complete output to /evidence/atomic-claims.md.

At the end, list all FLAGGED claims separately so Roy knows exactly what to address.

What does this prompt do?

Claude reads the master resume via filesystem MCP, applies the A1 agent prompt from the project knowledge, and writes a structured markdown file to the vault. Each claim gets tagged with what kind of evidence would prove it.

The FLAGGED-NEEDS-EVIDENCE items are critical. These are claims Roy cannot prove. He either needs to find the evidence or remove the claim entirely. The pipeline will never send an unsupported claim to an employer.

  • Wait for Claude to finish writing the file.
  • In Obsidian, open /evidence/atomic-claims.md and confirm it contains decomposed claims with evidence citations.
  • Review the FLAGGED-NEEDS-EVIDENCE section at the bottom. Note which claims Roy needs to supply proof for.

Claude didn't write the file?

Check that filesystem MCP is configured correctly in Claude Desktop settings. The vault path must match exactly. If Claude says it cannot access the path, restart Claude Desktop after updating the MCP config.

If Claude produced the output in the chat window instead of writing to the vault, add "Use your filesystem MCP tool to write directly to the vault" to the end of your prompt and try again.

Why isn't Claude writing files to my vault via MCP?

Generate 4 resume variants

Agent A2 takes the atomic claims and Roy's bucket definitions from client-identity.yaml to create four distinct variants. Each variant reorders and emphasizes claims differently for its target audience. Nothing is fabricated.

  • In the same Claude Project conversation, type the following message to run Agent A2 (Variant Curator):
Run Agent A2 (Variant Curator).

Read /evidence/atomic-claims.md and /clients/roy/client-identity.yaml.

Generate 4 resume variants:
1. /variants/ai-leaders.md - Emphasize ML infrastructure, model serving, AI platform work. Target: Anthropic, Palantir, OpenAI.
2. /variants/big-tech.md - Emphasize scale, distributed systems, cloud architecture. Target: Google, AWS, Microsoft.
3. /variants/government.md - Emphasize compliance, secure infrastructure, FedRAMP-adjacent work. Target: Government contractors.
4. /variants/other.md - Generalist baseline with balanced emphasis.

Rules:
- Preserve Roy's voice and resume structure.
- Reorder and emphasize only. Never fabricate claims.
- Only use claims marked SUPPORTED in atomic-claims.md.
- Each variant must include a header noting which bucket it targets.

Why 4 variants instead of one tailored resume?

Each bucket has a different hiring bar. AI Leaders care about ML systems experience. Government contractors need compliance and security clearance signals. A single resume cannot emphasize everything simultaneously without becoming generic.

The variants share the same underlying claims. They differ in ordering, which bullets are prominent, and which are condensed. Later, Agent C1 will do per-job tailoring on top of the correct variant.

  • Wait for Claude to write all 4 files.
  • In Obsidian, open the /variants/ folder and confirm you see ai-leaders.md, big-tech.md, government.md, and other.md.

Now each variant needs to pass the 3-lens Professional Presence Audit before it is finalized. This ensures no variant has a blind spot that would get it rejected at screening.

  • In the same Claude Project conversation, type the following message to evaluate all variants:
Evaluate all 4 resume variants against the 3-lens Professional Presence Audit.

For each variant (/variants/ai-leaders.md, /variants/big-tech.md, /variants/government.md, /variants/other.md):

1. Recruiter lens: Will ATS keyword-matching surface this variant for its target bucket roles? Does it pass a 6-second scan?
2. Hiring Manager lens: Does it demonstrate increasing scope? Are achievements framed as business outcomes?
3. Lead Engineer lens: Does it show system design fluency at scale? Are technical choices specific and opinionated?

Score each variant per lens (1-10). If any lens scores below 7, rewrite that section and re-evaluate.

Once all variants pass (7+ on all 3 lenses), append "Evaluated against 3-lens audit: PASS" to each file header.

What does the 3-lens evaluation catch?

A variant might emphasize the right technical skills but frame them as tasks instead of outcomes. That passes the Lead Engineer lens but fails the Hiring Manager lens. The evaluation catches these gaps before any variant enters the submission pipeline.

If Claude rewrites sections to bring a score above 7, it updates the variant file in place via MCP. You can review the changes in Obsidian's file history.

  • Wait for Claude to complete the evaluation and any rewrites.
  • Open each variant file in Obsidian and confirm it includes the "Evaluated against 3-lens audit: PASS" header line.

A variant keeps failing one lens?

If a variant cannot score 7+ on all lenses after rewriting, the issue is likely missing source material. Check /evidence/atomic-claims.md for claims relevant to that lens. If there are none, Roy needs to gain that experience or shift bucket strategy.

Help me improve my resume variant's audit scores

Validate claims with Codex CLI

Claude generated the claims and the variants. Now a different model family checks the work. Codex CLI (powered by GPT-5.x) independently reads the atomic claims file and verifies that each claim is supported by the cited evidence. This cross-model-family validation catches hallucinations, over-generalizations, and subtle inflation that a single model might miss.

  • Open PowerShell.
  • Launch Codex CLI pointed at your vault directory by running this command:
codex --cd [[VAULT_PATH="C:\Users\Roy\Documents\pipeline-vault"]]

What does --cd do?

The --cd flag sets the working directory for the Codex agent before it starts processing your request. This gives Codex read access to all vault files without needing to navigate there first.

  • Once the Codex TUI opens, type the following instruction:
Read /evidence/atomic-claims.md. For each claim marked SUPPORTED, independently verify:
1. Is the evidence citation specific enough to be verifiable? (Not vague like "various projects")
2. Does the claim text match what the cited evidence would actually prove?
3. Are there any scope or metric inflations (e.g., "200+" when evidence suggests fewer)?

Produce a fact-check report with PASS or FLAG per claim. For each FLAG, explain exactly what is unsupported or inflated.

Save the report to /evidence/codex-claims-validation.md.

Why a different model family?

Claude generated the atomic claims. If Claude also validates them, it is checking its own work. Codex CLI runs on OpenAI's GPT-5.x family, which means it approaches the text with different training biases and reasoning patterns. A claim that Claude considers well-supported might get flagged by Codex for vague evidence citations.

The key constraint: Claude cannot override a Codex FLAG. Only Roy can approve sending a package that Codex has flagged.

  • Wait for Codex to finish writing the validation report.
  • In Obsidian, open /evidence/codex-claims-validation.md and review the PASS/FLAG results.
  • For any claims marked FLAG, decide whether to supply evidence or remove the claim from the master resume.

Codex can't read the vault files?

If Codex reports it cannot access the file path, confirm the --cd path you provided matches your vault's actual location on disk. The TUI header shows the active working directory.

If the vault folder is not version-controlled with Git, Codex may start in read-only mode. When prompted, allow workspace write access so Codex can save the report file.

Why can't Codex CLI read my vault files?

Roy's source of truth is now established. The master resume is decomposed into verifiable claims, four variants are tuned per bucket and audit-validated, and a second model family has independently checked the work. Next, you will build the command-center dashboard where Roy operates daily.

Build the Command-Center Dashboard

The pipeline can discover, score, and synthesize application packages. But without a place to see what the system is doing, Roy is flying blind.

In this step, you will build the operational dashboard Roy works out of daily. This is not a metrics page. It is a command center where Roy reviews packages, approves submissions, inspects fit breakdowns, and watches the pipeline improve itself over time.

In this step, get ready to:
  • Scaffold a Next.js project with TypeScript, Tailwind CSS, and Recharts.
  • Create a SQLite database with tables for applications, claims, variants, metrics, and activity tracking.
  • Build the Home, Queue, and Job Detail views that Roy uses daily.
Scaffold the Next.js project with Recharts

The dashboard needs a fast, modern frontend framework that supports server-side rendering and file-based routing. Next.js with the App Router gives you both, plus built-in TypeScript support.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create the dashboard project by running this command:
npx create-next-app@latest dashboard --yes

What does this command do?

The --yes flag tells create-next-app to skip all interactive prompts and use the recommended defaults: TypeScript, ESLint, Tailwind CSS, App Router, and Turbopack.

This gives you exactly the stack the dashboard needs without answering individual prompts.

  • Move into the new project folder by running:
cd dashboard
  • Install Recharts (for data visualization) and better-sqlite3 (for the local database) by running:
npm install recharts better-sqlite3
  • Confirm the packages installed by running:
npm list recharts better-sqlite3
  • You should see version numbers listed for both recharts and better-sqlite3 in the output.

Seeing install errors for better-sqlite3?

This package includes native C++ bindings that need to compile. Make sure you have the Windows Build Tools installed. If not, run npm install --global windows-build-tools in an administrator PowerShell, then retry the install.

help me fix native module build errors on Windows

Now create the 7-view page structure that maps to the dashboard's operational sections.

  • Create the page directories for all 7 views by running these commands:
mkdir -p app/queue app/job app/funnel app/variants app/weekly-review app/profile-evolution

Why 7 views?

Each view answers a different question Roy asks daily. Home shows the bucket overview and activity feed. Queue shows packages ready for approval. Job Detail breaks down fit scoring. Funnel tracks conversion. Variants compares resume versions. Weekly Review shows improvement trends. Profile Evolution tracks audit scores over time.

  • Create the db directory for database files by running:
mkdir db
  • Confirm your project structure by running:
ls app
  • You should see folders for each view: funnel, job, profile-evolution, queue, variants, and weekly-review listed alongside the default page.tsx and layout.tsx.
Create the SQLite schema and seed data

The dashboard reads all pipeline data from a local SQLite database. This keeps things simple: no Docker, no cloud database, and the file survives machine reboots.

  • Create the schema file by creating db/schema.sql inside your dashboard folder and pasting this SQL:
-- Applications table: tracks every job application through the pipeline
CREATE TABLE IF NOT EXISTS applications (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  job_id TEXT NOT NULL,
  bucket TEXT NOT NULL CHECK(bucket IN ('ai-leaders', 'big-tech', 'government', 'other')),
  variant_used TEXT,
  cover_approach TEXT,
  submitted_at TEXT,
  response_type TEXT,
  response_at TEXT
);

-- Claims table: atomic resume claims with evidence traceability
CREATE TABLE IF NOT EXISTS claims (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  text TEXT NOT NULL,
  evidence_citation TEXT,
  support_status TEXT NOT NULL CHECK(support_status IN ('SUPPORTED', 'FLAGGED-NEEDS-EVIDENCE'))
);

-- Variants table: resume versions per bucket
CREATE TABLE IF NOT EXISTS variants (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  bucket TEXT NOT NULL,
  version INTEGER NOT NULL DEFAULT 1,
  content TEXT NOT NULL,
  is_baseline INTEGER NOT NULL DEFAULT 0
);

-- Weekly metrics: funnel data per bucket per week
CREATE TABLE IF NOT EXISTS weekly_metrics (
  week TEXT NOT NULL,
  bucket TEXT NOT NULL,
  applied INTEGER DEFAULT 0,
  responded INTEGER DEFAULT 0,
  phone_screen INTEGER DEFAULT 0,
  onsite INTEGER DEFAULT 0,
  offer INTEGER DEFAULT 0,
  codex_pass_rate REAL DEFAULT 0.0,
  standards_pass_rate REAL DEFAULT 0.0,
  avg_time_per_package REAL DEFAULT 0.0,
  PRIMARY KEY (week, bucket)
);

-- Cover approaches: tracks which voice/angle gets replies
CREATE TABLE IF NOT EXISTS cover_approaches (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  approach_name TEXT NOT NULL,
  bucket TEXT NOT NULL,
  response_rate REAL DEFAULT 0.0
);

-- Profile updates: tracks every change to resume, LinkedIn, or GitHub
CREATE TABLE IF NOT EXISTS profile_updates (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  date TEXT NOT NULL,
  surface TEXT NOT NULL CHECK(surface IN ('resume', 'linkedin', 'github')),
  change_description TEXT NOT NULL,
  reason TEXT,
  bucket_impact TEXT
);

-- Throughput benchmarks: measures pipeline speed per run
CREATE TABLE IF NOT EXISTS throughput_benchmarks (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  date TEXT NOT NULL,
  urls_input INTEGER NOT NULL,
  packages_output INTEGER NOT NULL,
  total_time_min REAL NOT NULL,
  claude_usage_notes TEXT
);

-- Activity log: real-time feed of every system action
CREATE TABLE IF NOT EXISTS activity_log (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  timestamp TEXT NOT NULL DEFAULT (datetime('now')),
  agent TEXT NOT NULL,
  action TEXT NOT NULL,
  job_id TEXT,
  details TEXT
);

What does this schema do?

Each table serves a specific dashboard view. The applications table feeds the Funnel view with conversion data per bucket. The activity_log powers the real-time feed on the Home page so Roy can see what the system did without digging through files.

The weekly_metrics table stores response rate trends. The claims table connects back to the atomic claims from your vault so the dashboard can show which claims pass fact-checking.

  • Create the seed data file at db/seed.sql with initial records from your Step 6 outputs:
-- Seed variants (one per bucket, from Step 6)
INSERT INTO variants (bucket, version, content, is_baseline) VALUES
  ('ai-leaders', 1, 'AI Leaders variant content from /variants/ai-leaders.md', 1),
  ('big-tech', 1, 'Big Tech variant content from /variants/big-tech.md', 1),
  ('government', 1, 'Government variant content from /variants/government.md', 1),
  ('other', 1, 'Other/generalist variant content from /variants/other.md', 1);

-- Seed sample claims from atomic-claims.md
INSERT INTO claims (text, evidence_citation, support_status) VALUES
  ('Led migration of 200+ microservices to Kubernetes', 'Offer letter from CompanyX confirming title + GitHub commit history', 'SUPPORTED'),
  ('Reduced infrastructure costs by 40% through auto-scaling', 'Internal cost dashboard screenshot + quarterly review', 'SUPPORTED'),
  ('Managed team of 8 engineers across 3 time zones', 'Pending evidence from Roy', 'FLAGGED-NEEDS-EVIDENCE');

-- Seed baseline weekly metrics (empty week 1)
INSERT INTO weekly_metrics (week, bucket, applied, responded, phone_screen, onsite, offer) VALUES
  ('2026-W23', 'ai-leaders', 0, 0, 0, 0, 0),
  ('2026-W23', 'big-tech', 0, 0, 0, 0, 0),
  ('2026-W23', 'government', 0, 0, 0, 0, 0),
  ('2026-W23', 'other', 0, 0, 0, 0, 0);

-- Seed activity log with system initialization
INSERT INTO activity_log (timestamp, agent, action, job_id, details) VALUES
  (datetime('now'), 'A1', 'ingest_complete', NULL, 'Master resume decomposed into 47 atomic claims'),
  (datetime('now'), 'A2', 'variants_generated', NULL, '4 bucket variants created and audit-validated'),
  (datetime('now'), 'D1', 'fact_check_complete', NULL, 'Codex validated 44/47 claims. 3 flagged for evidence.');

What does the seed data do?

The seed file loads the baseline state from your Step 6 work into the database. The 4 variant records reference your vault files. The sample claims show the dashboard how fact-check status looks. The activity log entries give the Home feed something to display on first load.

Replace the placeholder content strings with your actual variant text once the dashboard is running.

Now initialize the database using a Node.js script that runs both SQL files.

  • Create db/init.js with this initialization script:
// db/init.js - Initialize SQLite database from schema and seed files
const Database = require('better-sqlite3');
const fs = require('fs');
const path = require('path');

// Create or open the database file
const db = new Database(path.join(__dirname, 'pipeline.db'));

// Enable WAL mode for better performance
db.pragma('journal_mode = WAL');

// Read and execute the schema
const schema = fs.readFileSync(path.join(__dirname, 'schema.sql'), 'utf-8');
db.exec(schema);
console.log('Schema created successfully.');

// Read and execute seed data
const seed = fs.readFileSync(path.join(__dirname, 'seed.sql'), 'utf-8');
db.exec(seed);
console.log('Seed data loaded successfully.');

// Verify by counting rows in each table
const tables = ['applications', 'claims', 'variants', 'weekly_metrics', 'activity_log'];
tables.forEach(table => {
  const count = db.prepare(`SELECT COUNT(*) as count FROM ${table}`).get();
  console.log(`  ${table}: ${count.count} rows`);
});

db.close();
console.log('Database initialized at db/pipeline.db');
  • Run the initialization script:
node db/init.js
  • You should see output confirming the schema was created and seed data loaded, with row counts for each table.

Seeing "Cannot find module better-sqlite3"?

Make sure you are running the command from inside the dashboard folder where you ran npm install. The node_modules folder must be in the same directory as your db folder.

help me fix module not found errors when running my db init script

Build the Home, Queue, and Job Detail views

These three views are where Roy spends 90% of his dashboard time. Home gives the big picture. Queue is where he approves packages. Job Detail is where he inspects individual opportunities.

You will use Claude Desktop (via filesystem MCP) to generate the view components. Claude can read the schema and seed data you just created, then produce React components that query the database and render the dashboard.

  • In your Claude Desktop Project, paste the following prompt to generate the Home view:
Read the file at dashboard/db/schema.sql and dashboard/db/seed.sql.

Generate a Next.js App Router page at dashboard/app/page.tsx (the Home view) that:
1. Shows 4 bucket cards (AI Leaders, Big Tech, Government, Other) each with a sparkline chart showing weekly application trends from the weekly_metrics table
2. Displays total pipeline stats (total applications, total responses, avg response rate)
3. Includes a real-time activity feed that reads the 10 most recent entries from activity_log and displays them as timestamped cards (e.g. "B2 scored 3 new jobs", "D1 fact-checked package #47")

Use Tailwind CSS for styling. Use Recharts for the sparkline charts.
Create a shared database utility at dashboard/lib/db.ts that exports a singleton better-sqlite3 connection.
The page should use a dark theme (gray-900 background, white text) to match a professional command-center aesthetic.

Why use Claude to generate the views?

The dashboard components follow standard patterns (read from SQLite, render with Recharts, style with Tailwind). Claude can generate production-quality code faster than hand-typing each view because it has access to your schema and understands the relationships between tables.

You review and modify every file Claude produces. Claude writes the boilerplate. You make the architectural decisions.

  • Review the generated lib/db.ts file. Confirm it creates a singleton database connection using better-sqlite3 pointing at db/pipeline.db.
  • Review the generated app/page.tsx file. Confirm it imports from recharts and queries the weekly_metrics and activity_log tables.
  • Now generate the Queue view by pasting this prompt in Claude Desktop:
Generate a Next.js App Router page at dashboard/app/queue/page.tsx (the Queue view) that:
1. Shows cards for each application in the 'applications' table that has a variant_used but no submitted_at date (ready for review)
2. Each card shows an inline preview of the resume variant text (from the variants table) and the cover letter approach (from cover_approaches)
3. Each card has an "Approve" button that sets submitted_at to the current timestamp
4. Use the shared lib/db.ts connection
5. Style with Tailwind CSS, dark theme matching the Home view
  • Finally, generate the Job Detail view by pasting this prompt:
Generate a Next.js App Router page at dashboard/app/job/[id]/page.tsx (the Job Detail view) that:
1. Takes a job ID as a dynamic route parameter
2. Shows a full fit-score breakdown: skills match, experience match, seniority match (read from the applications table and cross-reference with claims)
3. Shows a gap analysis section listing claims that are FLAGGED-NEEDS-EVIDENCE for this job's bucket
4. Shows the recommended variant for this bucket
5. Displays the complete application package (variant text + cover letter text side by side)
6. Use the shared lib/db.ts connection
7. Style with Tailwind CSS, dark theme matching Home and Queue views
  • After Claude generates all three views, start the development server by running:
npm run dev
  • Open your browser and navigate to http://localhost:3000.
  • You should see the Home view with 4 bucket cards, pipeline stats, and the activity feed showing the seed data entries.
  • Navigate to http://localhost:3000/queue to verify the Queue view loads (it will be empty until you process jobs in Step 9).

Dashboard not loading on localhost:3000?

Check your terminal for compilation errors. The most common issue is a missing import or a TypeScript type error in the generated code. Read the error message carefully and fix the specific line Claude generated.

If you see a database error, confirm db/pipeline.db exists by running ls db/ in your terminal.

help me fix compilation errors in my Next.js dashboard

Your command center is live. Roy now has a place to see everything the pipeline does at a glance. Next up, you will wire Gmail API response tracking and build the remaining 4 views so the dashboard captures the full application lifecycle.

Wire Gmail API and Response Tracking

The pipeline can discover jobs, score them, and synthesize application packages. But without response tracking, Roy is flying blind.

He cannot know which buckets convert, which variants get replies, or which cover approaches resonate. In this step, you will wire the Gmail API to automatically detect application responses and feed that signal back into the dashboard.

In this step, get ready to:
  • Set up Gmail API OAuth credentials for personal use with read-only scope.
  • Build a response scanner that classifies recruiter replies, interview invitations, and rejection notices.
  • Connect the Funnel, Variants, and Weekly Review views to live SQLite data.
Set up Gmail API OAuth

The Gmail API lets your scanner script read Roy's inbox programmatically. Google requires an OAuth consent flow so the user explicitly grants read-only access.

  • Open your browser and navigate to the Google Cloud Console at https://console.cloud.google.com.
  • Sign in with the Google account whose Gmail you want to scan for application responses.
  • In the top navigation bar, click the project dropdown (next to the Google Cloud logo).
  • Click New Project.
  • Enter job-pipeline-scanner as the project name.
  • Leave Location set to No organization (this is normal for personal Gmail accounts).
  • Click Create.

Why a dedicated Google Cloud project?

A Google Cloud project is a container for API credentials and usage tracking. Creating a separate project for the scanner keeps your OAuth credentials isolated from any other Google Cloud work you might have.

  • Once the project is created, make sure it is selected in the top navigation dropdown.
  • Click the hamburger menu (three horizontal lines) in the top left.
  • Navigate to APIs & Services, then Library.
  • Search for Gmail API in the search box.
  • Click Gmail API in the results.
  • Click Enable.

Now configure the OAuth consent screen so Google knows how your app will use Gmail data.

  • In the left sidebar, navigate to Google Auth platform, then Branding.
  • If you see a message saying the Google Auth platform is not configured yet, click Get Started.
  • Under App Information, enter Job Pipeline Scanner as the App name.
  • Select your email address for the User support email field.
  • Click Next.
  • Under Audience, select External as the user type.
  • Click Next.
  • Enter your email address under Contact Information.
  • Click Next.
  • Review the Google API Services User Data Policy and check the agreement box.
  • Click Continue, then Create.
  • After the consent screen is created, click Audience in the sidebar.
  • Under Test users, click Add users.
  • Enter your own Gmail address and click Save.

Why add yourself as a test user?

While the app is in testing mode, only users listed as test users can authorize the OAuth flow. Since this scanner is for personal use only, adding your own email is all you need.

Now create the OAuth client credentials that your script will use to authenticate.

  • In the left sidebar, navigate to Google Auth platform, then Clients.
  • Click Create Client.
  • Select Desktop app as the Application type.
  • Enter Pipeline Scanner as the Name.
  • Click Create.
  • A dialog appears showing your new client ID. Click the download button to save the JSON file.
  • Rename the downloaded file to credentials.json and move it into your dashboard/scripts/ folder.

Keep credentials.json private

This file contains your OAuth client secret. Never commit it to Git. Add credentials.json and token.json to your .gitignore file.

Not seeing the download option?

If the credential dialog closed before you could download, return to the Clients page. Click on the client name you just created. Use the download icon in the top bar to get credentials.json.

Help me find the credentials download in Google Cloud Console.

Build the response scanner

The scanner script polls Roy's Gmail inbox for application-related emails. It classifies each match as a recruiter reply, interview invitation, rejection notice, or LinkedIn notification, then writes the result to the SQLite database.

  • Install the Gmail API client library and local auth helper by running this command from your dashboard/ folder:
npm install googleapis @google-cloud/local-auth --save

What do these packages do?

The googleapis package is the official Google API client for Node.js. It handles constructing Gmail API requests and parsing responses.

The @google-cloud/local-auth package manages the OAuth flow by spinning up a temporary local server to receive the authorization callback from Google.

  • Create the scanner script file by running:
mkdir -p scripts
touch scripts/gmail-scanner.js
  • Open scripts/gmail-scanner.js in your editor and add the imports, constants, and authentication logic:
const fs = require('fs').promises;
const path = require('path');
const { authenticate } = require('@google-cloud/local-auth');
const { google } = require('googleapis');
const Database = require('better-sqlite3');

// Read-only access to Gmail messages
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
const TOKEN_PATH = path.join(__dirname, 'token.json');
const CREDENTIALS_PATH = path.join(__dirname, 'credentials.json');
const DB_PATH = path.join(__dirname, '..', 'db', 'pipeline.db');

/**
 * Load previously saved credentials from token.json.
 * Returns null if no saved credentials exist.
 */
async function loadSavedCredentials() {
  try {
    const content = await fs.readFile(TOKEN_PATH);
    const credentials = JSON.parse(content);
    return google.auth.fromJSON(credentials);
  } catch (err) {
    return null;
  }
}

/**
 * Save OAuth credentials to token.json for future runs.
 */
async function saveCredentials(client) {
  const content = await fs.readFile(CREDENTIALS_PATH);
  const keys = JSON.parse(content);
  const key = keys.installed || keys.web;
  const payload = JSON.stringify({
    type: 'authorized_user',
    client_id: key.client_id,
    client_secret: key.client_secret,
    refresh_token: client.credentials.refresh_token,
  });
  await fs.writeFile(TOKEN_PATH, payload);
}

/**
 * Authenticate with Gmail API using saved or new credentials.
 */
async function authorize() {
  let client = await loadSavedCredentials();
  if (client) {
    return client;
  }
  client = await authenticate({
    scopes: SCOPES,
    keyfilePath: CREDENTIALS_PATH,
  });
  if (client.credentials) {
    await saveCredentials(client);
  }
  return client;
}

What does this code do?

  • SCOPES restricts the script to read-only Gmail access using the gmail.readonly scope.
  • loadSavedCredentials checks if a previous authorization token exists so Roy does not have to re-authorize every run.
  • authorize either reuses saved credentials or triggers the OAuth consent flow in the browser.
  • saveCredentials persists the refresh token to token.json for future sessions.
  • Below the authentication code, add the email classification and scanning logic:
/**
 * Classify an email based on sender, subject, and body content.
 * Returns: 'recruiter_reply' | 'interview_invite' | 'rejection' | 'linkedin_notification' | null
 */
function classifyEmail(headers, body) {
  const from = headers.find(h => h.name === 'From')?.value || '';
  const subject = headers.find(h => h.name === 'Subject')?.value || '';
  const text = `${subject} ${body}`.toLowerCase();

  // LinkedIn notification emails
  if (from.includes('linkedin.com') || from.includes('messaging-digest')) {
    return 'linkedin_notification';
  }

  // Rejection patterns
  if (text.includes('unfortunately') || text.includes('decided not to move forward') ||
      text.includes('other candidates') || text.includes('not selected')) {
    return 'rejection';
  }

  // Interview invitation patterns
  if (text.includes('interview') || text.includes('schedule a call') ||
      text.includes('phone screen') || text.includes('next steps in the process')) {
    return 'interview_invite';
  }

  // Recruiter reply patterns
  if (text.includes('your application') || text.includes('your resume') ||
      text.includes('regarding your') || text.includes('we received')) {
    return 'recruiter_reply';
  }

  return null;
}

/**
 * Fetch recent emails matching application-related patterns.
 * Classify each and update the database.
 */
async function scanResponses(auth) {
  const gmail = google.gmail({ version: 'v1', auth });
  const db = new Database(DB_PATH);

  // Search for emails from the last 7 days that match response patterns
  const query = 'newer_than:7d (from:linkedin.com OR subject:application OR subject:interview OR subject:unfortunately OR subject:"next steps")';

  const res = await gmail.users.messages.list({
    userId: 'me',
    q: query,
    maxResults: 50,
  });

  const messages = res.data.messages || [];
  console.log(`Found ${messages.length} potential response emails.`);

  for (const msg of messages) {
    // Get the full message content
    const full = await gmail.users.messages.get({
      userId: 'me',
      id: msg.id,
      format: 'full',
    });

    const headers = full.data.payload.headers;
    const body = full.data.snippet || '';
    const responseType = classifyEmail(headers, body);

    if (!responseType) continue;

    const from = headers.find(h => h.name === 'From')?.value || '';
    const subject = headers.find(h => h.name === 'Subject')?.value || '';
    const date = headers.find(h => h.name === 'Date')?.value || '';

    // Check if we already processed this message
    const existing = db.prepare(
      'SELECT id FROM activity_log WHERE details LIKE ?'
    ).get(`%${msg.id}%`);

    if (existing) continue;

    // Update the applications table if we can match by sender
    const updateStmt = db.prepare(`
      UPDATE applications
      SET response_type = ?, response_at = ?
      WHERE response_type IS NULL
      AND id = (
        SELECT id FROM applications
        WHERE response_type IS NULL
        ORDER BY submitted_at DESC LIMIT 1
      )
    `);
    updateStmt.run(responseType, new Date(date).toISOString());

    // Log to activity_log for the dashboard feed
    const logStmt = db.prepare(`
      INSERT INTO activity_log (timestamp, agent, action, details)
      VALUES (?, ?, ?, ?)
    `);
    logStmt.run(
      new Date().toISOString(),
      'gmail-scanner',
      `detected_${responseType}`,
      JSON.stringify({ gmail_id: msg.id, from, subject })
    );

    console.log(`Classified: ${responseType} | From: ${from} | Subject: ${subject}`);
  }

  db.close();
  console.log('Scan complete.');
}

How does classification work?

The classifyEmail function checks email headers and body text against keyword patterns. LinkedIn notifications are caught by sender domain. Rejections, interview invitations, and recruiter replies each have distinct language patterns.

Every detection is logged to the activity_log table with the Gmail message ID, sender, and subject. This powers the real-time activity feed on the dashboard Home view.

  • Finally, add the main execution block at the bottom of the file:
/**
 * Main entry point: authorize and scan.
 */
async function main() {
  console.log('Starting Gmail response scan...');
  const auth = await authorize();
  await scanResponses(auth);
}

main().catch(console.error);

What does the main function do?

It calls authorize to get a valid OAuth client, then passes it to scanResponses. On the first run, a browser window opens for Google sign-in. Subsequent runs reuse the saved token.

  • Save scripts/gmail-scanner.js.
  • Run the scanner for the first time to trigger the OAuth flow:
node scripts/gmail-scanner.js

A browser window opens asking you to sign in and authorize read-only access to your Gmail. After you click Allow, the script scans your recent emails and prints any matches to the console.

Seeing a redirect error?

Make sure credentials.json is in the dashboard/scripts/ folder (the same directory as the scanner script). The @google-cloud/local-auth package needs to read it from the path specified in CREDENTIALS_PATH.

Help me debug the Gmail OAuth redirect error in my scanner script.

✔️ Awesome, I've got everything!

Great. Double check you have saved gmail-scanner.js and that token.json was created in your scripts/ folder after the first successful authorization.

ⓧ I'd like to double check the full code

const fs = require('fs').promises;
const path = require('path');
const { authenticate } = require('@google-cloud/local-auth');
const { google } = require('googleapis');
const Database = require('better-sqlite3');

// Read-only access to Gmail messages
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
const TOKEN_PATH = path.join(__dirname, 'token.json');
const CREDENTIALS_PATH = path.join(__dirname, 'credentials.json');
const DB_PATH = path.join(__dirname, '..', 'db', 'pipeline.db');

/**
 * Load previously saved credentials from token.json.
 * Returns null if no saved credentials exist.
 */
async function loadSavedCredentials() {
  try {
    const content = await fs.readFile(TOKEN_PATH);
    const credentials = JSON.parse(content);
    return google.auth.fromJSON(credentials);
  } catch (err) {
    return null;
  }
}

/**
 * Save OAuth credentials to token.json for future runs.
 */
async function saveCredentials(client) {
  const content = await fs.readFile(CREDENTIALS_PATH);
  const keys = JSON.parse(content);
  const key = keys.installed || keys.web;
  const payload = JSON.stringify({
    type: 'authorized_user',
    client_id: key.client_id,
    client_secret: key.client_secret,
    refresh_token: client.credentials.refresh_token,
  });
  await fs.writeFile(TOKEN_PATH, payload);
}

/**
 * Authenticate with Gmail API using saved or new credentials.
 */
async function authorize() {
  let client = await loadSavedCredentials();
  if (client) {
    return client;
  }
  client = await authenticate({
    scopes: SCOPES,
    keyfilePath: CREDENTIALS_PATH,
  });
  if (client.credentials) {
    await saveCredentials(client);
  }
  return client;
}

/**
 * Classify an email based on sender, subject, and body content.
 * Returns: 'recruiter_reply' | 'interview_invite' | 'rejection' | 'linkedin_notification' | null
 */
function classifyEmail(headers, body) {
  const from = headers.find(h => h.name === 'From')?.value || '';
  const subject = headers.find(h => h.name === 'Subject')?.value || '';
  const text = `${subject} ${body}`.toLowerCase();

  // LinkedIn notification emails
  if (from.includes('linkedin.com') || from.includes('messaging-digest')) {
    return 'linkedin_notification';
  }

  // Rejection patterns
  if (text.includes('unfortunately') || text.includes('decided not to move forward') ||
      text.includes('other candidates') || text.includes('not selected')) {
    return 'rejection';
  }

  // Interview invitation patterns
  if (text.includes('interview') || text.includes('schedule a call') ||
      text.includes('phone screen') || text.includes('next steps in the process')) {
    return 'interview_invite';
  }

  // Recruiter reply patterns
  if (text.includes('your application') || text.includes('your resume') ||
      text.includes('regarding your') || text.includes('we received')) {
    return 'recruiter_reply';
  }

  return null;
}

/**
 * Fetch recent emails matching application-related patterns.
 * Classify each and update the database.
 */
async function scanResponses(auth) {
  const gmail = google.gmail({ version: 'v1', auth });
  const db = new Database(DB_PATH);

  // Search for emails from the last 7 days that match response patterns
  const query = 'newer_than:7d (from:linkedin.com OR subject:application OR subject:interview OR subject:unfortunately OR subject:"next steps")';

  const res = await gmail.users.messages.list({
    userId: 'me',
    q: query,
    maxResults: 50,
  });

  const messages = res.data.messages || [];
  console.log(`Found ${messages.length} potential response emails.`);

  for (const msg of messages) {
    // Get the full message content
    const full = await gmail.users.messages.get({
      userId: 'me',
      id: msg.id,
      format: 'full',
    });

    const headers = full.data.payload.headers;
    const body = full.data.snippet || '';
    const responseType = classifyEmail(headers, body);

    if (!responseType) continue;

    const from = headers.find(h => h.name === 'From')?.value || '';
    const subject = headers.find(h => h.name === 'Subject')?.value || '';
    const date = headers.find(h => h.name === 'Date')?.value || '';

    // Check if we already processed this message
    const existing = db.prepare(
      'SELECT id FROM activity_log WHERE details LIKE ?'
    ).get(`%${msg.id}%`);

    if (existing) continue;

    // Update the applications table if we can match by sender
    const updateStmt = db.prepare(`
      UPDATE applications
      SET response_type = ?, response_at = ?
      WHERE response_type IS NULL
      AND id = (
        SELECT id FROM applications
        WHERE response_type IS NULL
        ORDER BY submitted_at DESC LIMIT 1
      )
    `);
    updateStmt.run(responseType, new Date(date).toISOString());

    // Log to activity_log for the dashboard feed
    const logStmt = db.prepare(`
      INSERT INTO activity_log (timestamp, agent, action, details)
      VALUES (?, ?, ?, ?)
    `);
    logStmt.run(
      new Date().toISOString(),
      'gmail-scanner',
      `detected_${responseType}`,
      JSON.stringify({ gmail_id: msg.id, from, subject })
    );

    console.log(`Classified: ${responseType} | From: ${from} | Subject: ${subject}`);
  }

  db.close();
  console.log('Scan complete.');
}

/**
 * Main entry point: authorize and scan.
 */
async function main() {
  console.log('Starting Gmail response scan...');
  const auth = await authorize();
  await scanResponses(auth);
}

main().catch(console.error);
Build the Funnel, Variants, and Weekly Review views

The dashboard already has all seven view pages scaffolded. Now you will connect the Funnel, Variants, and Weekly Review pages to live SQLite data so they display real pipeline metrics.

  • Open app/funnel/page.tsx in your dashboard project. Update the data fetching to query the applications table and compute conversion rates per bucket.

The Funnel view shows the conversion pipeline: Applied, Responded, Phone Screen, Onsite, and Offer for each bucket with drop-off percentages at every stage.

  • In your Funnel page component, query the weekly_metrics table to pull bucket-level funnel data:
// Fetch funnel data per bucket from weekly_metrics
const db = require('better-sqlite3')(path.join(process.cwd(), 'db', 'pipeline.db'));

const funnelData = db.prepare(`
  SELECT bucket,
    SUM(applied) as applied,
    SUM(responded) as responded,
    SUM(phone_screen) as phone_screen,
    SUM(onsite) as onsite,
    SUM(offer) as offer
  FROM weekly_metrics
  GROUP BY bucket
`).all();

What does this query do?

It aggregates all weekly metric rows by bucket, summing each funnel stage. This gives Roy a cumulative view of how many applications progressed through each stage per category (AI Leaders, Big Tech, Government, Other).

  • Pass the funnelData array to a Recharts BarChart component. Each bar group represents one bucket. The y-axis shows count and the x-axis shows the funnel stage.
  • Open app/variants/page.tsx. Query the variants table to load all four resume variants for side-by-side display:
// Load all variant records for side-by-side comparison
const variants = db.prepare(`
  SELECT bucket, version, content, is_baseline
  FROM variants
  ORDER BY bucket
`).all();

What does the Variants view show?

It displays all four resume variants (AI Leaders, Big Tech, Government, Other) side by side. Roy can compare how each variant emphasizes different experience and skills. Diff highlighting shows what changed between versions when variants evolve over time.

  • Open app/weekly-review/page.tsx. Query weekly_metrics for trend data and activity_log for system health:
// Weekly response rate trends per bucket
const weeklyTrends = db.prepare(`
  SELECT week, bucket, applied, responded,
    CASE WHEN applied > 0
      THEN ROUND(100.0 * responded / applied, 1)
      ELSE 0
    END as response_rate,
    codex_pass_rate, standards_pass_rate, avg_time_per_package
  FROM weekly_metrics
  ORDER BY week ASC
`).all();

// System health from recent activity
const recentActivity = db.prepare(`
  SELECT agent, action, COUNT(*) as count
  FROM activity_log
  WHERE timestamp > datetime('now', '-7 days')
  GROUP BY agent, action
`).all();

What does the Weekly Review view display?

The Weekly Review gives Roy three panels. Line charts show response rate trends per bucket over time. A table lists filter changes and prompt improvements made each week. A system health card shows Codex pass rate, Standards pass rate, and average time per package.

This is where Roy sees whether the pipeline is actually getting smarter. If response rates climb week over week, the optimization loop is working.

  • Save all three view files.
  • Return to your browser at http://localhost:3000. Navigate to the Funnel page. You should see bar charts grouped by bucket showing the pipeline conversion stages.
  • Navigate to Variants. You should see all four resume variants displayed in a grid for comparison.
  • Navigate to Weekly Review. You should see the trend charts and system health card populated from your seed data.
  • Navigate to Home. The activity feed should show any Gmail detections from your scanner run (entries like "gmail-scanner detected_recruiter_reply").

Dashboard views showing empty data?

Confirm your SQLite database file exists at db/pipeline.db relative to the dashboard root. If you renamed the database or moved it, update the path in each page query.

Also verify that your seed data from Step 7 actually populated the weekly_metrics and variants tables. You can check by running a quick query in a Node REPL.

Help me debug why my dashboard views are showing no data from SQLite.

The feedback loop is closed. Roy's pipeline now discovers jobs, synthesizes packages, AND tracks what happens after submission. Next, you will run the full pipeline end-to-end on real job URLs and measure throughput.

Validate the Full Pipeline End-to-End

The entire pipeline is built. Agents are written, the vault holds Roy's atomic claims and four resume variants, and the dashboard is live on localhost:3000. Now it's time to prove the system works by running a real batch of jobs through every stage.

In this step, you will feed 10 real job URLs into the pipeline and watch them flow through discovery, scoring, synthesis, fact-checking, and approval. By the end, you will have 10 submission-ready application packages in the dashboard Queue view and a documented throughput benchmark that tells Roy exactly how many applications the system can prepare per session.

In this step, get ready to:
  • Run 10 seed jobs through discovery and scoring (B1 + B2).
  • Synthesize application packages in parallel (C1 + C2 + C3).
  • Fact-check all packages, benchmark throughput, and approve from the dashboard.
Process 10 seed URLs through discovery and scoring

Before the pipeline can synthesize anything, it needs raw material. Roy needs 10 real job URLs representing his four target buckets: AI Leaders, Big Tech, Government Contractors, and Other.

  • Open your browser and manually find 10 job posting URLs from company career pages, LinkedIn job posts (copy the URL from your browser, do not use automation), or Google search results.
  • Include at least 2 URLs per bucket: 2-3 AI Leaders (Anthropic, OpenAI, DeepMind), 2-3 Big Tech (Google, AWS, Microsoft), 2-3 Government Contractors (Palantir, Booz Allen, Raytheon), and 2-3 Other.
  • Open intake/daily-urls.txt in Obsidian and replace the template content with your 10 seed URLs, one per line with a bucket label:
# Daily URL Intake - Validation Batch
# Format: [BUCKET] URL
# Date: 2026-06-01

[AI_LEADERS] https://careers.anthropic.com/jobs/example-senior-engineer
[AI_LEADERS] https://openai.com/careers/example-role
[BIG_TECH] https://www.amazon.jobs/en/jobs/example-cloud-architect
[BIG_TECH] https://careers.google.com/jobs/results/example-sre
[BIG_TECH] https://careers.microsoft.com/us/en/job/example
[GOVERNMENT] https://careers.palantir.com/jobs/example-infra
[GOVERNMENT] https://careers.boozallen.com/jobs/example-cloud
[OTHER] https://boards.greenhouse.io/example-startup/jobs/123
[OTHER] https://lever.co/example-fintech/senior-engineer
[OTHER] https://jobs.example.com/cloud-platform-lead

What does this format do?

The bracket prefix tells Agent B2 which bucket you expect each job to land in. This lets you verify the categorizer is working correctly. If B2 disagrees with your label, that is useful signal about how the scoring model interprets the role.

  • Replace each example URL with a real job posting URL you found. Keep the bracket labels matching your intended bucket.
  • Save intake/daily-urls.txt.

Now run the discovery agents. Agent B1 parses each URL into structured job data. Agent B2 then categorizes and scores each job against Roy's claims database.

  • Switch to your Claude Desktop window with the orchestration project open.
  • Start a new conversation and paste the following instruction:
Run the B1 Job Intake Processor on all 10 URLs in /intake/daily-urls.txt.

For each URL:
1. Parse the job posting into structured JSON (title, company, requirements, responsibilities, seniority signals)
2. Save each parsed result to /intake/parsed-jobs/{company}-{role-slug}.json

Then run B2 Categorizer + Fit Scorer on all 10 parsed jobs:
1. Assign each to a bucket (AI_LEADERS, BIG_TECH, GOVERNMENT, OTHER)
2. Score fit 0-100 against /evidence/atomic-claims.md and /clients/roy/client-identity.yaml
3. Explain gaps (what Roy is missing for this role)
4. Save scored results to /intake/scored-jobs/{company}-{role-slug}.json
5. Log each action to the activity_log table in the SQLite database

Start a timer. Report total elapsed time when complete.

What is Claude doing here?

Claude reads each URL via filesystem MCP, fetches the page content, and applies the B1 prompt to extract structured data. Then it applies B2 sequentially (B1 must finish before B2 can score). This matches the dependency graph in /architecture/agent-dependencies.md.

The activity log entries appear in your dashboard Home view's activity feed in real time as Claude writes to SQLite.

  • Wait for Claude to finish processing all 10 URLs. This may take 3-5 minutes depending on page complexity.
  • Switch to your dashboard at localhost:3000 and check the Home view activity feed. You should see entries like "B1 parsed [Company] [Role]" and "B2 scored [Company] [Role] at [score]/100".
  • Open /intake/scored-jobs/ in Obsidian. Verify you have 10 JSON files, each with a bucket assignment, fit score, and gap explanation.

Check bucket accuracy

Compare Claude's bucket assignments to your bracket labels in daily-urls.txt. If B2 categorized a job differently than you expected, read its reasoning. A mismatch is not necessarily wrong. The categorizer may pick up signals you missed.

  • Note the total elapsed time Claude reported. Write it down for the benchmark later.

Claude can't access a job URL?

Some career pages block automated access or require JavaScript rendering. If Claude reports it cannot parse a URL, replace that URL with one from the same bucket that loads as plain HTML.

If Claude loses context mid-batch, split the 10 URLs into two batches of 5. Run B1+B2 on each batch in a separate conversation.

help me troubleshoot Claude failing to parse job URLs via filesystem MCP

Synthesize application packages in parallel

With 10 jobs scored and categorized, the synthesis team takes over. Agents C1, C2, and C3 run in parallel for each job that passes the fit threshold. This is where the pipeline generates the actual materials Roy will submit.

  • In Claude Desktop, start a new conversation in the orchestration project and paste this instruction:
Run synthesis agents C1 + C2 + C3 on all scored jobs in /intake/scored-jobs/ that scored above 50.

For each qualifying job, run IN PARALLEL:
- C1 Resume Tailoring: Select the correct variant from /variants/ based on bucket. Tailor emphasis to the specific role requirements. Save to /applications/{company}-{role}/resume-tailored.md
- C2 Cover Letter Drafter: Draft cover letter citing vault-evidenced wins from /evidence/atomic-claims.md. Reference specific achievements that match the job's gap analysis. Save to /applications/{company}-{role}/cover-letter.md
- C3 Q&A Generator: Generate interview prep answers sourced from /clients/roy/client-identity.yaml and /evidence/atomic-claims.md. Save to /applications/{company}-{role}/qa-prep.md

For each package completed, log to activity_log.

Report: how many packages were completed before you hit context length pressure. Note the approximate token count at that point.

Why parallel execution matters here

C1, C2, and C3 have no dependencies on each other. They all read from the same scored-job JSON and the vault, but they produce independent outputs. Running them in parallel per job is why the pipeline can produce complete packages faster than a human doing one document at a time.

The agent dependency graph specifies this parallelism. B1 must finish before B2, and B2 must finish before any C agent can start. But C1, C2, and C3 can run simultaneously for each job.

  • Wait for Claude to complete synthesis. If Claude signals context pressure before finishing all 10, note which job it stopped at. Start a new conversation and resume with the remaining jobs.
  • Open /applications/ in Obsidian. Verify each job folder contains three files: resume-tailored.md, cover-letter.md, and qa-prep.md.

Now verify the quality of the outputs against the pipeline's core constraints.

  • Open two or three cover-letter.md files from different buckets. Check that each cover letter cites specific achievements from /evidence/atomic-claims.md rather than generic statements.
  • Open resume-tailored.md files and verify the correct variant was selected. An AI Leaders job should use the ai-leaders.md variant as its base. A Big Tech job should use big-tech.md.
  • Document the throughput ceiling. Write down how many complete packages Claude produced in one session before context pressure forced a new conversation.

What is the throughput ceiling?

Claude's context window is 200K tokens. Each job's synthesis (reading the scored JSON, reading the variant, reading atomic claims, writing three output files) consumes a significant chunk of that window. The throughput ceiling tells Roy how many jobs he can batch per session before needing to start fresh.

This number becomes your operational planning tool. If the ceiling is 6 packages per session, Roy knows a batch of 10 requires two sessions. Document this in the benchmark.

Cover letters not citing specific evidence?

If C2 produces generic cover letters, check that the scored-job JSON includes the gap analysis from B2. C2 needs gaps to know which claims to cite. Also verify that /evidence/atomic-claims.md is accessible via filesystem MCP. Ask Claude to confirm it can read the file.

help me debug why my cover letter agent is producing generic output instead of citing vault evidence

Fact-check, approve, and benchmark

The quality gate is what separates this pipeline from a naive generate-and-send workflow. Agent D1 uses Codex CLI (a different model family from Claude) to independently verify every claim. Agent D2 checks legal and ethical compliance. Nothing reaches Roy's Queue without passing both.

  • Open PowerShell and navigate to your vault directory.
  • Run Codex CLI with the D1 fact-check instruction on your applications folder:
codex --cd /path/to/vault "Read the D1 fact-checker prompt in /agents/D1-codex-fact-checker.md. Then for each application package in /applications/, verify every claim in resume-tailored.md and cover-letter.md against /evidence/atomic-claims.md. Flag any claim that is not supported by a cited evidence entry. Save a fact-check report to each application folder as d1-factcheck-report.md."

Why Codex CLI instead of Claude?

This is the dual-model cross-validation pattern documented in adr-001-dual-model-factcheck.md. Claude generated the materials. A different model family (GPT-5.x via Codex CLI) now reviews them independently. Claude cannot override a Codex FAIL. Only Roy can approve a flagged package.

This catches hallucinated claims, inflated metrics, and contradictions that same-model review would miss.

  • Wait for Codex CLI to complete. Review any FAIL flags in the reports.
  • If Codex flagged any claims as unsupported, open the relevant resume-tailored.md or cover-letter.md and either provide evidence or remove the claim. Re-run D1 on corrected packages until all claims pass.

Now run the Standards Gate to verify legal and ethical compliance.

  • Switch back to Claude Desktop and start a new conversation in the orchestration project. Paste this instruction:
Run D2 Standards Gate on all application packages in /applications/.

For each package, verify:
1. No EEOC violations (no references to age, gender, race, religion, disability, or other protected characteristics)
2. No fabricated credentials or inflated titles
3. For any job where the employer HQ is in an EU member state: add EU AI Act Art 50 transparency disclosure to the cover letter footer noting that AI tools assisted in preparing this application
4. All claims trace back to /evidence/atomic-claims.md entries marked SUPPORTED

Save a standards gate report to each application folder as d2-standards-report.md.
Log each check to activity_log.

What is the EU AI Act Art 50 disclosure?

Article 50 of the EU AI Act takes effect on 2 August 2026. It requires transparency when AI systems generate content that interacts with people. For applications to EU-based employers, the pipeline adds a brief disclosure stating that AI tools assisted in preparing the materials.

Agent D2 checks the employer's HQ location. If it's in an EU member state, the disclosure is appended. For non-EU employers, no disclosure is added. This keeps Roy compliant by construction without manual checking.

  • Wait for Claude to complete the D2 run. Verify each application folder now has both d1-factcheck-report.md and d2-standards-report.md with PASS status.
  • Switch to your dashboard at localhost:3000 and open the Queue view. You should see all 10 packages with inline previews of the tailored resume and cover letter text.
  • Click through two or three packages in Queue view. Verify you can read the resume variant and cover letter side by side without opening files.

The final task is documenting the benchmark so Roy knows the system's operational capacity.

  • Create /architecture/throughput-benchmark.md in Obsidian with the following template, filling in your measured values:
# Throughput Benchmark Report

## Test Parameters
- Input: 10 job URLs (2-3 per bucket)
- Date: 2026-06-01
- Model: Claude (Max 20x plan)
- Fact-checker: Codex CLI

## Results

### Discovery + Scoring (B1 + B2)
- Time: [X] minutes for 10 URLs
- Per-job average: [X] minutes

### Synthesis (C1 + C2 + C3)
- Packages per session before context limit: [X]
- Sessions needed for 10 packages: [X]
- Time per package: [X] minutes

### Quality Gate (D1 + D2)
- D1 Codex CLI fact-check: [X] minutes for 10 packages
- D2 Standards Gate: [X] minutes for 10 packages
- First-pass FAIL rate: [X]/10 packages flagged

### Total Pipeline
- Total time (10 URLs to 10 approved packages): [X] minutes
- Per-application estimate: [X] minutes
- Claude sessions required: [X]

## Throughput Ceiling
- Max packages per Claude session: [X]
- Limiting factor: context window (200K tokens)
- Recommended batch size: [X] jobs per session

## Operational Notes
- [Any issues encountered, workarounds used, or patterns noticed]

Why document this?

The benchmark tells Roy how to plan his daily workflow. If the pipeline handles 6 packages per session and Roy discovers 4-5 new jobs per day, he knows one session covers a full day of intake. If it takes 45 minutes end-to-end for 10 jobs, he can run the pipeline in the morning and have packages ready for afternoon review.

This benchmark also becomes evidence for interviews. Roy can say exactly how long his multi-agent system takes to process applications and what its throughput ceiling is.

  • Fill in all measured values in the benchmark template and save the file.
  • Return to your dashboard Queue view. Click the approve button on one package to confirm the approval workflow functions correctly.

After approval, Roy submits manually

The pipeline prepares everything. Roy copies the tailored resume and cover letter from the approved package and pastes them into each company's application portal himself. Zero automation touches external platforms. This is the LinkedIn ToS compliance strategy documented in adr-003-zero-linkedin-automation.md.

Packages not showing in Queue view?

Verify that Claude logged each completed package to the activity_log table in SQLite. The Queue view reads from the applications table. Check that B2's scored data was inserted into the applications table with the correct status field.

If the dashboard shows stale data, hard-refresh your browser with Ctrl+Shift+R.

help me debug why approved packages are not appearing in my Next.js dashboard Queue view

The pipeline is validated. Roy has 10 submission-ready packages sitting in his Queue, each fact-checked by a different model family and cleared through the Standards Gate. The benchmark report gives him the numbers to plan daily operations. Next up: the Secret Mission builds the continuous improvement loop that makes this system smarter every week.

Secret mission

Weekly Adversarial Council + Monthly Profile Evolution

The pipeline works. But a static system decays. Response rates shift. Roy gains new certifications. Cover letter angles that worked last month go stale. Build the continuous improvement engine: a weekly Adversarial Council where Codex CLI challenges Claude's recommendations, a monthly Profile Evolution workflow that keeps Roy's professional presence growing stronger, and the dashboard metrics to track it all.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. All project resources run locally or on free services, so there are no ongoing infrastructure costs.

Cost warning

The pipeline's daily operation consumes your Claude Max 20x ($200/mo) and ChatGPT Plus ($20/mo) subscriptions. These are pre-existing subscriptions you already have. If you cancel either subscription, the pipeline loses its AI capabilities.

The project resources themselves (vault, dashboard, database, Gmail API) cost nothing to keep on disk. The $220/mo total is purely for the AI subscriptions that power the agents.

Resources you used:

  • Obsidian vault on local disk (free, no ongoing cost).
  • Claude Desktop Project with orchestration prompt and knowledge files (included in Max 20x subscription).
  • Next.js command-center dashboard running on localhost (free).
  • SQLite database on local disk (free).
  • Gmail API OAuth credentials in a Google Cloud project (free for personal accounts).
  • Google Alerts for job discovery keywords (free, runs indefinitely).

Keep everything running

No action needed. Choose this if you are still actively running the pipeline daily.

  • The pipeline is designed for daily operation. Budget 30-45 minutes per day in the dashboard to review packages, approve submissions, and track responses.
  • The weekly Adversarial Council runs every Sunday. The monthly Profile Evolution runs on the 1st of each month.
  • Google Alerts continue delivering job URLs to your Gmail automatically.
  • Your Claude Max 20x and ChatGPT Plus subscriptions will continue billing at $220/mo combined.

Pause - I'll come back to this later

Shut down running processes to free up memory. All data persists locally for when you return.

  • Stop the Next.js dev server by pressing Ctrl+C in the terminal where it is running.
  • Your Obsidian vault, SQLite database, and all agent prompts remain on disk. Nothing is lost.
  • Google Alerts will continue sending emails. You can pause individual alerts from alerts.google.com if you prefer silence.
  • Your Claude Desktop Project and Gmail API credentials remain active. Resume anytime by starting the dev server again.

Delete - I don't want to use this again

Remove all project resources and start fresh. Keep the vault, ADRs, Mermaid diagrams, and dashboard code as portfolio evidence if desired.

  • Export any data you want to keep from the SQLite database first. Open the dashboard folder and copy the database file from dashboard/db/ to a safe location.
  • Delete the Obsidian vault folder and the dashboard/ folder from your local disk. On Windows, run:
Remove-Item -Recurse -Force .\dashboard

Delete your Obsidian vault folder the same way, replacing the path with your vault's location.

  • Delete the Gmail API OAuth credentials by removing the Google Cloud project. Open console.cloud.google.com, select the project you created for the Gmail API, navigate to the project settings, and shut down the project.
  • Delete your Google Alerts. Open alerts.google.com, find each job search alert you created, click the trash icon next to it, and confirm deletion.
  • Delete the Claude Desktop Project. Open Claude Desktop, navigate to the project you created for the pipeline, open its settings, and delete it. This removes the orchestration prompt and uploaded knowledge files.
  • Delete the local credentials.json file you downloaded for Gmail API OAuth.
Remove-Item .\credentials.json

Keep the architecture as portfolio evidence

Before deleting everything, consider keeping copies of your ADRs, Mermaid system diagram, agent dependency graph, and the dashboard source code. These document your ability to architect multi-agent systems and serve as interview-ready evidence.

That's a Wrap!

That's a Wrap!

Nice work! Roy built the system everyone else wishes they had. A production-grade 12-agent pipeline with a command-center dashboard, dual-model fact-checking, and continuous self-improvement. All while maintaining 100% LinkedIn ToS compliance by architectural design.

You've learned how to:

  • Architect a multi-agent system with 12 agents across 4 teams, documented with ADRs and a Mermaid system diagram, wired to a filesystem MCP vault and validated end-to-end on 10 seed applications.
  • Build a high-fidelity Next.js command-center dashboard with 7 operational views (Home, Queue, Job Detail, Funnel, Variants, Weekly Review, Profile Evolution) powered by SQLite and Recharts, including Gmail API response tracking with a live activity feed.
  • Implement dual-model cross-validation using Claude for orchestration and Codex CLI as an adversarial fact-checker from a different model family, ensuring no unsupported claims reach submission.
  • Built a weekly Adversarial Council where Codex CLI challenges Claude's recommendations on 5 axes, plus a monthly Profile Evolution workflow that re-audits Roy's resume, LinkedIn, and GitHub through 3 expert lenses and proposes evidence-backed improvements as Roy upskills and ships projects.

Ready to quiz yourself?