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/.claude/agents/general-free.md +1 -0
- package/.claude/agents/general-paid.md +3 -1
- package/.codex/config.toml +5 -0
- package/.cursor/mcp.json +13 -0
- package/.cursor/rules/agent-general-free.mdc +1 -0
- package/.cursor/rules/agent-general-paid.mdc +3 -1
- package/.cursor/rules/main.mdc +25 -144
- package/.mcp.json +13 -0
- package/.opencode/agents/general-free.md +2 -0
- package/.opencode/agents/general-paid.md +15 -3
- package/.opencode/skills/job-forge.md +5 -1
- package/AGENTS.md +25 -144
- package/CLAUDE.md +25 -144
- package/README.md +7 -1
- package/docs/SETUP.md +1 -0
- package/iso/agents/general-free.md +2 -0
- package/iso/agents/general-paid.md +15 -3
- package/iso/commands/job-forge.md +5 -1
- package/iso/instructions.md +25 -144
- package/iso/mcp.json +9 -0
- package/modes/apply.md +29 -13
- package/modes/reference-geometra.md +3 -2
- package/modes/reference-portals.md +7 -6
- package/opencode.json +14 -0
- package/package.json +6 -3
- package/scripts/check-iso-smoke.mjs +43 -0
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
|
|
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
|
|
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.
|
|
58
|
-
9.
|
|
59
|
-
10.
|
|
60
|
-
11.
|
|
61
|
-
12. On
|
|
62
|
-
13.
|
|
63
|
-
14.
|
|
64
|
-
15.
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
79
|
-
2. When dispatching any subagent whose work involves a `geometra_connect` call,
|
|
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
|
|
86
|
-
2. If the task prompt
|
|
87
|
-
3.
|
|
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.
|
|
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.
|
|
93
|
-
"@razroo/iso-trace": "^0.
|
|
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
|
+
}
|