job-forge 2.14.7 → 2.14.9

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/modes/apply.md CHANGED
@@ -16,8 +16,8 @@ Live application assistant. Reads the active application form in Chrome (via Geo
16
16
  - [H4] Before dispatching the first subagent in a multi-job run, the orchestrator MUST call `geometra_list_sessions` then `geometra_disconnect({closeBrowser: true})`. Every dispatch-round, no exceptions.
17
17
  why: prior aborted subagents leave Chromium sessions stuck in the pool; next `geometra_connect` fails with "Not connected" (see root `[H3]`)
18
18
 
19
- - [H5] Max 2 parallel `task` dispatches per round. For N jobs, run `ceil(N/2)` sequential rounds of 2. Never emit 3+ dispatches in a single message.
20
- why: free-tier rate limits + subagent post-cleanup cost; racing more than 2 reliably loses at least one result (see root `[H1]`)
19
+ - [H5] Max 2 parallel `task` dispatches per round. For N jobs, run `ceil(N/2)` sequential rounds of 2. Never emit 3+ dispatches in a single message. Do not start the next round until both current-round subagents return final outcomes (`APPLIED`, `APPLY FAILED`, `SKIP`, `Discarded`, or a written TSV path); task/session ids are only launch receipts.
20
+ why: free-tier rate limits + subagent post-cleanup cost; racing more than 2 reliably loses at least one result (see root `[H1]`). A 2026-04-25 OpenCode trace launched round 2 while round 1 was still running, then lost two fallback recoveries
21
21
 
22
22
  ## Defaults
23
23
 
@@ -42,8 +42,14 @@ Live application assistant. Reads the active application form in Chrome (via Geo
42
42
  - [D6] Use `fieldLabel` over `fieldId` everywhere it works.
43
43
  why: labels are stable across DOM refreshes; IDs are regenerated
44
44
 
45
- - [D7] If the orchestrator's task prompt includes a `proxy` object (sourced from `config/profile.yml`), pass it verbatim into every `geometra_connect` call — including Call 3 of the recovery sequence. If absent, run without one; never invent a proxy URL.
46
- why: class-B Ashby / Cloudflare-fronted portals need a residential outbound IP; the fix is wired in Geometra MCP v1.59.0 but the orchestrator owns the config pipe. See "BYO Residential Proxy" in iso/instructions.md.
45
+ - [D7] If the orchestrator says a proxy is configured, read the top-level `proxy:` block from `config/profile.yml` and pass that object into every `geometra_connect` call — including Call 3 of the recovery sequence. If the task prompt includes a legacy inline `proxy` object, pass it through, but do not echo credentials in status text. If absent, run without one; never invent a proxy URL.
46
+ why: class-B Ashby / Cloudflare-fronted portals need a residential outbound IP; the fix is wired in Geometra MCP v1.59.0 but the orchestrator owns the config pipe. See "BYO Residential Proxy" in modes/reference-portals.md.
47
+
48
+ - [D8] Upgrade application routing to `@general-paid` when the offer score is ≥ 4.0/5, the user flags "top-tier", "dream job", or "high-stakes", or the candidate is late-stage/post-screen.
49
+ why: form-fill flows are 6+ steps; free-tier sometimes aborts mid-flow on large Greenhouse/Workday schemas; paid tier has more headroom
50
+
51
+ - [D9] If an upgraded `@general-paid` subagent fails with provider-side errors, re-dispatch the same URL once on `@general-free` before marking FAILED. Provider-side errors include Venice, Diem, Chutes, HTTP 402/429, insufficient credits/funds/balance, overload, and temporarily unavailable.
52
+ why: OpenCode paid-tier routing can still use free OpenRouter model IDs; backend pool limits are not evidence that a procedural free-tier worker cannot complete the same form after preflight gates pass
47
53
 
48
54
  ## Procedure
49
55
 
@@ -54,14 +60,16 @@ Live application assistant. Reads the active application form in Chrome (via Geo
54
60
  5. Compare role on screen vs evaluated role [D3].
55
61
  6. If different, pause for the candidate's decision [D3].
56
62
  7. Before dispatch, run Geometra cleanup [H4] and location filter [D1].
57
- 8. Extract form questions; classify each Section-G vs new.
58
- 9. Generate answers from Block B + Block F + Section G + JD.
59
- 10. Submit as ONE `run_actions` call [H1] using labels [D6] with `imeFriendly: true` [D4].
60
- 11. On session error, run the 4-step recovery; only one retry [H2].
61
- 12. On OTP prompt, fetch the code from Gmail via `gmail_get_message`.
62
- 13. Submit the OTP with `geometra_fill_otp` and click Submit.
63
- 14. Write outcome as `batch/tracker-additions/*.tsv` [H3].
64
- 15. Cap parallelism at 2 per round [H5]; one in-flight per company.
63
+ 8. Route high-stakes applications through `@general-paid` [D8].
64
+ 9. Extract form questions; classify each Section-G vs new.
65
+ 10. Generate answers from Block B + Block F + Section G + JD.
66
+ 11. Submit as ONE `run_actions` call [H1] using labels [D6] with `imeFriendly: true` [D4].
67
+ 12. On session error, run the 4-step recovery; only one retry [H2].
68
+ 13. On upgraded-provider failure, downgrade once to `@general-free` [D9].
69
+ 14. On OTP prompt, fetch the code from Gmail via `gmail_get_message`.
70
+ 15. Submit the OTP with `geometra_fill_otp` and click Submit.
71
+ 16. Write outcome as `batch/tracker-additions/*.tsv` [H3].
72
+ 17. Cap parallelism at 2 per round [H5]; one in-flight per company.
65
73
 
66
74
  ## Routing
67
75
 
@@ -107,6 +115,13 @@ Sections below are the detailed runbooks, decision tables, and portal-specific e
107
115
 
108
116
  **DO NOT dispatch 3+ `task` calls in one message.** Two is the absolute ceiling. This is non-negotiable, even when the user asks for "apply to 10 jobs" — that becomes 5 rounds of 2, not one message with 10 dispatches.
109
117
 
118
+ **A task/session id is not a result.** If OpenCode gives you a `ses_...`
119
+ id or title after dispatch, do not treat that as the subagent return.
120
+ Do not create another `task` to check it. Stop the round, report the
121
+ in-flight ids, and resume only after a real outcome is visible in the
122
+ subagent return or in an authoritative file (`batch/tracker-additions/`,
123
+ `batch/tracker-additions/merged/`, or the day file).
124
+
110
125
  For a single application interactively, carry on in the current session — the rule targets multi-job loops.
111
126
 
112
127
  ## Apply Preflight — Location Filter (orchestrator runs before dispatch)
@@ -168,7 +183,8 @@ Step 4 — For round in ceil(N/2):
168
183
  # ONE message, 1 or 2 task() calls. Never 3.
169
184
  task(apply to pair[0])
170
185
  task(apply to pair[1]) # only if pair has 2
171
- # WAIT for both returns. Do not proceed until both done.
186
+ # WAIT for both final outcomes. A session id is not completion.
187
+ # Do not dispatch round N+1 while round N is still in flight.
172
188
  Step 5 — Between rounds: geometra_list_sessions() + geometra_disconnect({closeBrowser: true})
173
189
  Step 6 — Reconcile outcomes (Hard Limit #6):
174
190
  bash: npx job-forge merge # TSVs → day file
@@ -188,7 +188,8 @@ Step 2: geometra_disconnect({ closeBrowser: true })
188
188
  Step 3: geometra_connect({ pageUrl: "<the URL the orchestrator gave you>", isolated: true, headless: true, slowMo: 350 })
189
189
  ```
190
190
 
191
- **If the orchestrator passed a `proxy` object in the task prompt** (sourced from `config/profile.yml`), add it to Step 3:
191
+ **If the orchestrator says proxy is configured,** read the top-level
192
+ `proxy:` block from `config/profile.yml` and add it to Step 3:
192
193
 
193
194
  ```
194
195
  Step 3: geometra_connect({
@@ -197,7 +198,7 @@ Step 3: geometra_connect({
197
198
  })
198
199
  ```
199
200
 
200
- Pass the proxy object through unchanged. Do NOT paraphrase or drop fields — `username`/`password`/`bypass` are optional, so only include what the orchestrator gave you. See the "BYO Residential Proxy" reference section for the why.
201
+ Pass the proxy object through unchanged. Do NOT paraphrase or drop fields — `username`/`password`/`bypass` are optional, so only include what exists in `config/profile.yml`. Do not echo proxy credentials in status text. See the "BYO Residential Proxy" reference section for the why.
201
202
 
202
203
  **DO NOT** skip Step 1 or Step 2. **DO NOT** think about whether it's needed. **DO NOT** look at `geometra_list_sessions` output and reason about it — just always call `geometra_disconnect({ closeBrowser: true })` next. The disconnect is a no-op if the pool is empty, and a poison-cure if it isn't.
203
204
 
@@ -75,16 +75,17 @@ See `config/profile.example.yml` for the commented-out template.
75
75
 
76
76
  **Orchestrator responsibilities:**
77
77
 
78
- 1. On session start, read `config/profile.yml` once. If a `proxy:` block is present, capture it as the `PROXY_CONFIG` for the session.
79
- 2. When dispatching any subagent whose work involves a `geometra_connect` call, include `PROXY_CONFIG` verbatim in the task prompt. Example dispatch prompt line: "Pass `proxy: { server: ..., username: ..., password: ..., bypass: ... }` to every `geometra_connect` call you make."
80
- 3. When the orchestrator itself opens a Chromium session (single-application interactive flow), include the same `proxy` object in its own `geometra_connect` call.
78
+ 1. On session start, read `config/profile.yml` once. If a `proxy:` block is present, remember that a proxy is configured, but do not paste username/password values into task prompts or user-visible status.
79
+ 2. When dispatching any subagent whose work involves a `geometra_connect` call, tell it to read `config/profile.yml` and pass the top-level `proxy:` block to every `geometra_connect` call. Example dispatch prompt line: "Proxy is configured; read `config/profile.yml` and pass its top-level `proxy:` object to every `geometra_connect` call."
80
+ 3. When the orchestrator itself opens a Chromium session (single-application interactive flow), include the same `proxy` object from `config/profile.yml` in its own `geometra_connect` call.
81
81
  4. If `proxy:` is absent from `profile.yml`, skip the param entirely. Do NOT invent a proxy URL or leave a stale placeholder.
82
82
 
83
83
  **Subagent responsibilities:**
84
84
 
85
- 1. If the task prompt includes a `proxy` object, pass it through to `geometra_connect` and any `geometra_prepare_browser` calls unchanged.
86
- 2. If the task prompt does NOT include a proxy object, run without one.
87
- 3. Never second-guess the proxy field if the orchestrator sourced it from `profile.yml`, it's authoritative.
85
+ 1. If the task prompt says proxy is configured, read `config/profile.yml` and pass the top-level `proxy:` object through to `geometra_connect` and any `geometra_prepare_browser` calls unchanged.
86
+ 2. If the task prompt includes a legacy inline `proxy` object, pass it through unchanged, but never print the credentials back in status text.
87
+ 3. If the task prompt does NOT mention a proxy and `config/profile.yml` has no `proxy:` block, run without one.
88
+ 4. Never second-guess the proxy field — if it comes from `profile.yml`, it's authoritative.
88
89
 
89
90
  ### When proxy use is load-bearing
90
91
 
package/opencode.json CHANGED
@@ -50,6 +50,20 @@
50
50
  "environment": {
51
51
  "DISABLE_HTTP": "true"
52
52
  }
53
+ },
54
+ "state-trace": {
55
+ "type": "local",
56
+ "command": [
57
+ "uvx",
58
+ "--from",
59
+ "state-trace[mcp]",
60
+ "state-trace-mcp"
61
+ ],
62
+ "environment": {
63
+ "STATE_TRACE_STORAGE_PATH": ".state-trace/memory.db",
64
+ "STATE_TRACE_NAMESPACE": "job-forge",
65
+ "STATE_TRACE_CAPACITY_LIMIT": "256"
66
+ }
53
67
  }
54
68
  },
55
69
  "plugin": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "job-forge",
3
- "version": "2.14.7",
3
+ "version": "2.14.9",
4
4
  "description": "AI-powered job search pipeline built on opencode",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,6 +28,7 @@
28
28
  "test:agentmd:baseline": "agentmd test iso/instructions.md --fixtures fixtures/instructions.yml --via claude-code --model claude-haiku-4-5 --concurrency 2 --trials 3 --format json --out fixtures/baseline.json",
29
29
  "test:agentmd:apply": "agentmd test modes/apply.md --fixtures fixtures/modes/apply.yml --via claude-code --model claude-haiku-4-5 --concurrency 2 --trials 3",
30
30
  "lint:agentmd:modes": "agentmd lint modes/apply.md",
31
+ "smoke:iso": "iso plan . && iso build . --dry-run && iso-route verify models.yaml && node scripts/check-iso-smoke.mjs . && JOBFORGE_ROOT=$PWD iso-eval run fixtures/iso-smoke/eval.yml",
31
32
  "build:config": "iso build .",
32
33
  "prepack": "iso build .",
33
34
  "release:check-source": "node ./scripts/release/check-source.mjs",
@@ -87,10 +88,12 @@
87
88
  "playwright": "^1.58.1"
88
89
  },
89
90
  "devDependencies": {
91
+ "@razroo/agentmd": "^0.3.0",
90
92
  "@razroo/iso": "^0.2.5",
93
+ "@razroo/iso-eval": "^0.4.0",
91
94
  "@razroo/iso-harness": "^0.6.1",
92
- "@razroo/iso-route": "^0.5.2",
93
- "@razroo/iso-trace": "^0.3.1",
95
+ "@razroo/iso-route": "^0.5.3",
96
+ "@razroo/iso-trace": "^0.4.0",
94
97
  "@razroo/opencode-model-fallback": "^0.3.1"
95
98
  }
96
99
  }
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+
5
+ const root = resolve(process.argv[2] ?? ".");
6
+ const files = {
7
+ instructions: readFileSync(resolve(root, "iso/instructions.md"), "utf8"),
8
+ apply: readFileSync(resolve(root, "modes/apply.md"), "utf8"),
9
+ models: readFileSync(resolve(root, "models.yaml"), "utf8"),
10
+ config: readFileSync(resolve(root, "iso/config.json"), "utf8"),
11
+ };
12
+
13
+ const checks = [
14
+ ["root defines H1-H7", () => every(files.instructions, ["[H1]", "[H2]", "[H3]", "[H4]", "[H5]", "[H6]", "[H7]"])],
15
+ ["H1 caps dispatches at 2", () => /Max 2 parallel `task` dispatches/.test(files.instructions)],
16
+ ["H2 checks all duplicate sources", () => every(files.instructions, ["data/pipeline.md", "data/applications/*.md", "batch/tracker-additions/*.tsv", "batch/tracker-additions/merged/*.tsv"])],
17
+ ["H3 names Geometra cleanup calls", () => every(files.instructions, ["geometra_list_sessions", "geometra_disconnect({closeBrowser: true})"])],
18
+ ["H4 blocks orchestrator form filling", () => every(files.instructions, ["MUST NOT call `geometra_fill_form`", "`geometra_run_actions`", "`geometra_fill_otp`"])],
19
+ ["H5 blocks same-company concurrent retry", () => every(files.instructions, ["Re-dispatch the same company only AFTER", "previous subagent returns"])],
20
+ ["H6 requires merge and verify", () => every(files.instructions, ["batch/tracker-additions/*.tsv", "npx job-forge merge", "npx job-forge verify"])],
21
+ ["H7 distrusts subagent prose", () => every(files.instructions, ["must originate from a file", "not from prior subagent prose"])],
22
+ ["shared prompt points to on-demand references", () => every(files.instructions, ["modes/{mode}.md", "modes/reference-setup.md", "modes/reference-portals.md", "modes/reference-geometra.md"])],
23
+ ["apply mode owns high-stakes upgrade", () => every(files.apply, ["[D8]", "@general-paid", "4.0/5", "high-stakes"])],
24
+ ["apply mode owns provider downgrade", () => every(files.apply, ["[D9]", "@general-free", "HTTP 402/429", "insufficient credits/funds/balance"])],
25
+ ["models policy extends free OpenRouter preset", () => /extends:\s*openrouter-free/.test(files.models)],
26
+ ["OpenCode fallback plugin is configured", () => every(files.config, ["opencodeModelFallback", "@razroo/opencode-model-fallback"])],
27
+ ];
28
+
29
+ const failures = checks
30
+ .filter(([, check]) => !check())
31
+ .map(([name]) => name);
32
+
33
+ if (failures.length > 0) {
34
+ console.error("JobForge iso smoke failed:");
35
+ for (const failure of failures) console.error(`- ${failure}`);
36
+ process.exit(1);
37
+ }
38
+
39
+ console.log(`JobForge iso smoke passed (${checks.length} checks).`);
40
+
41
+ function every(source, needles) {
42
+ return needles.every((needle) => source.includes(needle));
43
+ }