job-forge 2.11.0 → 2.13.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/.claude/agents/general-paid.md +6 -1
- package/.codex/config.toml +2 -7
- package/.cursor/rules/agent-general-paid.mdc +6 -1
- package/.cursor/rules/main.mdc +73 -1
- package/.opencode/agents/general-free.md +5 -1
- package/.opencode/agents/general-paid.md +11 -2
- package/.opencode/agents/glm-minimal.md +5 -1
- package/.opencode/opencode-model-fallback.json +11 -0
- package/AGENTS.md +73 -1
- package/CLAUDE.md +73 -1
- package/README.md +2 -2
- package/bin/create-job-forge.mjs +21 -0
- package/bin/sync.mjs +1 -0
- package/config/profile.example.yml +20 -0
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/MODEL-ROUTING.md +15 -15
- package/iso/agents/general-free.md +4 -0
- package/iso/agents/general-paid.md +13 -3
- package/iso/agents/glm-minimal.md +4 -0
- package/iso/config.json +17 -1
- package/iso/instructions.md +73 -1
- package/models.yaml +20 -8
- package/modes/apply.md +6 -2
- package/opencode.json +23 -5
- package/package.json +5 -4
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: general-paid
|
|
3
|
-
description: Quality-sensitive worker on
|
|
3
|
+
description: Quality-sensitive worker on the strongest free-tier OpenCode model by default. 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.
|
|
4
4
|
model: claude-opus-4-7
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
You are the @general-paid subagent. The orchestrator delegated this task to you because it requires quality writing or judgment — the kind of work `@general-free` isn't well-suited for.
|
|
8
8
|
|
|
9
|
+
On OpenCode, this agent now defaults to a free OpenRouter model. On other
|
|
10
|
+
harnesses, the same role may still resolve to a premium model. Your job is
|
|
11
|
+
still the same: produce the best final writing you can from the context you
|
|
12
|
+
were given.
|
|
13
|
+
|
|
9
14
|
## Do These Tasks
|
|
10
15
|
|
|
11
16
|
- Generate evaluation narratives (Blocks A-F) per `modes/offer.md`.
|
package/.codex/config.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# generated by @razroo/iso-route — do not hand-edit
|
|
2
|
-
model = "
|
|
3
|
-
model_provider = "
|
|
2
|
+
model = "gpt-5.4"
|
|
3
|
+
model_provider = "openai"
|
|
4
4
|
|
|
5
5
|
[profiles.fast]
|
|
6
6
|
model = "gpt-5.4-mini"
|
|
@@ -15,11 +15,6 @@ model_reasoning_effort = "high"
|
|
|
15
15
|
model = "gpt-5.4-nano"
|
|
16
16
|
model_provider = "openai"
|
|
17
17
|
|
|
18
|
-
[model_providers.anthropic]
|
|
19
|
-
name = "Anthropic"
|
|
20
|
-
base_url = "https://api.anthropic.com/v1"
|
|
21
|
-
env_key = "ANTHROPIC_API_KEY"
|
|
22
|
-
|
|
23
18
|
[model_providers.openai]
|
|
24
19
|
name = "OpenAI"
|
|
25
20
|
base_url = "https://api.openai.com/v1"
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Quality-sensitive worker on
|
|
2
|
+
description: Quality-sensitive worker on the strongest free-tier OpenCode model by default. 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
|
alwaysApply: false
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
You are the @general-paid subagent. The orchestrator delegated this task to you because it requires quality writing or judgment — the kind of work `@general-free` isn't well-suited for.
|
|
7
7
|
|
|
8
|
+
On OpenCode, this agent now defaults to a free OpenRouter model. On other
|
|
9
|
+
harnesses, the same role may still resolve to a premium model. Your job is
|
|
10
|
+
still the same: produce the best final writing you can from the context you
|
|
11
|
+
were given.
|
|
12
|
+
|
|
8
13
|
## Do These Tasks
|
|
9
14
|
|
|
10
15
|
- Generate evaluation narratives (Blocks A-F) per `modes/offer.md`.
|
package/.cursor/rules/main.mdc
CHANGED
|
@@ -419,6 +419,8 @@ These blocks come from two distinct root causes and require different responses:
|
|
|
419
419
|
|
|
420
420
|
**Rule — do NOT loop retrying a class B block.** One retry with `imeFriendly: true` is the correct test for class A. If the same spam message fires after a clean `imeFriendly` refill, stop, mark Failed, move on. Repeated retries waste subagent time and do not change the outcome.
|
|
421
421
|
|
|
422
|
+
**Class B fix — BYO residential proxy** (added 2026-04-20 via Geometra MCP v1.59.0). When the candidate has configured `proxy:` in `config/profile.yml`, every `geometra_connect` call threads that proxy through to Chromium, which flips the outbound IP from datacenter to residential/mobile and collapses most class-B failures. See the "BYO Residential Proxy" reference section below. Without a configured proxy, class B stays Failed.
|
|
423
|
+
|
|
422
424
|
**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:
|
|
423
425
|
|
|
424
426
|
- 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**
|
|
@@ -510,6 +512,8 @@ geometra_connect({ pageUrl: "https://...", isolated: true, headless: true, slowM
|
|
|
510
512
|
|
|
511
513
|
**Wrong:** running `geometra_connect` without `isolated: true` when submitting multiple forms concurrently. The forms may share state and produce incorrect submissions.
|
|
512
514
|
|
|
515
|
+
**With a configured proxy,** add `proxy: { server, username?, password?, bypass? }` to the same call — see "BYO Residential Proxy" below. The reusable-proxy pool is partitioned by proxy identity, so mixing direct and proxied sessions across parallel rounds is safe.
|
|
516
|
+
|
|
513
517
|
### Session Reuse — When Subagents Cannot Reach Existing Sessions
|
|
514
518
|
|
|
515
519
|
Subagents launched via the `task` tool start with a fresh context and cannot automatically attach to Chromium sessions spawned by a previous orchestrator session. If you dispatch a subagent to fill a form in session `s16`, but `s16` was created by a previous opencode session, the subagent's MCP calls will silently fail (returning empty results) because the subagent's MCP server has no knowledge of `s16`.
|
|
@@ -553,6 +557,17 @@ Step 2: geometra_disconnect({ closeBrowser: true })
|
|
|
553
557
|
Step 3: geometra_connect({ pageUrl: "<the URL the orchestrator gave you>", isolated: true, headless: true, slowMo: 350 })
|
|
554
558
|
```
|
|
555
559
|
|
|
560
|
+
**If the orchestrator passed a `proxy` object in the task prompt** (sourced from `config/profile.yml`), add it to Step 3:
|
|
561
|
+
|
|
562
|
+
```
|
|
563
|
+
Step 3: geometra_connect({
|
|
564
|
+
pageUrl: "<URL>", isolated: true, headless: true, slowMo: 350,
|
|
565
|
+
proxy: { server: "...", username: "...", password: "...", bypass: "..." }
|
|
566
|
+
})
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
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.
|
|
570
|
+
|
|
556
571
|
**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.
|
|
557
572
|
|
|
558
573
|
**Single exception:** if the orchestrator's task prompt says literally "attach to sessionId X" or "use existing session X", skip Steps 1-3 and call `geometra_page_model({ sessionId: "X" })` directly.
|
|
@@ -574,7 +589,64 @@ When the orchestrator dispatches an `apply` (form-fill + submit), pick the subag
|
|
|
574
589
|
|
|
575
590
|
---
|
|
576
591
|
|
|
577
|
-
##
|
|
592
|
+
## BYO Residential Proxy — opt-in outbound-IP override
|
|
593
|
+
|
|
594
|
+
**Problem:** on 2026-04-19 cycle 4, 5/5 untested Ashby tenants and 100% of Dropbox-class Cloudflare-fronted portals fingerprint-blocked headless Chromium from datacenter IPs. `imeFriendly: true` fixes class A (React validation lag) but has zero effect on class B (environment fingerprint). There is no in-session software-only fix for class B: the server decided the session is a bot before the form response was rendered.
|
|
595
|
+
|
|
596
|
+
**Fix:** route the spawned Chromium through a residential or mobile proxy the candidate already pays for. Geometra MCP v1.59.0 added a `proxy: { server, username?, password?, bypass? }` parameter on `geometra_connect` and `geometra_prepare_browser` that forwards straight to Playwright's `chromium.launch({ proxy })`. The outbound IP becomes residential/mobile, and the fingerprint check that fired class B no longer trips.
|
|
597
|
+
|
|
598
|
+
**Opt-in, BYO.** JobForge does NOT bundle or resell proxy bandwidth — the candidate brings their own provider (Bright Data, Oxylabs, SOAX, Smartproxy, mobile hotspot, self-hosted SOCKS). Without a configured proxy, JobForge behavior is unchanged from v2.11.0 and earlier.
|
|
599
|
+
|
|
600
|
+
### Where the proxy config lives
|
|
601
|
+
|
|
602
|
+
`config/profile.yml` → top-level `proxy:` block:
|
|
603
|
+
|
|
604
|
+
```yaml
|
|
605
|
+
proxy:
|
|
606
|
+
server: "http://residential.example.com:8080" # http://, https://, or socks5://
|
|
607
|
+
username: "your-proxy-username" # optional
|
|
608
|
+
password: "your-proxy-password" # optional
|
|
609
|
+
bypass: "*.internal,localhost" # optional
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
See `config/profile.example.yml` for the commented-out template.
|
|
613
|
+
|
|
614
|
+
### How the orchestrator threads it through
|
|
615
|
+
|
|
616
|
+
**Orchestrator responsibilities:**
|
|
617
|
+
|
|
618
|
+
1. On session start, read `config/profile.yml` once. If a `proxy:` block is present, capture it as the `PROXY_CONFIG` for the session.
|
|
619
|
+
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."
|
|
620
|
+
3. When the orchestrator itself opens a Chromium session (single-application interactive flow), include the same `proxy` object in its own `geometra_connect` call.
|
|
621
|
+
4. If `proxy:` is absent from `profile.yml`, skip the param entirely. Do NOT invent a proxy URL or leave a stale placeholder.
|
|
622
|
+
|
|
623
|
+
**Subagent responsibilities:**
|
|
624
|
+
|
|
625
|
+
1. If the task prompt includes a `proxy` object, pass it through to `geometra_connect` and any `geometra_prepare_browser` calls unchanged.
|
|
626
|
+
2. If the task prompt does NOT include a proxy object, run without one.
|
|
627
|
+
3. Never second-guess the proxy field — if the orchestrator sourced it from `profile.yml`, it's authoritative.
|
|
628
|
+
|
|
629
|
+
### When proxy use is load-bearing
|
|
630
|
+
|
|
631
|
+
Apply these rules when deciding whether the proxy is worth waiting for:
|
|
632
|
+
|
|
633
|
+
- **Required** for known-block Ashby tenants (see the class-B list in the Ashby section above), for `happydance.website` / Cloudflare-fronted ATSes, and for any Lever tenant that previously failed in the class-B pattern.
|
|
634
|
+
- **Recommended** for any Ashby tenant NOT on the class-A-compatible list (base rate prior: ~80-90% block headless).
|
|
635
|
+
- **Optional** for Greenhouse, Workday, Lever-clean tenants — these accept datacenter IPs today; using the proxy adds ~100ms per frame but no material downside.
|
|
636
|
+
- **Not useful** for Typeform (Geometra-unsupported), Avature native-select lag (not a fingerprint issue), JazzHR+reCAPTCHA (reCAPTCHA scores unrelated to IP), Breezy (tenant-configured per-IP throttle — proxy may help or may hit a fresh throttle).
|
|
637
|
+
|
|
638
|
+
### Pool partitioning — why mixed runs are safe
|
|
639
|
+
|
|
640
|
+
The Geometra MCP partitions its reusable-proxy pool by `(server, username, bypass)` — see `@geometra/mcp@1.59.0` release notes. A direct session and a proxied session NEVER share a Chromium instance, and two sessions with different proxy configs don't pool either. Practical consequence: flipping `proxy:` on or off in `profile.yml` mid-session is safe — the next `geometra_connect` just opens a fresh Chromium in its own pool partition.
|
|
641
|
+
|
|
642
|
+
### Troubleshooting
|
|
643
|
+
|
|
644
|
+
| Symptom | Diagnosis |
|
|
645
|
+
|---|---|
|
|
646
|
+
| `Error: Failed to connect to proxy` immediately after `geometra_connect` | Proxy URL is wrong / unreachable. Verify the `server:` field hits the right host:port. |
|
|
647
|
+
| `407 Proxy Authentication Required` | `username` or `password` is wrong or missing. Many residential providers require both. |
|
|
648
|
+
| Class-B submit failure persists even with proxy set | (a) proxy is a datacenter proxy, not residential; (b) same tenant IP-banned your specific proxy's IP pool; (c) tenant uses TLS fingerprint / canvas fingerprint, not IP — switch to a fresh Chromium (isolated: true) and retry once, else mark Failed. |
|
|
649
|
+
| Every `geometra_connect` is 3-5s slower than before | Expected — residential proxies add latency. Trade-off for higher submit-success rate. Do NOT revert unless the acceptance-rate lift is < 5%. |
|
|
578
650
|
|
|
579
651
|
- Node.js (mjs modules), Geometra MCP (PDF + scraping + form filling), Gmail MCP (email), YAML (config), HTML/CSS (template), Markdown (data)
|
|
580
652
|
|
|
@@ -1,7 +1,7 @@
|
|
|
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
|
mode: subagent
|
|
4
|
-
model:
|
|
4
|
+
model: openrouter/minimax/minimax-m2.5:free
|
|
5
5
|
tools:
|
|
6
6
|
geometra_connect: true
|
|
7
7
|
geometra_page_model: true
|
|
@@ -16,6 +16,10 @@ tools:
|
|
|
16
16
|
gmail_get_message: true
|
|
17
17
|
temperature: 0.1
|
|
18
18
|
reasoningEffort: minimal
|
|
19
|
+
fallback_models:
|
|
20
|
+
- openrouter/qwen/qwen3-coder:free
|
|
21
|
+
- openrouter/google/gemma-4-26b-a4b-it:free
|
|
22
|
+
- openrouter/openai/gpt-oss-20b:free
|
|
19
23
|
---
|
|
20
24
|
|
|
21
25
|
You are the @general-free subagent. You run on a free-tier model, which means the orchestrator has delegated this task to you **specifically because the work is procedural**: deterministic steps, scripted outputs, no nuanced writing required.
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Quality-sensitive worker on
|
|
2
|
+
description: Quality-sensitive worker on the strongest free-tier OpenCode model by default. 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
|
mode: subagent
|
|
4
|
-
model:
|
|
4
|
+
model: openrouter/qwen/qwen3-next-80b-a3b-instruct:free
|
|
5
5
|
tools:
|
|
6
6
|
geometra_*: false
|
|
7
7
|
gmail_*: false
|
|
8
8
|
temperature: 0.3
|
|
9
9
|
reasoningEffort: medium
|
|
10
|
+
fallback_models:
|
|
11
|
+
- openrouter/qwen/qwen3-coder:free
|
|
12
|
+
- openrouter/openai/gpt-oss-120b:free
|
|
13
|
+
- openrouter/z-ai/glm-4.5-air:free
|
|
10
14
|
---
|
|
11
15
|
|
|
12
16
|
You are the @general-paid subagent. The orchestrator delegated this task to you because it requires quality writing or judgment — the kind of work `@general-free` isn't well-suited for.
|
|
13
17
|
|
|
18
|
+
On OpenCode, this agent now defaults to a free OpenRouter model. On other
|
|
19
|
+
harnesses, the same role may still resolve to a premium model. Your job is
|
|
20
|
+
still the same: produce the best final writing you can from the context you
|
|
21
|
+
were given.
|
|
22
|
+
|
|
14
23
|
## Do These Tasks
|
|
15
24
|
|
|
16
25
|
- Generate evaluation narratives (Blocks A-F) per `modes/offer.md`.
|
|
@@ -1,7 +1,7 @@
|
|
|
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
|
mode: subagent
|
|
4
|
-
model:
|
|
4
|
+
model: openrouter/google/gemma-4-26b-a4b-it:free
|
|
5
5
|
tools:
|
|
6
6
|
geometra_*: false
|
|
7
7
|
gmail_*: false
|
|
@@ -13,6 +13,10 @@ tools:
|
|
|
13
13
|
task: false
|
|
14
14
|
temperature: 0
|
|
15
15
|
reasoningEffort: none
|
|
16
|
+
fallback_models:
|
|
17
|
+
- openrouter/minimax/minimax-m2.5:free
|
|
18
|
+
- openrouter/openai/gpt-oss-20b:free
|
|
19
|
+
- openrouter/google/gemma-4-31b-it:free
|
|
16
20
|
---
|
|
17
21
|
|
|
18
22
|
You are the @glm-minimal subagent. You handle narrow, one-shot extractions where the orchestrator has pre-digested the context and just needs you to do a specific transform.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cooldown_seconds": 60,
|
|
3
|
+
"timeout_seconds": 30,
|
|
4
|
+
"notify_on_fallback": true,
|
|
5
|
+
"fallback_models": [
|
|
6
|
+
"openrouter/qwen/qwen3-next-80b-a3b-instruct:free",
|
|
7
|
+
"openrouter/openai/gpt-oss-120b:free",
|
|
8
|
+
"openrouter/google/gemma-4-26b-a4b-it:free",
|
|
9
|
+
"openrouter/z-ai/glm-4.5-air:free"
|
|
10
|
+
]
|
|
11
|
+
}
|
package/AGENTS.md
CHANGED
|
@@ -414,6 +414,8 @@ These blocks come from two distinct root causes and require different responses:
|
|
|
414
414
|
|
|
415
415
|
**Rule — do NOT loop retrying a class B block.** One retry with `imeFriendly: true` is the correct test for class A. If the same spam message fires after a clean `imeFriendly` refill, stop, mark Failed, move on. Repeated retries waste subagent time and do not change the outcome.
|
|
416
416
|
|
|
417
|
+
**Class B fix — BYO residential proxy** (added 2026-04-20 via Geometra MCP v1.59.0). When the candidate has configured `proxy:` in `config/profile.yml`, every `geometra_connect` call threads that proxy through to Chromium, which flips the outbound IP from datacenter to residential/mobile and collapses most class-B failures. See the "BYO Residential Proxy" reference section below. Without a configured proxy, class B stays Failed.
|
|
418
|
+
|
|
417
419
|
**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:
|
|
418
420
|
|
|
419
421
|
- 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**
|
|
@@ -505,6 +507,8 @@ geometra_connect({ pageUrl: "https://...", isolated: true, headless: true, slowM
|
|
|
505
507
|
|
|
506
508
|
**Wrong:** running `geometra_connect` without `isolated: true` when submitting multiple forms concurrently. The forms may share state and produce incorrect submissions.
|
|
507
509
|
|
|
510
|
+
**With a configured proxy,** add `proxy: { server, username?, password?, bypass? }` to the same call — see "BYO Residential Proxy" below. The reusable-proxy pool is partitioned by proxy identity, so mixing direct and proxied sessions across parallel rounds is safe.
|
|
511
|
+
|
|
508
512
|
### Session Reuse — When Subagents Cannot Reach Existing Sessions
|
|
509
513
|
|
|
510
514
|
Subagents launched via the `task` tool start with a fresh context and cannot automatically attach to Chromium sessions spawned by a previous orchestrator session. If you dispatch a subagent to fill a form in session `s16`, but `s16` was created by a previous opencode session, the subagent's MCP calls will silently fail (returning empty results) because the subagent's MCP server has no knowledge of `s16`.
|
|
@@ -548,6 +552,17 @@ Step 2: geometra_disconnect({ closeBrowser: true })
|
|
|
548
552
|
Step 3: geometra_connect({ pageUrl: "<the URL the orchestrator gave you>", isolated: true, headless: true, slowMo: 350 })
|
|
549
553
|
```
|
|
550
554
|
|
|
555
|
+
**If the orchestrator passed a `proxy` object in the task prompt** (sourced from `config/profile.yml`), add it to Step 3:
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
Step 3: geometra_connect({
|
|
559
|
+
pageUrl: "<URL>", isolated: true, headless: true, slowMo: 350,
|
|
560
|
+
proxy: { server: "...", username: "...", password: "...", bypass: "..." }
|
|
561
|
+
})
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
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.
|
|
565
|
+
|
|
551
566
|
**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.
|
|
552
567
|
|
|
553
568
|
**Single exception:** if the orchestrator's task prompt says literally "attach to sessionId X" or "use existing session X", skip Steps 1-3 and call `geometra_page_model({ sessionId: "X" })` directly.
|
|
@@ -569,7 +584,64 @@ When the orchestrator dispatches an `apply` (form-fill + submit), pick the subag
|
|
|
569
584
|
|
|
570
585
|
---
|
|
571
586
|
|
|
572
|
-
##
|
|
587
|
+
## BYO Residential Proxy — opt-in outbound-IP override
|
|
588
|
+
|
|
589
|
+
**Problem:** on 2026-04-19 cycle 4, 5/5 untested Ashby tenants and 100% of Dropbox-class Cloudflare-fronted portals fingerprint-blocked headless Chromium from datacenter IPs. `imeFriendly: true` fixes class A (React validation lag) but has zero effect on class B (environment fingerprint). There is no in-session software-only fix for class B: the server decided the session is a bot before the form response was rendered.
|
|
590
|
+
|
|
591
|
+
**Fix:** route the spawned Chromium through a residential or mobile proxy the candidate already pays for. Geometra MCP v1.59.0 added a `proxy: { server, username?, password?, bypass? }` parameter on `geometra_connect` and `geometra_prepare_browser` that forwards straight to Playwright's `chromium.launch({ proxy })`. The outbound IP becomes residential/mobile, and the fingerprint check that fired class B no longer trips.
|
|
592
|
+
|
|
593
|
+
**Opt-in, BYO.** JobForge does NOT bundle or resell proxy bandwidth — the candidate brings their own provider (Bright Data, Oxylabs, SOAX, Smartproxy, mobile hotspot, self-hosted SOCKS). Without a configured proxy, JobForge behavior is unchanged from v2.11.0 and earlier.
|
|
594
|
+
|
|
595
|
+
### Where the proxy config lives
|
|
596
|
+
|
|
597
|
+
`config/profile.yml` → top-level `proxy:` block:
|
|
598
|
+
|
|
599
|
+
```yaml
|
|
600
|
+
proxy:
|
|
601
|
+
server: "http://residential.example.com:8080" # http://, https://, or socks5://
|
|
602
|
+
username: "your-proxy-username" # optional
|
|
603
|
+
password: "your-proxy-password" # optional
|
|
604
|
+
bypass: "*.internal,localhost" # optional
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
See `config/profile.example.yml` for the commented-out template.
|
|
608
|
+
|
|
609
|
+
### How the orchestrator threads it through
|
|
610
|
+
|
|
611
|
+
**Orchestrator responsibilities:**
|
|
612
|
+
|
|
613
|
+
1. On session start, read `config/profile.yml` once. If a `proxy:` block is present, capture it as the `PROXY_CONFIG` for the session.
|
|
614
|
+
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."
|
|
615
|
+
3. When the orchestrator itself opens a Chromium session (single-application interactive flow), include the same `proxy` object in its own `geometra_connect` call.
|
|
616
|
+
4. If `proxy:` is absent from `profile.yml`, skip the param entirely. Do NOT invent a proxy URL or leave a stale placeholder.
|
|
617
|
+
|
|
618
|
+
**Subagent responsibilities:**
|
|
619
|
+
|
|
620
|
+
1. If the task prompt includes a `proxy` object, pass it through to `geometra_connect` and any `geometra_prepare_browser` calls unchanged.
|
|
621
|
+
2. If the task prompt does NOT include a proxy object, run without one.
|
|
622
|
+
3. Never second-guess the proxy field — if the orchestrator sourced it from `profile.yml`, it's authoritative.
|
|
623
|
+
|
|
624
|
+
### When proxy use is load-bearing
|
|
625
|
+
|
|
626
|
+
Apply these rules when deciding whether the proxy is worth waiting for:
|
|
627
|
+
|
|
628
|
+
- **Required** for known-block Ashby tenants (see the class-B list in the Ashby section above), for `happydance.website` / Cloudflare-fronted ATSes, and for any Lever tenant that previously failed in the class-B pattern.
|
|
629
|
+
- **Recommended** for any Ashby tenant NOT on the class-A-compatible list (base rate prior: ~80-90% block headless).
|
|
630
|
+
- **Optional** for Greenhouse, Workday, Lever-clean tenants — these accept datacenter IPs today; using the proxy adds ~100ms per frame but no material downside.
|
|
631
|
+
- **Not useful** for Typeform (Geometra-unsupported), Avature native-select lag (not a fingerprint issue), JazzHR+reCAPTCHA (reCAPTCHA scores unrelated to IP), Breezy (tenant-configured per-IP throttle — proxy may help or may hit a fresh throttle).
|
|
632
|
+
|
|
633
|
+
### Pool partitioning — why mixed runs are safe
|
|
634
|
+
|
|
635
|
+
The Geometra MCP partitions its reusable-proxy pool by `(server, username, bypass)` — see `@geometra/mcp@1.59.0` release notes. A direct session and a proxied session NEVER share a Chromium instance, and two sessions with different proxy configs don't pool either. Practical consequence: flipping `proxy:` on or off in `profile.yml` mid-session is safe — the next `geometra_connect` just opens a fresh Chromium in its own pool partition.
|
|
636
|
+
|
|
637
|
+
### Troubleshooting
|
|
638
|
+
|
|
639
|
+
| Symptom | Diagnosis |
|
|
640
|
+
|---|---|
|
|
641
|
+
| `Error: Failed to connect to proxy` immediately after `geometra_connect` | Proxy URL is wrong / unreachable. Verify the `server:` field hits the right host:port. |
|
|
642
|
+
| `407 Proxy Authentication Required` | `username` or `password` is wrong or missing. Many residential providers require both. |
|
|
643
|
+
| Class-B submit failure persists even with proxy set | (a) proxy is a datacenter proxy, not residential; (b) same tenant IP-banned your specific proxy's IP pool; (c) tenant uses TLS fingerprint / canvas fingerprint, not IP — switch to a fresh Chromium (isolated: true) and retry once, else mark Failed. |
|
|
644
|
+
| Every `geometra_connect` is 3-5s slower than before | Expected — residential proxies add latency. Trade-off for higher submit-success rate. Do NOT revert unless the acceptance-rate lift is < 5%. |
|
|
573
645
|
|
|
574
646
|
- Node.js (mjs modules), Geometra MCP (PDF + scraping + form filling), Gmail MCP (email), YAML (config), HTML/CSS (template), Markdown (data)
|
|
575
647
|
|
package/CLAUDE.md
CHANGED
|
@@ -414,6 +414,8 @@ These blocks come from two distinct root causes and require different responses:
|
|
|
414
414
|
|
|
415
415
|
**Rule — do NOT loop retrying a class B block.** One retry with `imeFriendly: true` is the correct test for class A. If the same spam message fires after a clean `imeFriendly` refill, stop, mark Failed, move on. Repeated retries waste subagent time and do not change the outcome.
|
|
416
416
|
|
|
417
|
+
**Class B fix — BYO residential proxy** (added 2026-04-20 via Geometra MCP v1.59.0). When the candidate has configured `proxy:` in `config/profile.yml`, every `geometra_connect` call threads that proxy through to Chromium, which flips the outbound IP from datacenter to residential/mobile and collapses most class-B failures. See the "BYO Residential Proxy" reference section below. Without a configured proxy, class B stays Failed.
|
|
418
|
+
|
|
417
419
|
**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:
|
|
418
420
|
|
|
419
421
|
- 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**
|
|
@@ -505,6 +507,8 @@ geometra_connect({ pageUrl: "https://...", isolated: true, headless: true, slowM
|
|
|
505
507
|
|
|
506
508
|
**Wrong:** running `geometra_connect` without `isolated: true` when submitting multiple forms concurrently. The forms may share state and produce incorrect submissions.
|
|
507
509
|
|
|
510
|
+
**With a configured proxy,** add `proxy: { server, username?, password?, bypass? }` to the same call — see "BYO Residential Proxy" below. The reusable-proxy pool is partitioned by proxy identity, so mixing direct and proxied sessions across parallel rounds is safe.
|
|
511
|
+
|
|
508
512
|
### Session Reuse — When Subagents Cannot Reach Existing Sessions
|
|
509
513
|
|
|
510
514
|
Subagents launched via the `task` tool start with a fresh context and cannot automatically attach to Chromium sessions spawned by a previous orchestrator session. If you dispatch a subagent to fill a form in session `s16`, but `s16` was created by a previous opencode session, the subagent's MCP calls will silently fail (returning empty results) because the subagent's MCP server has no knowledge of `s16`.
|
|
@@ -548,6 +552,17 @@ Step 2: geometra_disconnect({ closeBrowser: true })
|
|
|
548
552
|
Step 3: geometra_connect({ pageUrl: "<the URL the orchestrator gave you>", isolated: true, headless: true, slowMo: 350 })
|
|
549
553
|
```
|
|
550
554
|
|
|
555
|
+
**If the orchestrator passed a `proxy` object in the task prompt** (sourced from `config/profile.yml`), add it to Step 3:
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
Step 3: geometra_connect({
|
|
559
|
+
pageUrl: "<URL>", isolated: true, headless: true, slowMo: 350,
|
|
560
|
+
proxy: { server: "...", username: "...", password: "...", bypass: "..." }
|
|
561
|
+
})
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
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.
|
|
565
|
+
|
|
551
566
|
**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.
|
|
552
567
|
|
|
553
568
|
**Single exception:** if the orchestrator's task prompt says literally "attach to sessionId X" or "use existing session X", skip Steps 1-3 and call `geometra_page_model({ sessionId: "X" })` directly.
|
|
@@ -569,7 +584,64 @@ When the orchestrator dispatches an `apply` (form-fill + submit), pick the subag
|
|
|
569
584
|
|
|
570
585
|
---
|
|
571
586
|
|
|
572
|
-
##
|
|
587
|
+
## BYO Residential Proxy — opt-in outbound-IP override
|
|
588
|
+
|
|
589
|
+
**Problem:** on 2026-04-19 cycle 4, 5/5 untested Ashby tenants and 100% of Dropbox-class Cloudflare-fronted portals fingerprint-blocked headless Chromium from datacenter IPs. `imeFriendly: true` fixes class A (React validation lag) but has zero effect on class B (environment fingerprint). There is no in-session software-only fix for class B: the server decided the session is a bot before the form response was rendered.
|
|
590
|
+
|
|
591
|
+
**Fix:** route the spawned Chromium through a residential or mobile proxy the candidate already pays for. Geometra MCP v1.59.0 added a `proxy: { server, username?, password?, bypass? }` parameter on `geometra_connect` and `geometra_prepare_browser` that forwards straight to Playwright's `chromium.launch({ proxy })`. The outbound IP becomes residential/mobile, and the fingerprint check that fired class B no longer trips.
|
|
592
|
+
|
|
593
|
+
**Opt-in, BYO.** JobForge does NOT bundle or resell proxy bandwidth — the candidate brings their own provider (Bright Data, Oxylabs, SOAX, Smartproxy, mobile hotspot, self-hosted SOCKS). Without a configured proxy, JobForge behavior is unchanged from v2.11.0 and earlier.
|
|
594
|
+
|
|
595
|
+
### Where the proxy config lives
|
|
596
|
+
|
|
597
|
+
`config/profile.yml` → top-level `proxy:` block:
|
|
598
|
+
|
|
599
|
+
```yaml
|
|
600
|
+
proxy:
|
|
601
|
+
server: "http://residential.example.com:8080" # http://, https://, or socks5://
|
|
602
|
+
username: "your-proxy-username" # optional
|
|
603
|
+
password: "your-proxy-password" # optional
|
|
604
|
+
bypass: "*.internal,localhost" # optional
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
See `config/profile.example.yml` for the commented-out template.
|
|
608
|
+
|
|
609
|
+
### How the orchestrator threads it through
|
|
610
|
+
|
|
611
|
+
**Orchestrator responsibilities:**
|
|
612
|
+
|
|
613
|
+
1. On session start, read `config/profile.yml` once. If a `proxy:` block is present, capture it as the `PROXY_CONFIG` for the session.
|
|
614
|
+
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."
|
|
615
|
+
3. When the orchestrator itself opens a Chromium session (single-application interactive flow), include the same `proxy` object in its own `geometra_connect` call.
|
|
616
|
+
4. If `proxy:` is absent from `profile.yml`, skip the param entirely. Do NOT invent a proxy URL or leave a stale placeholder.
|
|
617
|
+
|
|
618
|
+
**Subagent responsibilities:**
|
|
619
|
+
|
|
620
|
+
1. If the task prompt includes a `proxy` object, pass it through to `geometra_connect` and any `geometra_prepare_browser` calls unchanged.
|
|
621
|
+
2. If the task prompt does NOT include a proxy object, run without one.
|
|
622
|
+
3. Never second-guess the proxy field — if the orchestrator sourced it from `profile.yml`, it's authoritative.
|
|
623
|
+
|
|
624
|
+
### When proxy use is load-bearing
|
|
625
|
+
|
|
626
|
+
Apply these rules when deciding whether the proxy is worth waiting for:
|
|
627
|
+
|
|
628
|
+
- **Required** for known-block Ashby tenants (see the class-B list in the Ashby section above), for `happydance.website` / Cloudflare-fronted ATSes, and for any Lever tenant that previously failed in the class-B pattern.
|
|
629
|
+
- **Recommended** for any Ashby tenant NOT on the class-A-compatible list (base rate prior: ~80-90% block headless).
|
|
630
|
+
- **Optional** for Greenhouse, Workday, Lever-clean tenants — these accept datacenter IPs today; using the proxy adds ~100ms per frame but no material downside.
|
|
631
|
+
- **Not useful** for Typeform (Geometra-unsupported), Avature native-select lag (not a fingerprint issue), JazzHR+reCAPTCHA (reCAPTCHA scores unrelated to IP), Breezy (tenant-configured per-IP throttle — proxy may help or may hit a fresh throttle).
|
|
632
|
+
|
|
633
|
+
### Pool partitioning — why mixed runs are safe
|
|
634
|
+
|
|
635
|
+
The Geometra MCP partitions its reusable-proxy pool by `(server, username, bypass)` — see `@geometra/mcp@1.59.0` release notes. A direct session and a proxied session NEVER share a Chromium instance, and two sessions with different proxy configs don't pool either. Practical consequence: flipping `proxy:` on or off in `profile.yml` mid-session is safe — the next `geometra_connect` just opens a fresh Chromium in its own pool partition.
|
|
636
|
+
|
|
637
|
+
### Troubleshooting
|
|
638
|
+
|
|
639
|
+
| Symptom | Diagnosis |
|
|
640
|
+
|---|---|
|
|
641
|
+
| `Error: Failed to connect to proxy` immediately after `geometra_connect` | Proxy URL is wrong / unreachable. Verify the `server:` field hits the right host:port. |
|
|
642
|
+
| `407 Proxy Authentication Required` | `username` or `password` is wrong or missing. Many residential providers require both. |
|
|
643
|
+
| Class-B submit failure persists even with proxy set | (a) proxy is a datacenter proxy, not residential; (b) same tenant IP-banned your specific proxy's IP pool; (c) tenant uses TLS fingerprint / canvas fingerprint, not IP — switch to a fresh Chromium (isolated: true) and retry once, else mark Failed. |
|
|
644
|
+
| Every `geometra_connect` is 3-5s slower than before | Expected — residential proxies add latency. Trade-off for higher submit-success rate. Do NOT revert unless the acceptance-rate lift is < 5%. |
|
|
573
645
|
|
|
574
646
|
- Node.js (mjs modules), Geometra MCP (PDF + scraping + form filling), Gmail MCP (email), YAML (config), HTML/CSS (template), Markdown (data)
|
|
575
647
|
|
package/README.md
CHANGED
|
@@ -68,8 +68,8 @@ JobForge turns opencode into a full job search command center. Instead of manual
|
|
|
68
68
|
| **Portal Scanner** | 45+ companies pre-configured with fuzzy dedup for reposts |
|
|
69
69
|
| **Batch Processing** | Parallel evaluation with `opencode run` workers, with honest verification flagging |
|
|
70
70
|
| **Pipeline Integrity** | Automated merge, dedup, status normalization, health checks |
|
|
71
|
-
| **Cost-Aware Agent Routing** | Three subagents (`@general-free`, `@general-paid`, `@glm-minimal`) with per-task model tiers
|
|
72
|
-
| **Automatic Model Fallback** | When a model rate-limits or 5xx's, [`@razroo/opencode-model-fallback`](https://www.npmjs.com/package/@razroo/opencode-model-fallback) rotates the agent through a configured `fallback_models` chain and replays the request.
|
|
71
|
+
| **Cost-Aware Agent Routing** | Three subagents (`@general-free`, `@general-paid`, `@glm-minimal`) with per-task model tiers. On OpenCode, all three default to free OpenRouter models so the harness can run there without paid model spend. See [Subagent Routing in AGENTS.md](AGENTS.md) for the task-to-agent mapping. |
|
|
72
|
+
| **Automatic Model Fallback** | When a model rate-limits or 5xx's, [`@razroo/opencode-model-fallback`](https://www.npmjs.com/package/@razroo/opencode-model-fallback) rotates the agent through a configured `fallback_models` chain and replays the request. JobForge's OpenCode defaults stay on free models for both primaries and fallbacks. |
|
|
73
73
|
| **Token Cost Visibility** | `job-forge tokens --days 1` for per-session breakdown; `job-forge session-report --since-minutes 60 --log` to flag sessions over budget and append history to `data/token-usage.tsv`. Auto-logged after every batch run. |
|
|
74
74
|
|
|
75
75
|
## Usage
|
package/bin/create-job-forge.mjs
CHANGED
|
@@ -135,6 +135,11 @@ write('package.json', JSON.stringify(consumerPkg, null, 2) + '\n');
|
|
|
135
135
|
|
|
136
136
|
const opencodeCfg = {
|
|
137
137
|
$schema: 'https://opencode.ai/config.json',
|
|
138
|
+
// Keep the top-level orchestrator on a free model too. Subagents pin
|
|
139
|
+
// their own models in .opencode/agents/*.md; this covers the main chat
|
|
140
|
+
// session and any commands that don't hop to a subagent immediately.
|
|
141
|
+
model: 'openrouter/qwen/qwen3-coder:free',
|
|
142
|
+
small_model: 'openrouter/google/gemma-4-26b-a4b-it:free',
|
|
138
143
|
// Model-fallback plugin: on rate-limit / 5xx / known provider errors,
|
|
139
144
|
// rotates the agent's model to the next entry in its fallback_models
|
|
140
145
|
// chain (see `agent` below) and replays the request. Without this, a
|
|
@@ -170,6 +175,22 @@ const opencodeCfg = {
|
|
|
170
175
|
environment: { DISABLE_HTTP: 'true' },
|
|
171
176
|
},
|
|
172
177
|
},
|
|
178
|
+
// Register the exact OpenRouter free models the harness uses so they're
|
|
179
|
+
// selectable even if they are not in OpenCode's built-in preloaded set.
|
|
180
|
+
provider: {
|
|
181
|
+
openrouter: {
|
|
182
|
+
models: {
|
|
183
|
+
'qwen/qwen3-coder:free': {},
|
|
184
|
+
'minimax/minimax-m2.5:free': {},
|
|
185
|
+
'qwen/qwen3-next-80b-a3b-instruct:free': {},
|
|
186
|
+
'google/gemma-4-26b-a4b-it:free': {},
|
|
187
|
+
'google/gemma-4-31b-it:free': {},
|
|
188
|
+
'openai/gpt-oss-120b:free': {},
|
|
189
|
+
'openai/gpt-oss-20b:free': {},
|
|
190
|
+
'z-ai/glm-4.5-air:free': {},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
173
194
|
// Restrict the primary orchestrator to dispatching only the three harness
|
|
174
195
|
// subagents. Prevents accidental self-calls or unregistered agents.
|
|
175
196
|
// Override locally in opencode.json if you add project-specific agents.
|
package/bin/sync.mjs
CHANGED
|
@@ -75,6 +75,7 @@ const links = [
|
|
|
75
75
|
|
|
76
76
|
// OpenCode: skill router + subagent definitions. Users can override any
|
|
77
77
|
// single subagent by replacing its symlink with a local file.
|
|
78
|
+
{ src: '.opencode/opencode-model-fallback.json', dst: '.opencode/opencode-model-fallback.json' },
|
|
78
79
|
{ src: '.opencode/skills/job-forge.md', dst: '.opencode/skills/job-forge.md' },
|
|
79
80
|
{ src: '.opencode/agents', dst: '.opencode/agents' },
|
|
80
81
|
|
|
@@ -86,3 +86,23 @@ location_constraints:
|
|
|
86
86
|
- US
|
|
87
87
|
requires_visa_sponsorship: false # true → roles in non-authorized countries are blocked unless
|
|
88
88
|
# the JD explicitly mentions visa sponsorship
|
|
89
|
+
|
|
90
|
+
# Optional outbound proxy for the Chromium that Geometra MCP spawns.
|
|
91
|
+
# Uncomment and fill in to route ALL browser traffic through a residential /
|
|
92
|
+
# mobile / SOCKS proxy you already pay for. Bypasses the datacenter-IP
|
|
93
|
+
# fingerprinting that drives ~80-90% of Ashby / Lever / Cloudflare-fronted
|
|
94
|
+
# "flagged as possible spam" submit failures in headless mode.
|
|
95
|
+
#
|
|
96
|
+
# BYO — JobForge does NOT bundle or resell proxy bandwidth. Pick a residential
|
|
97
|
+
# or mobile provider (Bright Data, Oxylabs, SOAX, Smartproxy, etc.), or a
|
|
98
|
+
# mobile hotspot, or your own SOCKS relay. Required: Geometra MCP >= 1.59.0.
|
|
99
|
+
#
|
|
100
|
+
# When present, the apply / scan / auto-pipeline modes thread this into every
|
|
101
|
+
# `geometra_connect` call as `proxy: {...}`. Pool is partitioned by proxy
|
|
102
|
+
# identity so direct and proxied sessions never share a Chromium.
|
|
103
|
+
#
|
|
104
|
+
# proxy:
|
|
105
|
+
# server: "http://residential.example.com:8080" # http://, https://, or socks5://
|
|
106
|
+
# username: "your-proxy-username" # optional; omit if no auth
|
|
107
|
+
# password: "your-proxy-password" # optional; omit if no auth
|
|
108
|
+
# bypass: "*.internal,localhost" # optional comma-separated host patterns
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -43,7 +43,7 @@ The consumer's `opencode.json` loads a small set of stable files as always-prese
|
|
|
43
43
|
|
|
44
44
|
The skill router (`.opencode/skills/job-forge.md`) loads mode and data files on demand, keeping per-session input tokens low (~20-40K for most modes instead of ~130-170K when everything was force-loaded).
|
|
45
45
|
|
|
46
|
-
**Cost-tiered subagents** live in `.opencode/agents/` (`general-free`, `general-paid`, `glm-minimal`)
|
|
46
|
+
**Cost-tiered subagents** live in `.opencode/agents/` (`general-free`, `general-paid`, `glm-minimal`). On OpenCode, all three now resolve to free OpenRouter models by default, with different quality/latency tiers per task shape. See [MODEL-ROUTING.md](MODEL-ROUTING.md) for the routing architecture, why it exists, and how to customize.
|
|
47
47
|
|
|
48
48
|
**Multi-harness support.** Because `iso/` is the single source of truth, publishing ships config for OpenCode, Cursor, Claude Code, and Codex in one tarball. Consumers run any of `opencode`, `cursor`, `claude`, or `codex` in the project and each picks up the shared MCP config + instructions via the symlinks above.
|
|
49
49
|
|
package/docs/MODEL-ROUTING.md
CHANGED
|
@@ -7,8 +7,8 @@ JobForge routes each piece of work to the cheapest model that can do it well, in
|
|
|
7
7
|
A two-day trace early in development showed `$48` in spend, with **84% coming from GLM 5.1** despite the majority of the work being procedural (form fills, tracker updates, OTP retrieval). The root cause:
|
|
8
8
|
|
|
9
9
|
- **GLM 5.1's provider doesn't discount cache reads.** On Anthropic, a 10K-token cached prefix costs ~$0.03. On GLM 5.1 it bills near-full input rate (~$0.35). Every session that re-loads the prefix pays full price.
|
|
10
|
-
- **Procedural work is the high-volume work.** 1000+ messages per day go to form filling, TSV merges, scan dedup. Running that on
|
|
11
|
-
- **
|
|
10
|
+
- **Procedural work is the high-volume work.** 1000+ messages per day go to form filling, TSV merges, scan dedup. Running that on a paid model is unnecessary when current free OpenRouter models can handle the task.
|
|
11
|
+
- **Current OpenRouter free models are strong enough to cover the whole OpenCode path.** JobForge now defaults every OpenCode role to a free model, including the quality-sensitive writer tier.
|
|
12
12
|
|
|
13
13
|
Conclusion: route procedural work to free tier, reserve paid models for tasks that actually need the quality.
|
|
14
14
|
|
|
@@ -18,9 +18,9 @@ Defined in `.opencode/agents/*.md` (shipped in the harness, symlinked into consu
|
|
|
18
18
|
|
|
19
19
|
| Agent | Model | Reasoning | Use for |
|
|
20
20
|
|-------|-------|-----------|---------|
|
|
21
|
-
| `@general-free` | `
|
|
22
|
-
| `@general-paid` | `
|
|
23
|
-
| `@glm-minimal` | `
|
|
21
|
+
| `@general-free` | `openrouter/minimax/minimax-m2.5:free` | `minimal` | Geometra form fills, tracker TSV merges, scan dedup, OTP retrieval via Gmail, scripted pipeline steps |
|
|
22
|
+
| `@general-paid` | `openrouter/qwen/qwen3-next-80b-a3b-instruct:free` | `medium` | Offer evaluation narratives (Blocks A-F), cover letters, "Why X?" answers, STAR+R interview stories, LinkedIn outreach prose |
|
|
23
|
+
| `@glm-minimal` | `openrouter/google/gemma-4-26b-a4b-it:free` | `none` | Narrow one-shot transforms: "extract these 8 fields from this JD text → JSON", "classify this archetype" |
|
|
24
24
|
|
|
25
25
|
The full task-to-agent mapping lives in [AGENTS.md → Subagent Routing](../AGENTS.md#subagent-routing--which-agent-for-which-task). The orchestrator (your primary session) is expected to delegate before taking any multi-step action — see the **Pre-flight delegation** rule in AGENTS.md.
|
|
26
26
|
|
|
@@ -66,7 +66,7 @@ All three layers are designed to be edited — this is your search, your cost bu
|
|
|
66
66
|
|
|
67
67
|
### Swap the paid model
|
|
68
68
|
|
|
69
|
-
The default `@general-paid` is `
|
|
69
|
+
The default `@general-paid` is `openrouter/qwen/qwen3-next-80b-a3b-instruct:free`. To use Claude instead, edit `.opencode/agents/general-paid.md`:
|
|
70
70
|
|
|
71
71
|
```yaml
|
|
72
72
|
---
|
|
@@ -79,7 +79,7 @@ The `.opencode/agents/general-paid.md` file is a symlink into `node_modules/job-
|
|
|
79
79
|
|
|
80
80
|
### Swap the free tier
|
|
81
81
|
|
|
82
|
-
Same idea — edit `.opencode/agents/general-free.md`'s `model:` field.
|
|
82
|
+
Same idea — edit `.opencode/agents/general-free.md`'s `model:` field. If you run into quality issues on forms, swap to a different free OpenRouter model first before considering a paid tier.
|
|
83
83
|
|
|
84
84
|
### Add a custom agent
|
|
85
85
|
|
|
@@ -122,8 +122,8 @@ npx job-forge tokens --session <session-id>
|
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
Healthy pattern after this architecture lands:
|
|
125
|
-
-
|
|
126
|
-
-
|
|
125
|
+
- **Free OpenRouter defaults** now cover procedural, quality-sensitive, and extractor work on OpenCode
|
|
126
|
+
- If you opt back into a paid model, do it deliberately and only for the role that needs it
|
|
127
127
|
- Session titles prefixed `@general-free`, `@general-paid`, `@glm-minimal` appear in the list — confirms delegation actually happened
|
|
128
128
|
- `cache_read` >> `cache_creation` on parallel subagent runs within a 5-min window
|
|
129
129
|
|
|
@@ -139,13 +139,13 @@ Default chains ship upstream in each agent's YAML frontmatter (`node_modules/job
|
|
|
139
139
|
|
|
140
140
|
| Agent | Primary | Fallback chain (in order) |
|
|
141
141
|
|-------|---------|---------------------------|
|
|
142
|
-
| `@general-free` | `
|
|
143
|
-
| `@general-paid` | `
|
|
144
|
-
| `@glm-minimal` | `
|
|
142
|
+
| `@general-free` | `openrouter/minimax/minimax-m2.5:free` | `openrouter/qwen/qwen3-coder:free` → `openrouter/google/gemma-4-26b-a4b-it:free` → `openrouter/openai/gpt-oss-20b:free` |
|
|
143
|
+
| `@general-paid` | `openrouter/qwen/qwen3-next-80b-a3b-instruct:free` | `openrouter/qwen/qwen3-coder:free` → `openrouter/openai/gpt-oss-120b:free` → `openrouter/z-ai/glm-4.5-air:free` |
|
|
144
|
+
| `@glm-minimal` | `openrouter/google/gemma-4-26b-a4b-it:free` | `openrouter/minimax/minimax-m2.5:free` → `openrouter/openai/gpt-oss-20b:free` → `openrouter/google/gemma-4-31b-it:free` |
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
These chains are deliberately free-only so the default OpenCode path never needs to pay. **Note:** OpenCode model IDs must use the provider prefix it expects (`openrouter/...`, `opencode/...`, etc.). The raw OpenRouter model slug by itself is not enough.
|
|
147
147
|
|
|
148
|
-
Consumers **do not need to configure anything** to get these defaults: the chains arrive via the symlinked agent MD files, and `@razroo/opencode-model-fallback` (≥0.3.1) reads
|
|
148
|
+
Consumers **do not need to configure anything** to get these defaults: the subagent chains arrive via the symlinked agent MD files, and the harness also ships `.opencode/opencode-model-fallback.json` for the main orchestrator / any agent without its own list. `@razroo/opencode-model-fallback` (≥0.3.1) reads per-agent chains from the frontmatter-derived `fallback_models` field and falls through to the global file when no per-agent list exists. The consumer's `opencode.json` only needs `"plugin": ["@razroo/opencode-model-fallback"]` — which the scaffolder sets automatically.
|
|
149
149
|
|
|
150
150
|
**When fallback fires:** the plugin pattern-matches rate-limit / 5xx / quota / "overloaded" / "insufficient credits" errors. Failed models enter a 60-second cooldown before they're retried. Every rotation logs to `~/.config/opencode/opencode-model-fallback.log` with the trigger error, original model, and target model — grep for `"Auto-retrying with fallback model"` to confirm it fired.
|
|
151
151
|
|
|
@@ -172,7 +172,7 @@ Plugin-level config at `.opencode/opencode-model-fallback.json` — applies to a
|
|
|
172
172
|
"cooldown_seconds": 60,
|
|
173
173
|
"timeout_seconds": 30,
|
|
174
174
|
"notify_on_fallback": true,
|
|
175
|
-
"fallback_models": ["
|
|
175
|
+
"fallback_models": ["openrouter/minimax/minimax-m2.5:free", "openrouter/qwen/qwen3-coder:free"]
|
|
176
176
|
}
|
|
177
177
|
```
|
|
178
178
|
|
|
@@ -10,6 +10,10 @@ targets:
|
|
|
10
10
|
mode: subagent
|
|
11
11
|
temperature: 0.1
|
|
12
12
|
reasoningEffort: minimal
|
|
13
|
+
fallback_models:
|
|
14
|
+
- openrouter/qwen/qwen3-coder:free
|
|
15
|
+
- openrouter/google/gemma-4-26b-a4b-it:free
|
|
16
|
+
- openrouter/openai/gpt-oss-20b:free
|
|
13
17
|
tools:
|
|
14
18
|
geometra_connect: true
|
|
15
19
|
geometra_page_model: true
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Quality-sensitive worker on
|
|
2
|
+
description: Quality-sensitive worker on the strongest free-tier OpenCode model by default. 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
|
role: quality
|
|
4
4
|
targets:
|
|
5
|
-
# No inline model:
|
|
6
|
-
#
|
|
5
|
+
# No inline model: JobForge's models.yaml maps role "quality" to a
|
|
6
|
+
# free OpenRouter model on OpenCode, while Claude/Codex keep their
|
|
7
|
+
# quality-tier defaults from the standard preset. Claude Code reads
|
|
7
8
|
# .claude/iso-route.resolved.json; OpenCode reads opencode.json's
|
|
8
9
|
# agent.quality.model (iso-harness 0.6.0+).
|
|
9
10
|
opencode:
|
|
10
11
|
mode: subagent
|
|
11
12
|
temperature: 0.3
|
|
12
13
|
reasoningEffort: medium
|
|
14
|
+
fallback_models:
|
|
15
|
+
- openrouter/qwen/qwen3-coder:free
|
|
16
|
+
- openrouter/openai/gpt-oss-120b:free
|
|
17
|
+
- openrouter/z-ai/glm-4.5-air:free
|
|
13
18
|
tools:
|
|
14
19
|
geometra_*: false
|
|
15
20
|
gmail_*: false
|
|
@@ -17,6 +22,11 @@ targets:
|
|
|
17
22
|
|
|
18
23
|
You are the @general-paid subagent. The orchestrator delegated this task to you because it requires quality writing or judgment — the kind of work `@general-free` isn't well-suited for.
|
|
19
24
|
|
|
25
|
+
On OpenCode, this agent now defaults to a free OpenRouter model. On other
|
|
26
|
+
harnesses, the same role may still resolve to a premium model. Your job is
|
|
27
|
+
still the same: produce the best final writing you can from the context you
|
|
28
|
+
were given.
|
|
29
|
+
|
|
20
30
|
## Do These Tasks
|
|
21
31
|
|
|
22
32
|
- Generate evaluation narratives (Blocks A-F) per `modes/offer.md`.
|
|
@@ -10,6 +10,10 @@ targets:
|
|
|
10
10
|
mode: subagent
|
|
11
11
|
temperature: 0
|
|
12
12
|
reasoningEffort: none
|
|
13
|
+
fallback_models:
|
|
14
|
+
- openrouter/minimax/minimax-m2.5:free
|
|
15
|
+
- openrouter/openai/gpt-oss-20b:free
|
|
16
|
+
- openrouter/google/gemma-4-31b-it:free
|
|
13
17
|
tools:
|
|
14
18
|
geometra_*: false
|
|
15
19
|
gmail_*: false
|
package/iso/config.json
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"targets": {
|
|
3
3
|
"opencode": {
|
|
4
|
-
"
|
|
4
|
+
"plugin": ["@razroo/opencode-model-fallback"],
|
|
5
|
+
"instructions": ["templates/states.yml"],
|
|
6
|
+
"small_model": "openrouter/google/gemma-4-26b-a4b-it:free",
|
|
7
|
+
"provider": {
|
|
8
|
+
"openrouter": {
|
|
9
|
+
"models": {
|
|
10
|
+
"qwen/qwen3-coder:free": {},
|
|
11
|
+
"minimax/minimax-m2.5:free": {},
|
|
12
|
+
"qwen/qwen3-next-80b-a3b-instruct:free": {},
|
|
13
|
+
"google/gemma-4-26b-a4b-it:free": {},
|
|
14
|
+
"google/gemma-4-31b-it:free": {},
|
|
15
|
+
"openai/gpt-oss-120b:free": {},
|
|
16
|
+
"openai/gpt-oss-20b:free": {},
|
|
17
|
+
"z-ai/glm-4.5-air:free": {}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
5
21
|
}
|
|
6
22
|
}
|
|
7
23
|
}
|
package/iso/instructions.md
CHANGED
|
@@ -414,6 +414,8 @@ These blocks come from two distinct root causes and require different responses:
|
|
|
414
414
|
|
|
415
415
|
**Rule — do NOT loop retrying a class B block.** One retry with `imeFriendly: true` is the correct test for class A. If the same spam message fires after a clean `imeFriendly` refill, stop, mark Failed, move on. Repeated retries waste subagent time and do not change the outcome.
|
|
416
416
|
|
|
417
|
+
**Class B fix — BYO residential proxy** (added 2026-04-20 via Geometra MCP v1.59.0). When the candidate has configured `proxy:` in `config/profile.yml`, every `geometra_connect` call threads that proxy through to Chromium, which flips the outbound IP from datacenter to residential/mobile and collapses most class-B failures. See the "BYO Residential Proxy" reference section below. Without a configured proxy, class B stays Failed.
|
|
418
|
+
|
|
417
419
|
**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:
|
|
418
420
|
|
|
419
421
|
- 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**
|
|
@@ -505,6 +507,8 @@ geometra_connect({ pageUrl: "https://...", isolated: true, headless: true, slowM
|
|
|
505
507
|
|
|
506
508
|
**Wrong:** running `geometra_connect` without `isolated: true` when submitting multiple forms concurrently. The forms may share state and produce incorrect submissions.
|
|
507
509
|
|
|
510
|
+
**With a configured proxy,** add `proxy: { server, username?, password?, bypass? }` to the same call — see "BYO Residential Proxy" below. The reusable-proxy pool is partitioned by proxy identity, so mixing direct and proxied sessions across parallel rounds is safe.
|
|
511
|
+
|
|
508
512
|
### Session Reuse — When Subagents Cannot Reach Existing Sessions
|
|
509
513
|
|
|
510
514
|
Subagents launched via the `task` tool start with a fresh context and cannot automatically attach to Chromium sessions spawned by a previous orchestrator session. If you dispatch a subagent to fill a form in session `s16`, but `s16` was created by a previous opencode session, the subagent's MCP calls will silently fail (returning empty results) because the subagent's MCP server has no knowledge of `s16`.
|
|
@@ -548,6 +552,17 @@ Step 2: geometra_disconnect({ closeBrowser: true })
|
|
|
548
552
|
Step 3: geometra_connect({ pageUrl: "<the URL the orchestrator gave you>", isolated: true, headless: true, slowMo: 350 })
|
|
549
553
|
```
|
|
550
554
|
|
|
555
|
+
**If the orchestrator passed a `proxy` object in the task prompt** (sourced from `config/profile.yml`), add it to Step 3:
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
Step 3: geometra_connect({
|
|
559
|
+
pageUrl: "<URL>", isolated: true, headless: true, slowMo: 350,
|
|
560
|
+
proxy: { server: "...", username: "...", password: "...", bypass: "..." }
|
|
561
|
+
})
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
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.
|
|
565
|
+
|
|
551
566
|
**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.
|
|
552
567
|
|
|
553
568
|
**Single exception:** if the orchestrator's task prompt says literally "attach to sessionId X" or "use existing session X", skip Steps 1-3 and call `geometra_page_model({ sessionId: "X" })` directly.
|
|
@@ -569,7 +584,64 @@ When the orchestrator dispatches an `apply` (form-fill + submit), pick the subag
|
|
|
569
584
|
|
|
570
585
|
---
|
|
571
586
|
|
|
572
|
-
##
|
|
587
|
+
## BYO Residential Proxy — opt-in outbound-IP override
|
|
588
|
+
|
|
589
|
+
**Problem:** on 2026-04-19 cycle 4, 5/5 untested Ashby tenants and 100% of Dropbox-class Cloudflare-fronted portals fingerprint-blocked headless Chromium from datacenter IPs. `imeFriendly: true` fixes class A (React validation lag) but has zero effect on class B (environment fingerprint). There is no in-session software-only fix for class B: the server decided the session is a bot before the form response was rendered.
|
|
590
|
+
|
|
591
|
+
**Fix:** route the spawned Chromium through a residential or mobile proxy the candidate already pays for. Geometra MCP v1.59.0 added a `proxy: { server, username?, password?, bypass? }` parameter on `geometra_connect` and `geometra_prepare_browser` that forwards straight to Playwright's `chromium.launch({ proxy })`. The outbound IP becomes residential/mobile, and the fingerprint check that fired class B no longer trips.
|
|
592
|
+
|
|
593
|
+
**Opt-in, BYO.** JobForge does NOT bundle or resell proxy bandwidth — the candidate brings their own provider (Bright Data, Oxylabs, SOAX, Smartproxy, mobile hotspot, self-hosted SOCKS). Without a configured proxy, JobForge behavior is unchanged from v2.11.0 and earlier.
|
|
594
|
+
|
|
595
|
+
### Where the proxy config lives
|
|
596
|
+
|
|
597
|
+
`config/profile.yml` → top-level `proxy:` block:
|
|
598
|
+
|
|
599
|
+
```yaml
|
|
600
|
+
proxy:
|
|
601
|
+
server: "http://residential.example.com:8080" # http://, https://, or socks5://
|
|
602
|
+
username: "your-proxy-username" # optional
|
|
603
|
+
password: "your-proxy-password" # optional
|
|
604
|
+
bypass: "*.internal,localhost" # optional
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
See `config/profile.example.yml` for the commented-out template.
|
|
608
|
+
|
|
609
|
+
### How the orchestrator threads it through
|
|
610
|
+
|
|
611
|
+
**Orchestrator responsibilities:**
|
|
612
|
+
|
|
613
|
+
1. On session start, read `config/profile.yml` once. If a `proxy:` block is present, capture it as the `PROXY_CONFIG` for the session.
|
|
614
|
+
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."
|
|
615
|
+
3. When the orchestrator itself opens a Chromium session (single-application interactive flow), include the same `proxy` object in its own `geometra_connect` call.
|
|
616
|
+
4. If `proxy:` is absent from `profile.yml`, skip the param entirely. Do NOT invent a proxy URL or leave a stale placeholder.
|
|
617
|
+
|
|
618
|
+
**Subagent responsibilities:**
|
|
619
|
+
|
|
620
|
+
1. If the task prompt includes a `proxy` object, pass it through to `geometra_connect` and any `geometra_prepare_browser` calls unchanged.
|
|
621
|
+
2. If the task prompt does NOT include a proxy object, run without one.
|
|
622
|
+
3. Never second-guess the proxy field — if the orchestrator sourced it from `profile.yml`, it's authoritative.
|
|
623
|
+
|
|
624
|
+
### When proxy use is load-bearing
|
|
625
|
+
|
|
626
|
+
Apply these rules when deciding whether the proxy is worth waiting for:
|
|
627
|
+
|
|
628
|
+
- **Required** for known-block Ashby tenants (see the class-B list in the Ashby section above), for `happydance.website` / Cloudflare-fronted ATSes, and for any Lever tenant that previously failed in the class-B pattern.
|
|
629
|
+
- **Recommended** for any Ashby tenant NOT on the class-A-compatible list (base rate prior: ~80-90% block headless).
|
|
630
|
+
- **Optional** for Greenhouse, Workday, Lever-clean tenants — these accept datacenter IPs today; using the proxy adds ~100ms per frame but no material downside.
|
|
631
|
+
- **Not useful** for Typeform (Geometra-unsupported), Avature native-select lag (not a fingerprint issue), JazzHR+reCAPTCHA (reCAPTCHA scores unrelated to IP), Breezy (tenant-configured per-IP throttle — proxy may help or may hit a fresh throttle).
|
|
632
|
+
|
|
633
|
+
### Pool partitioning — why mixed runs are safe
|
|
634
|
+
|
|
635
|
+
The Geometra MCP partitions its reusable-proxy pool by `(server, username, bypass)` — see `@geometra/mcp@1.59.0` release notes. A direct session and a proxied session NEVER share a Chromium instance, and two sessions with different proxy configs don't pool either. Practical consequence: flipping `proxy:` on or off in `profile.yml` mid-session is safe — the next `geometra_connect` just opens a fresh Chromium in its own pool partition.
|
|
636
|
+
|
|
637
|
+
### Troubleshooting
|
|
638
|
+
|
|
639
|
+
| Symptom | Diagnosis |
|
|
640
|
+
|---|---|
|
|
641
|
+
| `Error: Failed to connect to proxy` immediately after `geometra_connect` | Proxy URL is wrong / unreachable. Verify the `server:` field hits the right host:port. |
|
|
642
|
+
| `407 Proxy Authentication Required` | `username` or `password` is wrong or missing. Many residential providers require both. |
|
|
643
|
+
| Class-B submit failure persists even with proxy set | (a) proxy is a datacenter proxy, not residential; (b) same tenant IP-banned your specific proxy's IP pool; (c) tenant uses TLS fingerprint / canvas fingerprint, not IP — switch to a fresh Chromium (isolated: true) and retry once, else mark Failed. |
|
|
644
|
+
| Every `geometra_connect` is 3-5s slower than before | Expected — residential proxies add latency. Trade-off for higher submit-success rate. Do NOT revert unless the acceptance-rate lift is < 5%. |
|
|
573
645
|
|
|
574
646
|
- Node.js (mjs modules), Geometra MCP (PDF + scraping + form filling), Gmail MCP (email), YAML (config), HTML/CSS (template), Markdown (data)
|
|
575
647
|
|
package/models.yaml
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# JobForge model policy.
|
|
2
2
|
#
|
|
3
|
-
# Extends @razroo/iso-route's bundled "
|
|
4
|
-
# cost
|
|
5
|
-
#
|
|
6
|
-
#
|
|
3
|
+
# Extends @razroo/iso-route's bundled "openrouter-free" preset — a
|
|
4
|
+
# curated zero-cost routing config that keeps Claude Code / Codex on
|
|
5
|
+
# their native providers but routes OpenCode through explicit free
|
|
6
|
+
# OpenRouter model IDs. Preset content lives in
|
|
7
|
+
# node_modules/@razroo/iso-route/presets/openrouter-free.yaml; run
|
|
8
|
+
# `npx iso-route plan models.yaml` to see the resolved policy.
|
|
7
9
|
#
|
|
8
10
|
# JobForge's subagents bind to preset roles via the `role:` field in
|
|
9
11
|
# iso/agents/<slug>.md:
|
|
10
|
-
# @general-free → role: fast (Haiku /
|
|
11
|
-
# @general-paid → role: quality (Opus 4.7 /
|
|
12
|
-
# @glm-minimal → role: minimal (Haiku /
|
|
12
|
+
# @general-free → role: fast (Haiku / OpenRouter MiniMax M2.5 free / gpt-5.4-mini)
|
|
13
|
+
# @general-paid → role: quality (Opus 4.7 / OpenRouter Qwen3 Next 80B free / gpt-5.4)
|
|
14
|
+
# @glm-minimal → role: minimal (Haiku / OpenRouter Gemma 4 26B free / gpt-5.4-nano)
|
|
13
15
|
#
|
|
14
16
|
# Override anything by adding fields here. For example, to pin Opus on
|
|
15
17
|
# Claude Code for the @general-paid (quality) role:
|
|
@@ -27,4 +29,14 @@
|
|
|
27
29
|
# provider: openai
|
|
28
30
|
# model: gpt-5.4
|
|
29
31
|
|
|
30
|
-
extends:
|
|
32
|
+
extends: openrouter-free
|
|
33
|
+
|
|
34
|
+
# Only override the preset where we prefer a different OpenRouter pick.
|
|
35
|
+
# The preset's quality-tier OpenCode pick is openai/gpt-oss-120b:free;
|
|
36
|
+
# we prefer Qwen3 Next 80B for job-application writing quality.
|
|
37
|
+
roles:
|
|
38
|
+
quality:
|
|
39
|
+
targets:
|
|
40
|
+
opencode:
|
|
41
|
+
provider: openrouter
|
|
42
|
+
model: qwen/qwen3-next-80b-a3b-instruct:free
|
package/modes/apply.md
CHANGED
|
@@ -39,9 +39,12 @@ Live application assistant. Reads the active application form in Chrome (via Geo
|
|
|
39
39
|
- [D6] Use `fieldLabel` over `fieldId` everywhere it works.
|
|
40
40
|
why: labels are stable across DOM refreshes; IDs are regenerated
|
|
41
41
|
|
|
42
|
+
- [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.
|
|
43
|
+
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.
|
|
44
|
+
|
|
42
45
|
## Procedure
|
|
43
46
|
|
|
44
|
-
1. `geometra_connect` + `geometra_page_model`;
|
|
47
|
+
1. `geometra_connect` + `geometra_page_model`; thread `proxy` if present [D7]; no WebFetch [D5].
|
|
45
48
|
2. If Geometra is unavailable, ask for screenshot or pasted text [D2].
|
|
46
49
|
3. Extract company + role; Grep `reports/` for a matching evaluation.
|
|
47
50
|
4. Load full report + Section G if present.
|
|
@@ -317,7 +320,8 @@ Call 3: geometra_connect({
|
|
|
317
320
|
pageUrl: "<the same URL as before>",
|
|
318
321
|
isolated: true,
|
|
319
322
|
headless: true,
|
|
320
|
-
slowMo: 350
|
|
323
|
+
slowMo: 350,
|
|
324
|
+
proxy: <pass through from task prompt if present; omit otherwise>
|
|
321
325
|
})
|
|
322
326
|
Call 4: geometra_run_actions({
|
|
323
327
|
sessionId: "<new sessionId from Call 3>",
|
package/opencode.json
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://opencode.ai/config.json",
|
|
3
|
-
"model": "
|
|
3
|
+
"model": "openrouter/qwen/qwen3-coder:free",
|
|
4
4
|
"agent": {
|
|
5
5
|
"fast": {
|
|
6
|
-
"model": "
|
|
6
|
+
"model": "openrouter/minimax/minimax-m2.5:free"
|
|
7
7
|
},
|
|
8
8
|
"quality": {
|
|
9
|
-
"model": "
|
|
9
|
+
"model": "openrouter/qwen/qwen3-next-80b-a3b-instruct:free"
|
|
10
10
|
},
|
|
11
11
|
"minimal": {
|
|
12
|
-
"model": "
|
|
12
|
+
"model": "openrouter/google/gemma-4-26b-a4b-it:free"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"provider": {
|
|
16
|
+
"openrouter": {
|
|
17
|
+
"models": {
|
|
18
|
+
"qwen/qwen3-coder:free": {},
|
|
19
|
+
"minimax/minimax-m2.5:free": {},
|
|
20
|
+
"qwen/qwen3-next-80b-a3b-instruct:free": {},
|
|
21
|
+
"google/gemma-4-26b-a4b-it:free": {},
|
|
22
|
+
"google/gemma-4-31b-it:free": {},
|
|
23
|
+
"openai/gpt-oss-120b:free": {},
|
|
24
|
+
"openai/gpt-oss-20b:free": {},
|
|
25
|
+
"z-ai/glm-4.5-air:free": {}
|
|
26
|
+
}
|
|
13
27
|
}
|
|
14
28
|
},
|
|
15
29
|
"mcp": {
|
|
@@ -34,7 +48,11 @@
|
|
|
34
48
|
}
|
|
35
49
|
}
|
|
36
50
|
},
|
|
51
|
+
"plugin": [
|
|
52
|
+
"@razroo/opencode-model-fallback"
|
|
53
|
+
],
|
|
37
54
|
"instructions": [
|
|
38
55
|
"templates/states.yml"
|
|
39
|
-
]
|
|
56
|
+
],
|
|
57
|
+
"small_model": "openrouter/google/gemma-4-26b-a4b-it:free"
|
|
40
58
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "job-forge",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "AI-powered job search pipeline built on opencode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -87,9 +87,10 @@
|
|
|
87
87
|
"playwright": "^1.58.1"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
|
-
"@razroo/iso": "^0.2.
|
|
90
|
+
"@razroo/iso": "^0.2.5",
|
|
91
91
|
"@razroo/iso-harness": "^0.6.0",
|
|
92
|
-
"@razroo/iso-route": "^0.
|
|
93
|
-
"@razroo/iso-trace": "^0.1.0"
|
|
92
|
+
"@razroo/iso-route": "^0.5.0",
|
|
93
|
+
"@razroo/iso-trace": "^0.1.0",
|
|
94
|
+
"@razroo/opencode-model-fallback": "^0.3.1"
|
|
94
95
|
}
|
|
95
96
|
}
|