# LinkedIn Job Search — MANIFEST

## Project Overlay

### Project Context

Owner: Braden McLeish. Goal: automate Braden's executive job search using the OpenClaw agent stack — surface Director-level operations roles ($150K+), qualify them, draft personalized outreach and application materials, and execute approved actions with minimal manual effort. Time horizon: active search, indefinite. The project lives under 07 — Projects → LinkedIn Job Search in the OpenClaw Notion workspace. The PRD is the authoritative source of truth for architecture, templates, scoring rubric, and approval policy. All completion contracts are logged as child pages under the PRD. Dorian is the executive orchestrator; workers execute drafts and Notion writes; Research Intelligence handles qualification (deferred to Path C in a future phase); ops-maintainer handles Telegram relay and cron. No external actions (LinkedIn sends, connection requests) execute without explicit Braden approval via Telegram.

### External System References

- OpenClaw — orchestration target for all filesystem work, Notion writes, IMAP ingestion, draft generation, and Telegram relay on the Mac Mini. Architecture pointer: OpenClaw MANIFEST and 01 — Architecture Notion page (AGENTS.md, IDENTITY.md, OPERATING-MODE.md, SOUL.md, WORKER-CONTRACT.md, AGENT-ROLES.md). Improvement-tracking surface: Notion Decision & Incident Index (THR-N entries) under 05 — Decision Log. Brief intake format: natural-language requests to Dorian via Cowork or Telegram. Brief intake format is NOT WORKER-CONTRACT YAML — that is internal to OpenClaw between Dorian and his worker pool. Cost/routing notes: all draft generation is Dorian Path B (Dorian constructs worker contract, worker executes); qualification scoring is inline qualify() for now, Path C Research Intelligence dispatch deferred until real research signals are available; Telegram relay is ops-maintainer standalone.

### Domain-Specific Pre-Flight Checks

1. IMAP runner health — confirm icloud_imap_ingest.py --once syntax-checks clean and the iCloud IMAP connection is reachable before dispatching any ingestion worker. Receipt: R3 debugging chain 2026-05-16 — stale NOTION_API_KEY env var caused silent 401 failures; confirmed-valid token path is /Users/openclaw/.config/notion/api_key, not the env var.
2. Notion pipeline DB accessible — confirm Notion data source cbde3032-5e2c-4557-8a00-cd2d14d4ab83 is reachable and the expected schema properties (Role Title, Fit Score, Strategy, Research Payload, Cover Draft, Connection Draft) are present before any write worker is dispatched.
3. Parser behavior check — before any ingestion pass that touches notion_sender_parser.py, run the three-case behavior check (direct LinkedIn, legacy forwarded LinkedIn, direct non-LinkedIn) and confirm all three pass. Receipt: Phase 1.5 — Step 3a corrective pass required after initial parser update broke legacy forwarded classification.
4. Self-authorship filter armed — confirm event-router service is running healthy (`{"ok":true,"failClosed":false}`) and LinkedIn pipeline DB + data source are in the routed subscription scope before any Notion write worker is dispatched. Receipt: R3 2026-05-16 — filter was already present in router.js; scope extension was the required work.
5. No `.bak-*` files in live workspace — confirm archive/ exists and no .bak-* files remain in /Users/openclaw/.openclaw/workspace/job-search/. Receipt: Phase 1.5 Deliverable 10.
6.UNSEEN backlog sanity — before a production heartbeat run, confirm the UNSEEN count in the Search folder is not unexpectedly large (>50 suggests a missed heartbeat, not a normal cycle). Note: 1,763-message historical backlog was manually marked read by Braden on 2026-05-20; queue is now clean.

### Domain-Specific Anti-Patterns

1. Draft sent without Telegram approval. No LinkedIn connection request, application submission, or outreach message executes without explicit Braden approval via Telegram per PRD §10. This is absolute. Receipt: PRD §10 approval policy.
2. Qualification scores treated as high-confidence. Most dimensions currently default to 5.0 because real research signals (company_health, referral_signal, hiring_manager_signal) are not yet sourced. Drafts use Research Payload as context only; do not present fit scores as authoritative until Path C research dispatch is active.
3. Company name misextracted as Role Title. LinkedIn digest subject format "keyword": Company - Role Title and more sometimes causes the parser to extract company name as Role Title. Spot-check records before drafting. Receipt: Phase 1.5 Findings 2026-05-18.
4. Inbound recruiter duplicates. Recruiter records with no job URL can produce duplicate pipeline records. Dedup check must include recruiter name + location + role title, not just job URL. Receipt: Phase 2 QA CyberCoders leg.
5. Job-board digest records drafted. Records with Source = Job Board Digest skip qualification and draft generation. Do not generate Template A or B drafts for digest records. Strategy and Fit Score are intentionally blank on these.
6. NOTION_API_KEY env var used as auth. The env var is stale and diverges from the confirmed-valid file token. All Notion auth must use /Users/openclaw/.config/notion/api_key. Receipt: R3 THR-21 2026-05-16.
7. Fetch limit left at non-zero in production. DEFAULT_FETCH_LIMIT must be 0 for production heartbeat runs. --limit N is for debug only. Receipt: Phase 1.5 Deliverable 8.

