job-forge 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -1,33 +1,97 @@
1
- # JobForge -- AI Job Search Pipeline
1
+ # Agent: job-forge
2
2
 
3
- ## Hard Limits NEVER exceed these numbers
3
+ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs via Geometra MCP, applies to jobs, tracks applications across day files. Runs inside opencode, Claude Code, Cursor, or Codex; the orchestrator session delegates tool-heavy batch work to subagents and keeps quality-sensitive narrative work inline.
4
4
 
5
- The Hard Limits below are non-negotiable numeric rules. If you catch yourself about to violate one, STOP and restructure.
5
+ ## Hard limits
6
6
 
7
- 1. **Max parallel subagents: 2.** Never emit 3+ `task` tool calls in a single message. For N jobs, run `ceil(N/2)` sequential rounds of 2. No exceptions not for "urgent", not for "the user asked for 10".
8
- 2. **Max 1 application per company+role.** Before every `task` dispatch for `apply`, Grep ALL of the following for the URL and for `company+role`:
9
- - `data/pipeline.md`
10
- - all `data/applications/*.md` day files (not just today's — prior-day Applies count too)
11
- - `batch/tracker-additions/*.tsv` (pending outcomes not yet merged)
12
- - `batch/tracker-additions/merged/*.tsv` (outcomes already consumed into day files — catches same-day earlier-batch Applies that merge collapsed into an existing row)
7
+ - [H1] Max 2 parallel `task` dispatches per message. For N jobs, run `ceil(N/2)` sequential rounds of 2. Applies in all modes, for all user phrasings ("urgent", "apply to 10 jobs now").
8
+ why: higher parallelism blows through free-tier rate limits; each subagent requires post-cleanup and racing more than 2 reliably loses at least one result
13
9
 
14
- If any source shows an APPLIED / Applied outcome for this URL or company+role, skip that job and do not dispatch. **Why merged/ matters**: when two batches in the same day target the same role, `npx job-forge merge` updates the existing day-file row instead of creating a new one — so `grep data/applications/*.md` for the higher report number misses the earlier apply. The merged TSV is the only place the newer attempt's breadcrumb remains.
15
- 3. **Always clean Geometra sessions before dispatching.** Before every round of `task` dispatches that will use Geometra, call `geometra_list_sessions` then `geometra_disconnect({closeBrowser: true})`. Every round. The disconnect is a no-op when the pool is empty.
16
- 4. **Orchestrator does NOT fill forms.** This session MUST NOT call `geometra_fill_form`, `geometra_run_actions`, `geometra_pick_listbox_option`, or `geometra_fill_otp` when handling a multi-job request. If you need to, it means you MUST have delegated — `task` out the remaining work instead.
17
- 5. **Re-dispatch only AFTER the previous subagent returns.** Never fire the same company's `task` twice while the first is still in-flight. Wait for the return value, then decide if a retry is warranted.
18
- 6. **Application outcomes flow through TSVs, not `data/pipeline.md`.** When a subagent returns APPLIED / FAILED / SKIP, the outcome goes to `batch/tracker-additions/{num}-{slug}.tsv`. `npx job-forge merge` then consumes the TSVs into the correct `data/applications/YYYY-MM-DD.md` day file. `data/pipeline.md` only tracks URL inbox state (`[ ]` pending → `[x]` processed). **NEVER append APPLIED / FAILED status lines to `pipeline.md`** — that's the day file's job, via the TSV pathway. After any multi-apply run, the orchestrator MUST run `npx job-forge merge` followed by `npx job-forge verify` before ending the session.
19
- 7. **Load-bearing facts passed to downstream subagents must come from a file, not from a prior subagent's prose.** A URL, score, email ID, confirmation page snippet, JD salary range, exact answer submitted to a form question, or any other specific value that a downstream subagent will act on MUST originate from one of:
20
- - `data/pipeline.md` (URL inbox state)
21
- - `data/scan-history.tsv` (scan provenance)
22
- - `batch/scan-output-*.md` (scan-ranked candidates)
23
- - A report file (`reports/{num}-*.md`) with authoritative headers (`**URL:**`, `**Score:**`, etc.)
24
- - A TSV in `batch/tracker-additions/` (per-apply outcomes)
10
+ - [H2] Max 1 application per company+role. Before every `apply` dispatch, grep all four sources for the URL and for `company+role`: `data/pipeline.md`, all `data/applications/*.md` day files, `batch/tracker-additions/*.tsv`, `batch/tracker-additions/merged/*.tsv`. If any source shows APPLIED / Applied, skip the dispatch.
11
+ why: 2026-04 same-day batch collision when two batches target the same role, `npx job-forge merge` updates the existing day-file row rather than appending, so grepping day files alone misses earlier-batch applies; merged/*.tsv is the only place the breadcrumb remains
25
12
 
26
- **Not trustworthy by default**: anything quoted from a subagent's return message, any ID or score the orchestrator "remembers" from prose, any page-content snippet reproduced from a subagent's narrative. Subagents can hallucinate plausible-looking IDs, scores, and confirmation text. Before passing any such fact to another subagent, cross-check it exists in one of the authoritative files above, OR instruct the dispatching subagent to write its output to a structured file and re-read from that file.
13
+ - [H3] Before every batch of `task` dispatches that will use Geometra, call `geometra_list_sessions` then `geometra_disconnect({closeBrowser: true})`. Every round, no exceptions. Name this cleanup as an explicit "step 0" in your first-response plan for any multi-apply request it is the most frequently skipped guardrail in practice, and skipping it produces cascade "Not connected" failures on the next dispatch.
14
+ why: if any prior subagent aborted mid-flow, its Chromium session stays stuck in the MCP pool and the next `geometra_connect` fails with "Not connected"; the disconnect is a no-op when the pool is empty but a poison-cure when it isn't; vocalizing it up-front doubles the odds it actually runs
27
15
 
28
- **Why**: on 2026-04-18, a scan subagent returned 30 fabricated Greenhouse IDs in prose (correct role titles, plausible-looking invented IDs that didn't exist in the API). The orchestrator dispatched 30 downstream subagents that all hit 404s. Verification rules downstream (Hard Limit #6, API-first verify) caught the symptom. This rule prevents the *shape* of the bug hallucinations propagating through prose handoffs across all quantitative / identifier / specific-fact claims, not just URLs.
16
+ - [H4] In multi-job mode, the orchestrator session MUST NOT call `geometra_fill_form`, `geometra_run_actions`, `geometra_pick_listbox_option`, or `geometra_fill_otp` directly. Your first-response plan must name the `task` dispatches explicitly ("dispatch subagent for job 1, subagent for job 2, …") do not describe the work in first person ("I'll visit each job, fill each form") when it will be delegated.
17
+ why: repeated Geometra calls in the orchestrator bloat the cache prefix — this is the 2026-04 "apply to 20 jobs" 341-msg incident where each turn re-processed 100K+ fresh tokens instead of reading from cache; first-person narration is a leading indicator that the agent is mentally queueing work for itself rather than a subagent
29
18
 
30
- Everything below is context and rationale. These seven numbers are the rules.
19
+ - [H5] Re-dispatch the same company only AFTER the previous subagent returns. Never fire the same `task` twice while the first is still in flight.
20
+ why: two in-flight subagents for the same URL race on Geometra sessions and on tracker TSV writes, corrupting state and sometimes double-submitting
21
+
22
+ - [H6] Application outcomes flow through `batch/tracker-additions/*.tsv`, not `data/pipeline.md`. After any multi-apply run, the orchestrator MUST run `npx job-forge merge` then `npx job-forge verify` before ending the session.
23
+ why: `pipeline.md` is the URL inbox (`[ ]` pending → `[x]` processed); `data/applications/YYYY-MM-DD.md` is the outcome log; the TSV pathway is the only safe bridge because `merge` handles column order and duplicate detection
24
+
25
+ - [H7] Load-bearing facts passed to downstream subagents must originate from a file, not from prior subagent prose. Authoritative sources: `data/pipeline.md`, `data/scan-history.tsv`, `batch/scan-output-*.md`, `reports/{num}-*.md` with `**URL:**` / `**Score:**` headers, `batch/tracker-additions/*.tsv`.
26
+ why: 2026-04-18 scan subagent returned 30 fabricated Greenhouse IDs in prose (plausible-looking, non-existent); orchestrator dispatched 30 downstream subagents that all 404'd. Subagents can hallucinate IDs, scores, and confirmation text — round-trip through a file or don't trust the value
27
+
28
+ ## Defaults
29
+
30
+ - [D1] Delegate to a subagent (`task`) only when the work involves repeated tool-heavy steps that bloat the cache prefix: applying to N≥2 jobs, batch scans hitting ≥3 companies, or any "apply to… / process pipeline / batch evaluate" user phrasing. Single-offer evals, dev work, file edits, `tracker` mode, single-URL checks, and one-shot questions stay inline.
31
+ why: iso-trace showed 0.25% Agent calls across 5174 turns under a prior over-broad "delegate before 2nd tool call" rule — the rule was ignored in practice; narrowing matches the original cache-bust incident
32
+
33
+ - [D2] Route subagent work by cost tier. `@general-free`: procedural — form-fill, TSV merge, verify, OTP retrieval, portal scan metadata extraction, one-shot structured-field transforms. `@general-paid`: quality-sensitive — offer evaluation narrative Blocks A-F, cover letters, "Why X?" answers, STAR interview stories, LinkedIn outreach. `@glm-minimal`: narrow ≤5K-input one-shot extract/classify jobs that do not need context.
34
+ why: GLM 5.1 doesn't discount cache reads so procedural work there costs ~10×; free-tier models handle procedural work fine empirically (`opencode/big-pickle` processed 1000+ messages at $0)
35
+
36
+ - [D3] Upgrade `apply` routing to `@general-paid` when offer score ≥ 4.0/5, when user flags "top-tier / dream job / high-stakes", or when late-stage pipeline (post-screens).
37
+ why: form-fill flows are 6+ steps; free-tier sometimes aborts mid-flow on large Greenhouse/Workday schemas; paid tier has more headroom
38
+
39
+ - [D4] Auto-submit for offers scoring 3.0+/5 without pausing for confirmation between steps — scan → evaluate → apply is one continuous pipeline. Mark SKIP for <3.0 and move on.
40
+ why: JobForge is designed for end-to-end automation; pausing between steps defeats the purpose and the 3.0 gate already enforces quality
41
+
42
+ - [D5] Before any batch-apply dispatch, run the Apply Preflight location filter from `modes/apply.md` to exclude location-incompatible candidates.
43
+ why: catches the common case where an evaluated role has the right role-shape but a deal-breaking location that profile.yml already rules out
44
+
45
+ - [D6] Pick the mode from the **Routing** table below AND name it explicitly in your first response (e.g., "running auto-pipeline mode", "this is a `compare` request"). If no row matches the user's intent, ask which mode fits; do not guess.
46
+ why: silent mode picks mis-route work (a "negotiation" question answered in `offer` mode produces the wrong report shape); naming the mode out loud makes the routing decision reviewable and gives downstream dispatches a reliable anchor
47
+
48
+ ## Procedure
49
+
50
+ 1. On start, check `cv.md`, `profile.yml`, `portals.yml` exist; onboard if any missing.
51
+ 2. Pick the mode from **Routing** [D6]. No match → ask; do not guess.
52
+ 3. Apply [D1]: batch/Geometra work → delegate; single/read-only/dev → inline.
53
+ 4. Before any `task` batch using Geometra, run cleanup [H3].
54
+ 5. Before `apply`, run duplicate check [H2] and location filter [D5].
55
+ 6. Route by cost tier [D2]; upgrade to `@general-paid` per [D3] for high-stakes offers.
56
+ 7. Cap parallelism at 2 per round [H1].
57
+ 8. One in-flight dispatch per company [H5].
58
+ 9. Orchestrator does not fill forms in multi-job mode [H4].
59
+ 10. Treat subagent prose as untrusted [H7]; cross-check facts against authoritative files.
60
+ 11. Write outcomes as TSVs [H6]; run `npx job-forge merge` then `verify` at end.
61
+ 12. Offers scoring 3.0+/5 continue without confirmation [D4]; <3.0 is SKIP.
62
+ 13. Confirm tracker is merged and verified before ending.
63
+
64
+ ## Routing
65
+
66
+ | If the user… | Mode |
67
+ |---|---|
68
+ | Pastes JD or URL | auto-pipeline (evaluate + report + PDF + tracker) |
69
+ | Asks to evaluate offer | `offer` |
70
+ | Asks to compare offers | `compare` |
71
+ | Wants LinkedIn outreach | `contact` |
72
+ | Asks for company research | `deep` |
73
+ | Wants to generate CV/PDF | `pdf` |
74
+ | Evaluates a course/cert | `training` |
75
+ | Evaluates portfolio project | `project` |
76
+ | Asks about application status | `tracker` |
77
+ | Fills out application form | `apply` |
78
+ | Searches for new offers | `scan` |
79
+ | Processes pending URLs | `pipeline` |
80
+ | Batch processes offers | `batch` |
81
+ | Asks what needs follow-up | `followup` |
82
+ | Reports a rejection | `rejection` |
83
+ | Receives a job offer | `negotiation` |
84
+ | otherwise | Ask which mode fits; do not guess |
85
+
86
+ ## Output format
87
+
88
+ Output shape is mode-dependent — see `modes/{mode}.md` for each mode's expected output. The orchestrator's own output is terse: short status updates during work, and a one-or-two-sentence summary at turn end. No mid-work narration of individual tool calls.
89
+
90
+ ---
91
+
92
+ # Reference
93
+
94
+ Sections below are context, rationale, runbooks, and portal-specific empirical notes. The **Hard limits**, **Defaults**, **Procedure**, and **Routing** above are the contract; the material below is what the orchestrator and each mode consult during execution.
31
95
 
32
96
  ---
33
97
 
@@ -207,24 +271,7 @@ JobForge is designed to be customized by YOU (opencode). When the user asks you
207
271
 
208
272
  ### Skill Modes
209
273
 
210
- | If the user... | Mode |
211
- |----------------|------|
212
- | Pastes JD or URL | auto-pipeline (evaluate + report + PDF + tracker) |
213
- | Asks to evaluate offer | `offer` |
214
- | Asks to compare offers | `compare` |
215
- | Wants LinkedIn outreach | `contact` |
216
- | Asks for company research | `deep` |
217
- | Wants to generate CV/PDF | `pdf` |
218
- | Evaluates a course/cert | `training` |
219
- | Evaluates portfolio project | `project` |
220
- | Asks about application status | `tracker` |
221
- | Fills out application form | `apply` |
222
- | Searches for new offers | `scan` |
223
- | Processes pending URLs | `pipeline` |
224
- | Batch processes offers | `batch` |
225
- | Asks what needs follow-up | `followup` |
226
- | Reports a rejection | `rejection` |
227
- | Receives a job offer | `negotiation` |
274
+ Mode routing is specified in the top-level **## Routing** section. Each mode is implemented in `modes/{mode}.md` — consult those files for per-mode prompts, state, and expected outputs.
228
275
 
229
276
  ### CV Source of Truth
230
277
 
@@ -369,11 +416,13 @@ These blocks come from two distinct root causes and require different responses:
369
416
 
370
417
  **Known-block Ashby tenants (2026-04-19 empirical observations).** These tenants fired class B on every attempted submit from a headless datacenter-IP proxy. Orchestrators planning apply dispatches should assume these tenants will Fail in headless — prioritize other portals, or skip same-tenant siblings after a confirmed class B to avoid burning subagent slots:
371
418
 
372
- - Vellum, Linear, Vanta, River Financial, Higharc, Trace Labs, Solace Health, Unstructured, ClickUp, Zapier, Deepgram, Ramp, WorkOS, **Ashby (self-tenant)**, **Perplexity**
419
+ - Vellum, Linear, Vanta, River Financial, Higharc, Trace Labs, Solace Health, Unstructured, ClickUp, Zapier, Deepgram, Ramp, WorkOS, Ashby (self-tenant), Perplexity, **Goody**, **Starbridge**, **Graphite**, **Prompt Health**, **Vantage**
373
420
 
374
421
  **Known class-A-compatible Ashby tenants (same observations).** These tenants accepted headless submits cleanly, often with `imeFriendly: true` making the difference on the text-field subset:
375
422
 
376
- - Supabase, LangChain, Poolside, Runway Financial, **Sentry**, **Cognition**
423
+ - Supabase, LangChain, Poolside, Runway Financial, Sentry, Cognition
424
+
425
+ **Base rate for untested Ashby tenants (5/5 tested 2026-04-19 cycle 4 = class B).** The prior today is ~80-90% of untested Ashby tenants fingerprint-block headless submits. Orchestrators should treat any tenant not on the class-A-compatible list as likely class B — still dispatch to collect the data point, but don't burn multiple sibling-role slots on the same Ashby tenant.
377
426
 
378
427
  The pattern is tenant configuration, not role or company size. Lists drift as tenants tune their anti-bot — treat as probabilistic priors, not hard rules.
379
428
 
@@ -391,6 +440,29 @@ The pattern is tenant configuration, not role or company size. Lists drift as te
391
440
 
392
441
  **`geometra_fill_otp` char-drop on first fill.** Occasionally `fill_otp` lands only the first character of an 8-char code (seen on Instacart, 2026-04-19). Recovery: click the first cell to focus, then re-issue `fill_otp` with `perCharDelayMs: 120`. The form usually auto-submits once all 8 cells are populated.
393
442
 
443
+ **Breezy portal — tenant-dependent, native `<select>`, resume-auto-parse is primary.** A subset of companies (Avantos AI, Courted, Instinct Science confirmed 2026-04-19) host applications on `*.breezy.hr` or `applytojob.com`. Empirical rules:
444
+
445
+ - **Class is per-tenant, not uniform.** Avantos (Failed 2026-04-19 #854) returned Breezy's own "It looks like maybe you've already applied to this job?" banner from IP fingerprinting, even on a first submit — distinct failure mode from Ashby's "flagged as possible spam". Courted (Applied 2026-04-19 #855) went through cleanly on the same session. Don't pre-skip Breezy; the outcome is tenant-specific.
446
+ - **Native `<select>` elements, not React comboboxes.** `geometra_pick_listbox_option` sets the visible display but NOT the underlying form state — submit will fail with "A response is required" on every combobox. Use `geometra_select_option` with x,y + label value for every choice field on Breezy.
447
+ - **Resume-auto-parse carries the signal.** After resume upload, Breezy auto-parses work history and education into structured rows. Do NOT Add/Delete position rows via Geometra — row mutations reshuffle fieldIds mid-flow, sequential `fill_fields` calls land in wrong rows, and upstream pollution corrupts earlier positions. Trust the parsed resume and fill only Personal Details + salary.
448
+
449
+ **Mailto-apply portals — direct email via gmail-mcp `attachments`.** A subset of HN-listed companies (CoPlane, Gambit Robotics, Rinse, Digital Health Strategies confirmed 2026-04-19) don't host an ATS form — their careers page instructs sending resume by email to `founders@...` / `jobs@...` / `contact@...`. Detection: WebFetch the careers URL; if the Apply link resolves to `mailto:` or the copy reads "email your resume to …", skip Geometra entirely.
450
+
451
+ Use `gmail_send_message` with the `attachments` parameter (available from `@razroo/gmail-mcp@1.8.0`):
452
+
453
+ ```
454
+ gmail_send_message({
455
+ to: ["founders@example.com"],
456
+ subject: "Application — Forward Deployed AI Engineer — Charlie Greenman (Austin)",
457
+ body: "<Section G pitch, 4-8 short paragraphs>",
458
+ attachments: [{ path: "/abs/path/to/Charlie-Greenman-CV.pdf" }]
459
+ })
460
+ ```
461
+
462
+ The MCP reads the file from disk and builds multipart/mixed MIME server-side — do NOT manually base64-encode a PDF into the `raw` parameter (the inline blob exceeds tool-call argument limits for any real attachment). Subject is auto MIME-encoded for non-ASCII (em-dash, smart quotes) by the same version. For older gmail-mcp versions (< 1.8.0) the only path was a direct Gmail API POST with the stored OAuth token at `~/.gmail-mcp/credentials.json` — upgrade if you can.
463
+
464
+ Mark Applied with note `mailto portal — sent via gmail_send_message; Gmail msgId {id}`. Verify via `gmail_get_message` that the attachment intact-size matches what was on disk before writing the TSV.
465
+
394
466
  ### Greenhouse Bot-Detection Honeypots
395
467
 
396
468
  Some Greenhouse tenants (Grafana Labs confirmed, 2026-04-19) inject a honeypot-style single-pick question on the application form, rendered as a listbox labeled something like "Which of the following best describes you?" with options resembling "I am a human being / I am a bot / I am a robot".
package/CLAUDE.md CHANGED
@@ -1,33 +1,97 @@
1
- # JobForge -- AI Job Search Pipeline
1
+ # Agent: job-forge
2
2
 
3
- ## Hard Limits NEVER exceed these numbers
3
+ AI-powered job search pipeline: scans portals, evaluates offers, generates CVs via Geometra MCP, applies to jobs, tracks applications across day files. Runs inside opencode, Claude Code, Cursor, or Codex; the orchestrator session delegates tool-heavy batch work to subagents and keeps quality-sensitive narrative work inline.
4
4
 
5
- The Hard Limits below are non-negotiable numeric rules. If you catch yourself about to violate one, STOP and restructure.
5
+ ## Hard limits
6
6
 
7
- 1. **Max parallel subagents: 2.** Never emit 3+ `task` tool calls in a single message. For N jobs, run `ceil(N/2)` sequential rounds of 2. No exceptions not for "urgent", not for "the user asked for 10".
8
- 2. **Max 1 application per company+role.** Before every `task` dispatch for `apply`, Grep ALL of the following for the URL and for `company+role`:
9
- - `data/pipeline.md`
10
- - all `data/applications/*.md` day files (not just today's — prior-day Applies count too)
11
- - `batch/tracker-additions/*.tsv` (pending outcomes not yet merged)
12
- - `batch/tracker-additions/merged/*.tsv` (outcomes already consumed into day files — catches same-day earlier-batch Applies that merge collapsed into an existing row)
7
+ - [H1] Max 2 parallel `task` dispatches per message. For N jobs, run `ceil(N/2)` sequential rounds of 2. Applies in all modes, for all user phrasings ("urgent", "apply to 10 jobs now").
8
+ why: higher parallelism blows through free-tier rate limits; each subagent requires post-cleanup and racing more than 2 reliably loses at least one result
13
9
 
14
- If any source shows an APPLIED / Applied outcome for this URL or company+role, skip that job and do not dispatch. **Why merged/ matters**: when two batches in the same day target the same role, `npx job-forge merge` updates the existing day-file row instead of creating a new one — so `grep data/applications/*.md` for the higher report number misses the earlier apply. The merged TSV is the only place the newer attempt's breadcrumb remains.
15
- 3. **Always clean Geometra sessions before dispatching.** Before every round of `task` dispatches that will use Geometra, call `geometra_list_sessions` then `geometra_disconnect({closeBrowser: true})`. Every round. The disconnect is a no-op when the pool is empty.
16
- 4. **Orchestrator does NOT fill forms.** This session MUST NOT call `geometra_fill_form`, `geometra_run_actions`, `geometra_pick_listbox_option`, or `geometra_fill_otp` when handling a multi-job request. If you need to, it means you MUST have delegated — `task` out the remaining work instead.
17
- 5. **Re-dispatch only AFTER the previous subagent returns.** Never fire the same company's `task` twice while the first is still in-flight. Wait for the return value, then decide if a retry is warranted.
18
- 6. **Application outcomes flow through TSVs, not `data/pipeline.md`.** When a subagent returns APPLIED / FAILED / SKIP, the outcome goes to `batch/tracker-additions/{num}-{slug}.tsv`. `npx job-forge merge` then consumes the TSVs into the correct `data/applications/YYYY-MM-DD.md` day file. `data/pipeline.md` only tracks URL inbox state (`[ ]` pending → `[x]` processed). **NEVER append APPLIED / FAILED status lines to `pipeline.md`** — that's the day file's job, via the TSV pathway. After any multi-apply run, the orchestrator MUST run `npx job-forge merge` followed by `npx job-forge verify` before ending the session.
19
- 7. **Load-bearing facts passed to downstream subagents must come from a file, not from a prior subagent's prose.** A URL, score, email ID, confirmation page snippet, JD salary range, exact answer submitted to a form question, or any other specific value that a downstream subagent will act on MUST originate from one of:
20
- - `data/pipeline.md` (URL inbox state)
21
- - `data/scan-history.tsv` (scan provenance)
22
- - `batch/scan-output-*.md` (scan-ranked candidates)
23
- - A report file (`reports/{num}-*.md`) with authoritative headers (`**URL:**`, `**Score:**`, etc.)
24
- - A TSV in `batch/tracker-additions/` (per-apply outcomes)
10
+ - [H2] Max 1 application per company+role. Before every `apply` dispatch, grep all four sources for the URL and for `company+role`: `data/pipeline.md`, all `data/applications/*.md` day files, `batch/tracker-additions/*.tsv`, `batch/tracker-additions/merged/*.tsv`. If any source shows APPLIED / Applied, skip the dispatch.
11
+ why: 2026-04 same-day batch collision when two batches target the same role, `npx job-forge merge` updates the existing day-file row rather than appending, so grepping day files alone misses earlier-batch applies; merged/*.tsv is the only place the breadcrumb remains
25
12
 
26
- **Not trustworthy by default**: anything quoted from a subagent's return message, any ID or score the orchestrator "remembers" from prose, any page-content snippet reproduced from a subagent's narrative. Subagents can hallucinate plausible-looking IDs, scores, and confirmation text. Before passing any such fact to another subagent, cross-check it exists in one of the authoritative files above, OR instruct the dispatching subagent to write its output to a structured file and re-read from that file.
13
+ - [H3] Before every batch of `task` dispatches that will use Geometra, call `geometra_list_sessions` then `geometra_disconnect({closeBrowser: true})`. Every round, no exceptions. Name this cleanup as an explicit "step 0" in your first-response plan for any multi-apply request it is the most frequently skipped guardrail in practice, and skipping it produces cascade "Not connected" failures on the next dispatch.
14
+ why: if any prior subagent aborted mid-flow, its Chromium session stays stuck in the MCP pool and the next `geometra_connect` fails with "Not connected"; the disconnect is a no-op when the pool is empty but a poison-cure when it isn't; vocalizing it up-front doubles the odds it actually runs
27
15
 
28
- **Why**: on 2026-04-18, a scan subagent returned 30 fabricated Greenhouse IDs in prose (correct role titles, plausible-looking invented IDs that didn't exist in the API). The orchestrator dispatched 30 downstream subagents that all hit 404s. Verification rules downstream (Hard Limit #6, API-first verify) caught the symptom. This rule prevents the *shape* of the bug hallucinations propagating through prose handoffs across all quantitative / identifier / specific-fact claims, not just URLs.
16
+ - [H4] In multi-job mode, the orchestrator session MUST NOT call `geometra_fill_form`, `geometra_run_actions`, `geometra_pick_listbox_option`, or `geometra_fill_otp` directly. Your first-response plan must name the `task` dispatches explicitly ("dispatch subagent for job 1, subagent for job 2, …") do not describe the work in first person ("I'll visit each job, fill each form") when it will be delegated.
17
+ why: repeated Geometra calls in the orchestrator bloat the cache prefix — this is the 2026-04 "apply to 20 jobs" 341-msg incident where each turn re-processed 100K+ fresh tokens instead of reading from cache; first-person narration is a leading indicator that the agent is mentally queueing work for itself rather than a subagent
29
18
 
30
- Everything below is context and rationale. These seven numbers are the rules.
19
+ - [H5] Re-dispatch the same company only AFTER the previous subagent returns. Never fire the same `task` twice while the first is still in flight.
20
+ why: two in-flight subagents for the same URL race on Geometra sessions and on tracker TSV writes, corrupting state and sometimes double-submitting
21
+
22
+ - [H6] Application outcomes flow through `batch/tracker-additions/*.tsv`, not `data/pipeline.md`. After any multi-apply run, the orchestrator MUST run `npx job-forge merge` then `npx job-forge verify` before ending the session.
23
+ why: `pipeline.md` is the URL inbox (`[ ]` pending → `[x]` processed); `data/applications/YYYY-MM-DD.md` is the outcome log; the TSV pathway is the only safe bridge because `merge` handles column order and duplicate detection
24
+
25
+ - [H7] Load-bearing facts passed to downstream subagents must originate from a file, not from prior subagent prose. Authoritative sources: `data/pipeline.md`, `data/scan-history.tsv`, `batch/scan-output-*.md`, `reports/{num}-*.md` with `**URL:**` / `**Score:**` headers, `batch/tracker-additions/*.tsv`.
26
+ why: 2026-04-18 scan subagent returned 30 fabricated Greenhouse IDs in prose (plausible-looking, non-existent); orchestrator dispatched 30 downstream subagents that all 404'd. Subagents can hallucinate IDs, scores, and confirmation text — round-trip through a file or don't trust the value
27
+
28
+ ## Defaults
29
+
30
+ - [D1] Delegate to a subagent (`task`) only when the work involves repeated tool-heavy steps that bloat the cache prefix: applying to N≥2 jobs, batch scans hitting ≥3 companies, or any "apply to… / process pipeline / batch evaluate" user phrasing. Single-offer evals, dev work, file edits, `tracker` mode, single-URL checks, and one-shot questions stay inline.
31
+ why: iso-trace showed 0.25% Agent calls across 5174 turns under a prior over-broad "delegate before 2nd tool call" rule — the rule was ignored in practice; narrowing matches the original cache-bust incident
32
+
33
+ - [D2] Route subagent work by cost tier. `@general-free`: procedural — form-fill, TSV merge, verify, OTP retrieval, portal scan metadata extraction, one-shot structured-field transforms. `@general-paid`: quality-sensitive — offer evaluation narrative Blocks A-F, cover letters, "Why X?" answers, STAR interview stories, LinkedIn outreach. `@glm-minimal`: narrow ≤5K-input one-shot extract/classify jobs that do not need context.
34
+ why: GLM 5.1 doesn't discount cache reads so procedural work there costs ~10×; free-tier models handle procedural work fine empirically (`opencode/big-pickle` processed 1000+ messages at $0)
35
+
36
+ - [D3] Upgrade `apply` routing to `@general-paid` when offer score ≥ 4.0/5, when user flags "top-tier / dream job / high-stakes", or when late-stage pipeline (post-screens).
37
+ why: form-fill flows are 6+ steps; free-tier sometimes aborts mid-flow on large Greenhouse/Workday schemas; paid tier has more headroom
38
+
39
+ - [D4] Auto-submit for offers scoring 3.0+/5 without pausing for confirmation between steps — scan → evaluate → apply is one continuous pipeline. Mark SKIP for <3.0 and move on.
40
+ why: JobForge is designed for end-to-end automation; pausing between steps defeats the purpose and the 3.0 gate already enforces quality
41
+
42
+ - [D5] Before any batch-apply dispatch, run the Apply Preflight location filter from `modes/apply.md` to exclude location-incompatible candidates.
43
+ why: catches the common case where an evaluated role has the right role-shape but a deal-breaking location that profile.yml already rules out
44
+
45
+ - [D6] Pick the mode from the **Routing** table below AND name it explicitly in your first response (e.g., "running auto-pipeline mode", "this is a `compare` request"). If no row matches the user's intent, ask which mode fits; do not guess.
46
+ why: silent mode picks mis-route work (a "negotiation" question answered in `offer` mode produces the wrong report shape); naming the mode out loud makes the routing decision reviewable and gives downstream dispatches a reliable anchor
47
+
48
+ ## Procedure
49
+
50
+ 1. On start, check `cv.md`, `profile.yml`, `portals.yml` exist; onboard if any missing.
51
+ 2. Pick the mode from **Routing** [D6]. No match → ask; do not guess.
52
+ 3. Apply [D1]: batch/Geometra work → delegate; single/read-only/dev → inline.
53
+ 4. Before any `task` batch using Geometra, run cleanup [H3].
54
+ 5. Before `apply`, run duplicate check [H2] and location filter [D5].
55
+ 6. Route by cost tier [D2]; upgrade to `@general-paid` per [D3] for high-stakes offers.
56
+ 7. Cap parallelism at 2 per round [H1].
57
+ 8. One in-flight dispatch per company [H5].
58
+ 9. Orchestrator does not fill forms in multi-job mode [H4].
59
+ 10. Treat subagent prose as untrusted [H7]; cross-check facts against authoritative files.
60
+ 11. Write outcomes as TSVs [H6]; run `npx job-forge merge` then `verify` at end.
61
+ 12. Offers scoring 3.0+/5 continue without confirmation [D4]; <3.0 is SKIP.
62
+ 13. Confirm tracker is merged and verified before ending.
63
+
64
+ ## Routing
65
+
66
+ | If the user… | Mode |
67
+ |---|---|
68
+ | Pastes JD or URL | auto-pipeline (evaluate + report + PDF + tracker) |
69
+ | Asks to evaluate offer | `offer` |
70
+ | Asks to compare offers | `compare` |
71
+ | Wants LinkedIn outreach | `contact` |
72
+ | Asks for company research | `deep` |
73
+ | Wants to generate CV/PDF | `pdf` |
74
+ | Evaluates a course/cert | `training` |
75
+ | Evaluates portfolio project | `project` |
76
+ | Asks about application status | `tracker` |
77
+ | Fills out application form | `apply` |
78
+ | Searches for new offers | `scan` |
79
+ | Processes pending URLs | `pipeline` |
80
+ | Batch processes offers | `batch` |
81
+ | Asks what needs follow-up | `followup` |
82
+ | Reports a rejection | `rejection` |
83
+ | Receives a job offer | `negotiation` |
84
+ | otherwise | Ask which mode fits; do not guess |
85
+
86
+ ## Output format
87
+
88
+ Output shape is mode-dependent — see `modes/{mode}.md` for each mode's expected output. The orchestrator's own output is terse: short status updates during work, and a one-or-two-sentence summary at turn end. No mid-work narration of individual tool calls.
89
+
90
+ ---
91
+
92
+ # Reference
93
+
94
+ Sections below are context, rationale, runbooks, and portal-specific empirical notes. The **Hard limits**, **Defaults**, **Procedure**, and **Routing** above are the contract; the material below is what the orchestrator and each mode consult during execution.
31
95
 
32
96
  ---
33
97
 
@@ -207,24 +271,7 @@ JobForge is designed to be customized by YOU (opencode). When the user asks you
207
271
 
208
272
  ### Skill Modes
209
273
 
210
- | If the user... | Mode |
211
- |----------------|------|
212
- | Pastes JD or URL | auto-pipeline (evaluate + report + PDF + tracker) |
213
- | Asks to evaluate offer | `offer` |
214
- | Asks to compare offers | `compare` |
215
- | Wants LinkedIn outreach | `contact` |
216
- | Asks for company research | `deep` |
217
- | Wants to generate CV/PDF | `pdf` |
218
- | Evaluates a course/cert | `training` |
219
- | Evaluates portfolio project | `project` |
220
- | Asks about application status | `tracker` |
221
- | Fills out application form | `apply` |
222
- | Searches for new offers | `scan` |
223
- | Processes pending URLs | `pipeline` |
224
- | Batch processes offers | `batch` |
225
- | Asks what needs follow-up | `followup` |
226
- | Reports a rejection | `rejection` |
227
- | Receives a job offer | `negotiation` |
274
+ Mode routing is specified in the top-level **## Routing** section. Each mode is implemented in `modes/{mode}.md` — consult those files for per-mode prompts, state, and expected outputs.
228
275
 
229
276
  ### CV Source of Truth
230
277
 
@@ -369,11 +416,13 @@ These blocks come from two distinct root causes and require different responses:
369
416
 
370
417
  **Known-block Ashby tenants (2026-04-19 empirical observations).** These tenants fired class B on every attempted submit from a headless datacenter-IP proxy. Orchestrators planning apply dispatches should assume these tenants will Fail in headless — prioritize other portals, or skip same-tenant siblings after a confirmed class B to avoid burning subagent slots:
371
418
 
372
- - Vellum, Linear, Vanta, River Financial, Higharc, Trace Labs, Solace Health, Unstructured, ClickUp, Zapier, Deepgram, Ramp, WorkOS, **Ashby (self-tenant)**, **Perplexity**
419
+ - Vellum, Linear, Vanta, River Financial, Higharc, Trace Labs, Solace Health, Unstructured, ClickUp, Zapier, Deepgram, Ramp, WorkOS, Ashby (self-tenant), Perplexity, **Goody**, **Starbridge**, **Graphite**, **Prompt Health**, **Vantage**
373
420
 
374
421
  **Known class-A-compatible Ashby tenants (same observations).** These tenants accepted headless submits cleanly, often with `imeFriendly: true` making the difference on the text-field subset:
375
422
 
376
- - Supabase, LangChain, Poolside, Runway Financial, **Sentry**, **Cognition**
423
+ - Supabase, LangChain, Poolside, Runway Financial, Sentry, Cognition
424
+
425
+ **Base rate for untested Ashby tenants (5/5 tested 2026-04-19 cycle 4 = class B).** The prior today is ~80-90% of untested Ashby tenants fingerprint-block headless submits. Orchestrators should treat any tenant not on the class-A-compatible list as likely class B — still dispatch to collect the data point, but don't burn multiple sibling-role slots on the same Ashby tenant.
377
426
 
378
427
  The pattern is tenant configuration, not role or company size. Lists drift as tenants tune their anti-bot — treat as probabilistic priors, not hard rules.
379
428
 
@@ -391,6 +440,29 @@ The pattern is tenant configuration, not role or company size. Lists drift as te
391
440
 
392
441
  **`geometra_fill_otp` char-drop on first fill.** Occasionally `fill_otp` lands only the first character of an 8-char code (seen on Instacart, 2026-04-19). Recovery: click the first cell to focus, then re-issue `fill_otp` with `perCharDelayMs: 120`. The form usually auto-submits once all 8 cells are populated.
393
442
 
443
+ **Breezy portal — tenant-dependent, native `<select>`, resume-auto-parse is primary.** A subset of companies (Avantos AI, Courted, Instinct Science confirmed 2026-04-19) host applications on `*.breezy.hr` or `applytojob.com`. Empirical rules:
444
+
445
+ - **Class is per-tenant, not uniform.** Avantos (Failed 2026-04-19 #854) returned Breezy's own "It looks like maybe you've already applied to this job?" banner from IP fingerprinting, even on a first submit — distinct failure mode from Ashby's "flagged as possible spam". Courted (Applied 2026-04-19 #855) went through cleanly on the same session. Don't pre-skip Breezy; the outcome is tenant-specific.
446
+ - **Native `<select>` elements, not React comboboxes.** `geometra_pick_listbox_option` sets the visible display but NOT the underlying form state — submit will fail with "A response is required" on every combobox. Use `geometra_select_option` with x,y + label value for every choice field on Breezy.
447
+ - **Resume-auto-parse carries the signal.** After resume upload, Breezy auto-parses work history and education into structured rows. Do NOT Add/Delete position rows via Geometra — row mutations reshuffle fieldIds mid-flow, sequential `fill_fields` calls land in wrong rows, and upstream pollution corrupts earlier positions. Trust the parsed resume and fill only Personal Details + salary.
448
+
449
+ **Mailto-apply portals — direct email via gmail-mcp `attachments`.** A subset of HN-listed companies (CoPlane, Gambit Robotics, Rinse, Digital Health Strategies confirmed 2026-04-19) don't host an ATS form — their careers page instructs sending resume by email to `founders@...` / `jobs@...` / `contact@...`. Detection: WebFetch the careers URL; if the Apply link resolves to `mailto:` or the copy reads "email your resume to …", skip Geometra entirely.
450
+
451
+ Use `gmail_send_message` with the `attachments` parameter (available from `@razroo/gmail-mcp@1.8.0`):
452
+
453
+ ```
454
+ gmail_send_message({
455
+ to: ["founders@example.com"],
456
+ subject: "Application — Forward Deployed AI Engineer — Charlie Greenman (Austin)",
457
+ body: "<Section G pitch, 4-8 short paragraphs>",
458
+ attachments: [{ path: "/abs/path/to/Charlie-Greenman-CV.pdf" }]
459
+ })
460
+ ```
461
+
462
+ The MCP reads the file from disk and builds multipart/mixed MIME server-side — do NOT manually base64-encode a PDF into the `raw` parameter (the inline blob exceeds tool-call argument limits for any real attachment). Subject is auto MIME-encoded for non-ASCII (em-dash, smart quotes) by the same version. For older gmail-mcp versions (< 1.8.0) the only path was a direct Gmail API POST with the stored OAuth token at `~/.gmail-mcp/credentials.json` — upgrade if you can.
463
+
464
+ Mark Applied with note `mailto portal — sent via gmail_send_message; Gmail msgId {id}`. Verify via `gmail_get_message` that the attachment intact-size matches what was on disk before writing the TSV.
465
+
394
466
  ### Greenhouse Bot-Detection Honeypots
395
467
 
396
468
  Some Greenhouse tenants (Grafana Labs confirmed, 2026-04-19) inject a honeypot-style single-pick question on the application form, rendered as a listbox labeled something like "Which of the following best describes you?" with options resembling "I am a human being / I am a bot / I am a robot".
package/bin/sync.mjs CHANGED
@@ -53,17 +53,24 @@ if (PROJECT_DIR === PKG_ROOT) {
53
53
 
54
54
  // Each entry: { source (inside harness), target (inside consumer project) }
55
55
  const links = [
56
- // Cursor: MCP servers + always-apply rule (harness-level). Consumers can
57
- // add their own rules in .cursor/rules/ alongside this one.
56
+ // Cursor: MCP servers, harness-level always-apply rule, per-agent
57
+ // @-referenceable rules, and an iso-route advisory README.
58
58
  { src: '.cursor/mcp.json', dst: '.cursor/mcp.json' },
59
59
  { src: '.cursor/rules/main.mdc', dst: '.cursor/rules/main.mdc' },
60
+ { src: '.cursor/rules/agent-general-free.mdc', dst: '.cursor/rules/agent-general-free.mdc' },
61
+ { src: '.cursor/rules/agent-general-paid.mdc', dst: '.cursor/rules/agent-general-paid.mdc' },
62
+ { src: '.cursor/rules/agent-glm-minimal.mdc', dst: '.cursor/rules/agent-glm-minimal.mdc' },
63
+ { src: '.cursor/iso-route.md', dst: '.cursor/iso-route.md' },
60
64
 
61
- // Claude Code: MCP config (.mcp.json is what claude-code reads for
62
- // project-scoped MCP). No subagents/commands emitted because iso/agents/
63
- // and iso/commands/ are flagged claude: skip.
65
+ // Claude Code: MCP + per-agent subagent definitions + default-model
66
+ // settings + iso-route resolved role map.
64
67
  { src: '.mcp.json', dst: '.mcp.json' },
68
+ { src: '.claude/agents', dst: '.claude/agents' },
69
+ { src: '.claude/settings.json', dst: '.claude/settings.json' },
70
+ { src: '.claude/iso-route.resolved.json', dst: '.claude/iso-route.resolved.json' },
65
71
 
66
- // Codex: MCP config.
72
+ // Codex: config.toml contains MCP + model + [profiles.<role>] + provider
73
+ // blocks (merged by iso-harness >=0.5.0 after iso-route writes first).
67
74
  { src: '.codex/config.toml', dst: '.codex/config.toml' },
68
75
 
69
76
  // OpenCode: skill router + subagent definitions. Users can override any
@@ -71,6 +78,10 @@ const links = [
71
78
  { src: '.opencode/skills/job-forge.md', dst: '.opencode/skills/job-forge.md' },
72
79
  { src: '.opencode/agents', dst: '.opencode/agents' },
73
80
 
81
+ // Model policy (source of truth for iso-route). Consumers can edit and
82
+ // re-run `npm run build:config` in their project to swap model picks.
83
+ { src: 'models.yaml', dst: 'models.yaml' },
84
+
74
85
  // Shared content directories referenced by opencode.json instructions +
75
86
  // skill router (Read's modes/{mode}.md, etc).
76
87
  { src: 'modes', dst: 'modes' },
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  description: Procedural worker on free-tier model. Use for form filling via Geometra, tracker updates, TSV merges, scan dedup, OTP retrieval, and other mechanical/scripted tasks where quality-sensitive text generation is NOT required.
3
3
  targets:
4
- claude: skip
5
- cursor: skip
6
- codex: skip
4
+ # Claude Code / Cursor / Codex: no inline override — iso-route's resolved
5
+ # role map stamps model from models.yaml (role name = filename slug).
6
+ # OpenCode keeps its provider-specific model identifier inline.
7
7
  opencode:
8
8
  mode: subagent
9
9
  model: opencode/big-pickle
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  description: Quality-sensitive worker on paid model. Use for offer evaluation narratives (Blocks A-F), cover letter generation, "Why X?" form answers, interview STAR stories, and other tasks where writing quality and judgment matter.
3
3
  targets:
4
- claude: skip
5
- cursor: skip
6
- codex: skip
4
+ # Claude Code / Cursor / Codex: no inline override — iso-route's resolved
5
+ # role map stamps model from models.yaml (role name = filename slug).
6
+ # OpenCode keeps its provider-specific model identifier inline.
7
7
  opencode:
8
8
  mode: subagent
9
9
  model: opencode/glm-5.1
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  description: Narrow-scope extractor on free-tier model. Use for single-purpose tasks where the orchestrator passes the exact input and expects a small, structured output — e.g., "extract these 8 fields from this JD text" or "parse this form schema into a label→type map". NOT for multi-step workflows.
3
3
  targets:
4
- claude: skip
5
- cursor: skip
6
- codex: skip
4
+ # Claude Code / Cursor / Codex: no inline override — iso-route's resolved
5
+ # role map stamps model from models.yaml (role name = filename slug).
6
+ # OpenCode keeps its provider-specific model identifier inline.
7
7
  opencode:
8
8
  mode: subagent
9
9
  model: opencode/minimax-m2.5-free