### Domain-Specific Worker Contract Patterns

- Ingestion workers must follow the IMAP sequencing contract: fetch UNSEEN → create Notion record → confirm success → run parser + qualification inline → only then mark SEEN. Never mark SEEN before Notion write is confirmed.
- Draft generation workers receive the Research Payload from the pipeline record as context. Output goes to Cover Draft (Template A), Connection Draft (connection note), Notes (recommendation note). Status transitions to Draft Ready on successful write. No send action in Phase 2.
- Notion write workers must apply the self-authorship filter: do not process any Notion webhook event where last_edited_by matches the OpenClaw integration token. Log drops at INFO with event ID.
- All worker contracts must include a backup file before any edit: .bak-<contract-id>-<YYYYMMDD> pattern, moved to archive/ on completion.
- Completion Contracts are logged as child pages under the PRD (`345f8b3e-f83d-818f-8df2-c5ddd7501b8c`), not inline in the MANIFEST.

### Domain-Specific Thresholds, Values, and Vocabulary

- Fit Score threshold: 5.0+ advances to draft generation. Below 5.0 = Skip (unless Braden has a pre-existing signal).
- Inbound recruiter threshold: 4.0+ advances (lower bar because inbound interest is itself a positive signal).
- Salary floor: $150K+. Roles without posted salary are flagged for review, not auto-dropped.
- Location: Remote + within 25 miles of 92870 (Yorba Linda, CA).
- Source taxonomy: LinkedIn Alert / Inbound Recruiter / Job Board Digest / Manual
- Status flow: New → Researching → Draft Ready → Pending Approval → Outreach Sent → Applied → Follow-Up Due → Interviewing → Offer → Closed
- Notion pipeline DB: 7f7dd470-f801-433b-a36e-7263a592a910 / data source cbde3032-5e2c-4557-8a00-cd2d14d4ab83
- PRD: 345f8b3e-f83d-818f-8df2-c5ddd7501b8c
- Notion auth token path: /Users/openclaw/.config/notion/api_key (env var NOTION_API_KEY is stale — THR-21)
- IMAP: imap.mail.me.com:993, folder Search, user bradenmcleish@icloud.com, password at /Users/openclaw/.secrets/icloud_imap_app_password- Draft templates: Template A (Cover Message) and Template B (Recruiter Response) — calibration targets are the DP World and Albert Alvarado approved worked examples in PRD §6 Phase C. Anti-patterns list in PRD §6 Phase C is mandatory reading before any draft worker is dispatched.

### Fallback Source Playbook

- Ingestion (IMAP): Primary — iCloud IMAP Search folder. Fallback — none currently; Notion Sender is decommissioned. If IMAP is unreachable, flag to Braden and hold.
- Employer resolution: Primary — email body and subject line. Fallback — job URL scrape via browser automation (requires Dorian approval before activation, ToS risk). Secondary fallback — flag record as Employer Unknown and hold for Braden review.
- Qualification signals: Primary — parser-extracted fields (role_title, company, salary, posted_date). Fallback for company_health / referral_signal / hiring_manager_signal — default to 5.0 with explicit flag in Research Payload. Path C Research Intelligence dispatch is the long-term fix; deferred.
- Draft context: Primary — Research Payload on the pipeline record. Fallback — raw Email Text. Never draft without at least one of these present.
- Telegram relay: Primary — ops-maintainer standalone agent. Fallback — Braden checks Notion directly and approves via chat.

### Accumulated Learnings

- 2026-05-16 — iCloud IMAP returns BODY.PEEK[] as mixed tuple + trailing bytes; RFC822 returns bytes-only IMAP fragments, not message bodies — surfaced in: R3 IMAP runner debugging — type: api
- 2026-05-16 — NOTION_API_KEY env var on Mac Mini diverges from confirmed-valid file token at /Users/openclaw/.config/notion/api_key; all Notion auth must use the file token — surfaced in: R3 THR-21 — type: failure-mode
- 2026-05-16 — Self-authorship filter framework already existed in router.js; scope extension + logging improvement was the required work, not structural surgery — surfaced in: R3 Step 1 — type: reusable-artifact
- 2026-05-18 — LinkedIn digest subject format "keyword": Company - Role Title and more sometimes causes parser to misextract company name as Role Title; spot-check before drafting — surfaced in: Phase 1.5 smoke test — type: data-source
- 2026-05-18 — Email Text chunking must use UTF-16 code unit boundaries, not Python character boundaries; BMP-exceeding characters (emoji, some CJK) cause segment overflow from Notion's perspective — surfaced in: Phase 1.5 Deliverable 6 — type: api
- 2026-05-18 — 1,763-message UNSEEN backlog existed from Nov 2025; manually marked read by Braden 2026-05-20; queue now clean — surfaced in: Phase 1.5 / Phase 2 handoff — type: scope-boundary
- 2026-05-20 — MANIFEST created retrospectively after Phases 1–1.5 completed; bootstrapped from PRD, completion contracts, and R3 Completion Contract — surfaced in: Phase 2 setup — type: pattern
- 2026-05-20 — Resume source file contained incorrect employee count (200+ vs correct 40+) for Reconserve of California; draft_engine.py corrected to 40+, resume source file still needs manual correction — surfaced in: Phase 2 DP World draft review — type: data-source
- 2026-05-22 — Canonical resume source file corrected from 200+ to 40+ employees for Reconserve of California during Phase 3 pre-flight; corrected DOCX hash: af70ca09f7ab23fa747016f0bea243797d4c16219d1ee355d138a91ab4585c0d — surfaced in: Phase 3 build pre-flight — type: remediation
- 2026-05-22 — Phase 3 execution automation required a live pipeline schema extension (`Braden Approval`, `Last Action Date`, `Follow-Up Due`) before hard-gated send and follow-up runners could be implemented — surfaced in: Phase 3 build pre-flight — type: schema

---

## Registry

### Source Files
- icloud_imap_ingest.py — IMAP runner, one-shot --once entry point, ingests UNSEEN from Search folder, runs parse → resolve → qualify → write inline
- notion_sender_parser.py — classifies and extracts structured fields from email body; backward compat with legacy Notion Sender records preserved
- employer_resolution.py — detects job-board middleman vs. real employer; called inline for linkedin_alert and recruiter_inmail classifications
- qualification_flow.py — weighted Phase B rubric scoring; inline dispatch only; Path C deferred
- RESUME-SOURCE.md — pointer to canonical resume file
- linkedin_send_actions.md — worker execution artifact for LinkedIn send actions (Phase 3)
- linkedin_send_runner.py — hard-gated outbound execution runner; dry-run first, live execution behind explicit runtime enablement
- followup_monitor.py — due-follow-up scanner and digest builder for Outreach Sent / Applied records
- followup_timing_logic.md — follow-up timing rules (Phase 3)
- archive/ — .bak-* files and retired artifacts

### Notion Objects
- PRD: 345f8b3e-f83d-818f-8df2-c5ddd7501b8c
- Pipeline DB: 7f7dd470-f801-433b-a36e-7263a592a910
- Pipeline data source: cbde3032-5e2c-4557-8a00-cd2d14d4ab83
- Task Brief (Phase 1): 346f8b3e-f83d-81fe-ac22-fdb218d791f6
- Task Brief (Phase 1.5): 364f8b3e-f83d-816a-b5d2-d7179b5b729e
- Completion Contract (R3): 362f8b3e-f83d-81cd-a388-c458e5f1887f
- Completion Contract (Phase 1.5): 364f8b3e-f83d-8139-a51c-fe49d805332b- THR-21 (stale env var): Decision & Incident Index 33bf8b3e-f83d-81e6-8aca-e1f8d64aaf39

### Infrastructure
- Event router: /Users/openclaw/event-router/router.js — LinkedIn pipeline scope active, self-authorship filter armed
- LaunchAgent: ai.openclaw.event-router.plist
- Heartbeat: HEARTBEAT.md — LinkedIn IMAP check 2x/day
- Cron: linkedin-weekly-digest-monday-730am — Monday 7:30 AM America/Los_Angeles

---

## Changelog

- 2026-05-20 — Phase 2 Draft Engine complete. draft_engine.py built and tested. Schema gap resolved (Cover Draft, Connection Draft, Notes added). End-to-end test completed on DP World / Plant Manager record. Telegram approval loop validated. Completion Contract logged: 369f8b3e-f83d-8190-b422-da877c97f6b4
- 2026-05-22 — Phase 3 execution automation build complete. Added hard-gated outbound runner (`linkedin_send_runner.py`), due-follow-up monitor (`followup_monitor.py`), live pipeline schema fields (`Braden Approval`, `Last Action Date`, `Follow-Up Due`), and heartbeat dry-run checks. Dry-run validation completed against the live pipeline; no records were eligible to send or follow up at validation time.
