job-forge 2.0.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/.codex/config.toml +8 -0
- package/.cursor/mcp.json +21 -0
- package/.cursor/rules/main.mdc +519 -0
- package/.mcp.json +21 -0
- package/.opencode/agents/general-free.md +85 -0
- package/.opencode/agents/general-paid.md +39 -0
- package/.opencode/agents/glm-minimal.md +50 -0
- package/.opencode/skills/job-forge.md +185 -0
- package/AGENTS.md +514 -0
- package/CLAUDE.md +514 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/batch/README.md +60 -0
- package/batch/batch-prompt.md +399 -0
- package/batch/batch-runner.sh +673 -0
- package/bin/create-job-forge.mjs +375 -0
- package/bin/job-forge.mjs +120 -0
- package/bin/sync.mjs +141 -0
- package/config/profile.example.yml +67 -0
- package/cv-sync-check.mjs +128 -0
- package/dedup-tracker.mjs +201 -0
- package/docs/ARCHITECTURE.md +220 -0
- package/docs/CUSTOMIZATION.md +101 -0
- package/docs/MODEL-ROUTING.md +195 -0
- package/docs/README.md +54 -0
- package/docs/SETUP.md +186 -0
- package/docs/demo.gif +0 -0
- package/fonts/dm-sans-latin-ext.woff2 +0 -0
- package/fonts/dm-sans-latin.woff2 +0 -0
- package/fonts/space-grotesk-latin-ext.woff2 +0 -0
- package/fonts/space-grotesk-latin.woff2 +0 -0
- package/generate-pdf.mjs +168 -0
- package/iso/agents/general-free.md +90 -0
- package/iso/agents/general-paid.md +44 -0
- package/iso/agents/glm-minimal.md +55 -0
- package/iso/commands/job-forge.md +188 -0
- package/iso/config.json +7 -0
- package/iso/instructions.md +514 -0
- package/iso/mcp.json +15 -0
- package/merge-tracker.mjs +377 -0
- package/modes/README.md +30 -0
- package/modes/_shared-calibration.md +26 -0
- package/modes/_shared.md +272 -0
- package/modes/apply.md +257 -0
- package/modes/auto-pipeline.md +70 -0
- package/modes/batch.md +110 -0
- package/modes/compare.md +23 -0
- package/modes/contact.md +82 -0
- package/modes/deep.md +99 -0
- package/modes/followup.md +68 -0
- package/modes/negotiation.md +146 -0
- package/modes/offer.md +199 -0
- package/modes/pdf.md +121 -0
- package/modes/pipeline.md +83 -0
- package/modes/project.md +30 -0
- package/modes/rejection.md +92 -0
- package/modes/scan.md +185 -0
- package/modes/tracker.md +31 -0
- package/modes/training.md +27 -0
- package/normalize-statuses.mjs +152 -0
- package/opencode.json +28 -0
- package/package.json +78 -0
- package/scripts/add-tags.mjs +894 -0
- package/scripts/cursor-agent-loop.sh +211 -0
- package/scripts/cursor-agent-stream-format.py +134 -0
- package/scripts/next-num.mjs +33 -0
- package/scripts/release/check-source.mjs +37 -0
- package/scripts/render-report-header.mjs +78 -0
- package/scripts/session-report.mjs +129 -0
- package/scripts/slugify.mjs +27 -0
- package/scripts/today.mjs +20 -0
- package/scripts/token-usage-report.mjs +315 -0
- package/scripts/tracker-line.mjs +67 -0
- package/scripts/verify-greenhouse-urls.mjs +195 -0
- package/templates/cv-template.html +395 -0
- package/templates/portals.example.yml +3140 -0
- package/templates/states.yml +62 -0
- package/tracker-lib.mjs +257 -0
- package/verify-pipeline.mjs +267 -0
package/.cursor/mcp.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"geometra": {
|
|
4
|
+
"command": "npx",
|
|
5
|
+
"args": [
|
|
6
|
+
"-y",
|
|
7
|
+
"@geometra/mcp"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"gmail": {
|
|
11
|
+
"command": "npx",
|
|
12
|
+
"args": [
|
|
13
|
+
"-y",
|
|
14
|
+
"@razroo/gmail-mcp"
|
|
15
|
+
],
|
|
16
|
+
"env": {
|
|
17
|
+
"DISABLE_HTTP": "true"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Project instructions
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JobForge -- AI Job Search Pipeline
|
|
7
|
+
|
|
8
|
+
## Hard Limits — NEVER exceed these numbers
|
|
9
|
+
|
|
10
|
+
The Hard Limits below are non-negotiable numeric rules. If you catch yourself about to violate one, STOP and restructure.
|
|
11
|
+
|
|
12
|
+
1. **Max parallel subagents: 2.** Never emit 3+ `task` tool calls in a single message. For N jobs, run `ceil(N/2)` sequential rounds of 2. No exceptions — not for "urgent", not for "the user asked for 10".
|
|
13
|
+
2. **Max 1 application per company+role.** Before every `task` dispatch for `apply`, Grep `data/pipeline.md` and today's `data/applications/*.md` for the URL and for `company+role`. If already APPLIED, skip that job and do not dispatch.
|
|
14
|
+
3. **Always clean Geometra sessions before dispatching.** Before every round of `task` dispatches that will use Geometra, call `geometra_list_sessions` then `geometra_disconnect({closeBrowser: true})`. Every round. The disconnect is a no-op when the pool is empty.
|
|
15
|
+
4. **Orchestrator does NOT fill forms.** This session MUST NOT call `geometra_fill_form`, `geometra_run_actions`, `geometra_pick_listbox_option`, or `geometra_fill_otp` when handling a multi-job request. If you need to, it means you MUST have delegated — `task` out the remaining work instead.
|
|
16
|
+
5. **Re-dispatch only AFTER the previous subagent returns.** Never fire the same company's `task` twice while the first is still in-flight. Wait for the return value, then decide if a retry is warranted.
|
|
17
|
+
6. **Application outcomes flow through TSVs, not `data/pipeline.md`.** When a subagent returns APPLIED / FAILED / SKIP, the outcome goes to `batch/tracker-additions/{num}-{slug}.tsv`. `node merge-tracker.mjs` then consumes the TSVs into the correct `data/applications/YYYY-MM-DD.md` day file. `data/pipeline.md` only tracks URL inbox state (`[ ]` pending → `[x]` processed). **NEVER append APPLIED / FAILED status lines to `pipeline.md`** — that's the day file's job, via the TSV pathway. After any multi-apply run, the orchestrator MUST run `node merge-tracker.mjs` followed by `node verify-pipeline.mjs` before ending the session.
|
|
18
|
+
|
|
19
|
+
Everything below is context and rationale. These six numbers are the rules.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Session Hygiene — ALWAYS enforce
|
|
24
|
+
|
|
25
|
+
**Multi-job workflows MUST delegate each job to its own subagent.** This rule applies even when the user does NOT explicitly invoke `/job-forge`.
|
|
26
|
+
|
|
27
|
+
Whenever the user says any variation of "apply to N jobs", "process the pipeline", "batch evaluate", or similar phrasing that implies more than one application/evaluation in sequence:
|
|
28
|
+
|
|
29
|
+
1. **Do not drive all N jobs from this session.** Repeated `geometra_fill_form` / `geometra_page_model` calls accumulate in conversation history and invalidate prompt caching — each new message ends up re-processing 100K+ tokens of fresh history instead of reading from cache.
|
|
30
|
+
2. **Launch one subagent per job, in parallel batches of ≤2** (see Hard Limits #1). Higher parallelism blows through free-tier rate limits and each subagent requires post-cleanup. Use the `task` tool / Agent with `subagent_type="general-purpose"`, passing the single URL and the relevant mode file content.
|
|
31
|
+
3. **This session acts as the orchestrator only**: plan, pick the jobs, dispatch subagents, aggregate results. No Geometra form-filling in this session unless it's a single one-off application.
|
|
32
|
+
|
|
33
|
+
**Why:** observed on a real run — a 341-msg "apply to 20 jobs" session had `cache_read ~1.8K` on 5 messages where input ballooned to 100K-144K tokens. A 40-msg orchestrator session that delegates instead stays under 40K input max with cache reads at full 100K+. Same work, ~5× fewer effective tokens.
|
|
34
|
+
|
|
35
|
+
**Verify after running:** `npx job-forge tokens --session <id>` — any message with `cache_read < 5K` and `input > 50K` is a cache-bust; next time split that work across subagents.
|
|
36
|
+
|
|
37
|
+
**Exception:** evaluation-only or tracker-only work (no Geometra, no repeated tool calls) can proceed in a single session. The rule targets tool-heavy multi-step loops.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Subagent Routing — which agent for which task
|
|
42
|
+
|
|
43
|
+
The harness ships three subagents (see `.opencode/agents/`). The orchestrator MUST route work by cost tier, not pick the default for everything. **GLM 5.1 does not discount cache reads**, so running procedural work on it costs ~10× what it would on a cache-discounting model. Free-tier models handle procedural work fine (confirmed empirically: `opencode/big-pickle` processed 1000+ messages at $0 in prior runs).
|
|
44
|
+
|
|
45
|
+
| Task type | Subagent | Why |
|
|
46
|
+
|-----------|----------|-----|
|
|
47
|
+
| Drive Geometra form-fill / submit (atomic `run_actions`) | `@general-free` | Procedural; label-driven; deterministic |
|
|
48
|
+
| Merge TSVs, run `verify-pipeline.mjs`, dedup | `@general-free` | Script-driven; no writing quality needed |
|
|
49
|
+
| OTP retrieval via Gmail MCP + `geometra_fill_otp` | `@general-free` | Fixed-shape lookup + input |
|
|
50
|
+
| Scan portals, extract offer metadata, return structured records (see schema below) | `@general-free` | Structured output; no judgment |
|
|
51
|
+
| Evaluation narrative — Blocks A-F per `modes/offer.md` | `@general-paid` | Judgment + writing quality |
|
|
52
|
+
| Cover letter, "Why X?" answers, Section G drafts | `@general-paid` | Tone and specificity matter |
|
|
53
|
+
| STAR+R interview stories, story-bank curation | `@general-paid` | Quality signals seniority |
|
|
54
|
+
| LinkedIn outreach messages (`modes/contact.md`) | `@general-paid` | First impression |
|
|
55
|
+
| "Extract N fields from this text → JSON" (≤5K input) | `@glm-minimal` | One-shot transform; no context needed |
|
|
56
|
+
| "Classify this JD as archetype X/Y/Z" | `@glm-minimal` | Narrow, structured output |
|
|
57
|
+
|
|
58
|
+
**Example JSON shape for the "extract / emit JSON" subagent rows above** (use this exact key set when delegating a portal-scan / extract task):
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"company": "Acme",
|
|
63
|
+
"role": "Senior Backend Engineer",
|
|
64
|
+
"location": "Remote (US)",
|
|
65
|
+
"comp_range_usd": "180000-220000",
|
|
66
|
+
"archetype": "backend-platform",
|
|
67
|
+
"url": "https://..."
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Rule:** when you (the orchestrator) delegate a task, pick the cheapest agent that can do it well. Do NOT route every subagent through the same tier. Auto-pipeline mode MUST split a single job across `@general-paid` (evaluation) and `@general-free` (PDF gen + tracker + apply), not run it all on one model.
|
|
72
|
+
|
|
73
|
+
**When to break this rule:** if the user explicitly asks for "quality over cost" or flags a high-stakes application (top-tier company, offer-stage negotiation, executive search), route everything through `@general-paid`. Document the exception in the session.
|
|
74
|
+
|
|
75
|
+
### Pre-flight delegation (HARD RULE)
|
|
76
|
+
|
|
77
|
+
For any task that will involve **more than one tool call** — i.e., anything beyond a one-shot answer — the orchestrator's **first tool call MUST be `task`** (dispatching to a subagent). Not `Read`, not `Bash`, not `geometra_connect`, not `Grep`. The orchestrator plans and dispatches; subagents execute.
|
|
78
|
+
|
|
79
|
+
**Why this is absolute:** every tool call in the orchestrator accumulates in the top-level session's history and pollutes the cache prefix. Once the orchestrator has read three files and made two Geometra calls, delegating to a subagent no longer helps — the subagent inherits the bloated context. The only way to keep the orchestrator lean is to delegate *before* doing anything else.
|
|
80
|
+
|
|
81
|
+
**What counts as "more than one tool call":**
|
|
82
|
+
- Evaluating any offer (always ≥3 steps: fetch JD, score, write report)
|
|
83
|
+
- Any `/job-forge` mode invocation except `tracker` (read-only)
|
|
84
|
+
- Applying to a job
|
|
85
|
+
- Scanning portals
|
|
86
|
+
- Any batch operation
|
|
87
|
+
|
|
88
|
+
**Explicit exception:** trivial one-shot answers — "what does this error mean?", "read this file and summarize", "what's my next report number?" — can stay in the orchestrator. If the question can be answered in ≤1 tool call, do not delegate.
|
|
89
|
+
|
|
90
|
+
**Detection signal:** if you (orchestrator) find yourself about to make your 2nd tool call in a session that wasn't a trivial one-shot, STOP. Instead, `task` out the remaining work as a single delegated job.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## What is JobForge
|
|
95
|
+
|
|
96
|
+
AI-powered job search automation built on opencode: pipeline tracking, offer evaluation, CV generation, portal scanning, batch processing.
|
|
97
|
+
|
|
98
|
+
**It will work out of the box, but it's designed to be made yours.** Ask if the archetypes don't match your career. Ask if the modes are in the wrong language. Ask if the scoring doesn't fit your priorities. You (opencode) can edit any file in this system. The user says "change the archetypes to data engineering roles" and you do it. Customization is the whole point.
|
|
99
|
+
|
|
100
|
+
### Main Files
|
|
101
|
+
|
|
102
|
+
| File | Function |
|
|
103
|
+
|------|----------|
|
|
104
|
+
| `data/applications/` | Application tracker (day-based: `YYYY-MM-DD.md`) |
|
|
105
|
+
| `data/pipeline.md` | Inbox of pending URLs |
|
|
106
|
+
| `data/scan-history.tsv` | Scanner dedup history |
|
|
107
|
+
| `portals.yml` | Query and company config |
|
|
108
|
+
| `templates/cv-template.html` | HTML template for CVs |
|
|
109
|
+
| `generate-pdf.mjs` | Geometra MCP (`geometra_generate_pdf`): HTML to PDF |
|
|
110
|
+
| `article-digest.md` | Compact proof points from portfolio (optional) |
|
|
111
|
+
| `interview-prep/story-bank.md` | Accumulated STAR+R stories across evaluations |
|
|
112
|
+
| `reports/` | Evaluation reports (format: `{###}-{company-slug}-{YYYY-MM-DD}.md`) |
|
|
113
|
+
|
|
114
|
+
### First Run — Onboarding (IMPORTANT)
|
|
115
|
+
|
|
116
|
+
**Before doing ANYTHING else, check if the system is set up.** Run these checks silently every time a session starts:
|
|
117
|
+
|
|
118
|
+
1. Does `cv.md` exist?
|
|
119
|
+
2. Does `config/profile.yml` exist (not just profile.example.yml)?
|
|
120
|
+
3. Does `portals.yml` exist (not just templates/portals.example.yml)?
|
|
121
|
+
|
|
122
|
+
**If ANY of these is missing, enter onboarding mode.** Do NOT proceed with evaluations, scans, or any other mode until the basics are in place. Guide the user step by step:
|
|
123
|
+
|
|
124
|
+
#### Step 1: CV (required)
|
|
125
|
+
If `cv.md` is missing, ask:
|
|
126
|
+
> "I don't have your CV yet. You can either:
|
|
127
|
+
> 1. Paste your CV here and I'll convert it to markdown
|
|
128
|
+
> 2. Paste your LinkedIn URL and I'll extract the key info
|
|
129
|
+
> 3. Tell me about your experience and I'll draft a CV for you
|
|
130
|
+
>
|
|
131
|
+
> Which do you prefer?"
|
|
132
|
+
|
|
133
|
+
Create `cv.md` from whatever they provide. Make it clean markdown with standard sections (Summary, Experience, Projects, Education, Skills).
|
|
134
|
+
|
|
135
|
+
#### Step 2: Profile (required)
|
|
136
|
+
If `config/profile.yml` is missing, copy from `config/profile.example.yml` and then ask:
|
|
137
|
+
> "I need a few details to personalize the system:
|
|
138
|
+
> - Your full name and email
|
|
139
|
+
> - Your location and timezone
|
|
140
|
+
> - What roles are you targeting? (e.g., 'Senior Backend Engineer', 'AI Product Manager')
|
|
141
|
+
> - Your salary target range
|
|
142
|
+
>
|
|
143
|
+
> I'll set everything up for you."
|
|
144
|
+
|
|
145
|
+
Fill in `config/profile.yml` with their answers. For archetypes, map their target roles to the closest matches and update `modes/_shared.md` when the existing archetypes do not cover their target roles.
|
|
146
|
+
|
|
147
|
+
#### Step 3: Portals (recommended)
|
|
148
|
+
If `portals.yml` is missing:
|
|
149
|
+
> "I'll set up the job scanner with 45+ pre-configured companies. Want me to customize the search keywords for your target roles?"
|
|
150
|
+
|
|
151
|
+
Copy `templates/portals.example.yml` → `portals.yml`. If they gave target roles in Step 2, update `title_filter.positive` to match.
|
|
152
|
+
|
|
153
|
+
#### Step 4: Tracker
|
|
154
|
+
If `data/applications/` directory doesn't exist, create it:
|
|
155
|
+
```bash
|
|
156
|
+
mkdir -p data/applications
|
|
157
|
+
```
|
|
158
|
+
The tracker stores entries in day-based files like `data/applications/2026-04-13.md`. Each file has the same table format:
|
|
159
|
+
```markdown
|
|
160
|
+
# Applications Tracker
|
|
161
|
+
|
|
162
|
+
| # | Date | Company | Role | Score | Status | PDF | Report | Notes |
|
|
163
|
+
|---|------|---------|------|-------|--------|-----|--------|-------|
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Step 5: Ready
|
|
167
|
+
Once all files exist, confirm:
|
|
168
|
+
> "You're all set! You can now:
|
|
169
|
+
> - Paste a job URL to evaluate it
|
|
170
|
+
> - Run `/job-forge scan` to search portals
|
|
171
|
+
> - Run `/job-forge` to see all commands
|
|
172
|
+
>
|
|
173
|
+
> Everything is customizable — just ask me to change anything.
|
|
174
|
+
>
|
|
175
|
+
"
|
|
176
|
+
|
|
177
|
+
Then suggest automation:
|
|
178
|
+
> "Want me to scan for new offers automatically? I can set up a recurring scan every few days so you don't miss anything. Just say 'scan every 3 days' and I'll configure it."
|
|
179
|
+
|
|
180
|
+
If the user accepts, use the `/loop` or `/schedule` skill (if available) to set up a recurring `/job-forge scan`. If those aren't available, suggest adding a cron job or remind them to run `/job-forge scan` periodically.
|
|
181
|
+
|
|
182
|
+
### Personalization
|
|
183
|
+
|
|
184
|
+
JobForge is designed to be customized by YOU (opencode). When the user asks you to change archetypes, translate modes, adjust scoring, add companies, or modify negotiation scripts -- do it directly. You read the same files you use, so you know exactly what to edit.
|
|
185
|
+
|
|
186
|
+
**Common customization requests:**
|
|
187
|
+
- "Change the archetypes to [backend/frontend/data/devops] roles" → edit `modes/_shared.md`
|
|
188
|
+
- "Translate the modes to English" → edit all files in `modes/`
|
|
189
|
+
- "Add these companies to my portals" → edit `portals.yml`
|
|
190
|
+
- "Update my profile" → edit `config/profile.yml`
|
|
191
|
+
- "Change the CV template design" → edit `templates/cv-template.html`
|
|
192
|
+
- "Adjust the scoring weights" → edit `modes/_shared.md` and `batch/batch-prompt.md`
|
|
193
|
+
|
|
194
|
+
### Skill Modes
|
|
195
|
+
|
|
196
|
+
| If the user... | Mode |
|
|
197
|
+
|----------------|------|
|
|
198
|
+
| Pastes JD or URL | auto-pipeline (evaluate + report + PDF + tracker) |
|
|
199
|
+
| Asks to evaluate offer | `offer` |
|
|
200
|
+
| Asks to compare offers | `compare` |
|
|
201
|
+
| Wants LinkedIn outreach | `contact` |
|
|
202
|
+
| Asks for company research | `deep` |
|
|
203
|
+
| Wants to generate CV/PDF | `pdf` |
|
|
204
|
+
| Evaluates a course/cert | `training` |
|
|
205
|
+
| Evaluates portfolio project | `project` |
|
|
206
|
+
| Asks about application status | `tracker` |
|
|
207
|
+
| Fills out application form | `apply` |
|
|
208
|
+
| Searches for new offers | `scan` |
|
|
209
|
+
| Processes pending URLs | `pipeline` |
|
|
210
|
+
| Batch processes offers | `batch` |
|
|
211
|
+
| Asks what needs follow-up | `followup` |
|
|
212
|
+
| Reports a rejection | `rejection` |
|
|
213
|
+
| Receives a job offer | `negotiation` |
|
|
214
|
+
|
|
215
|
+
### CV Source of Truth
|
|
216
|
+
|
|
217
|
+
- `cv.md` in project root is the canonical CV
|
|
218
|
+
- `article-digest.md` has detailed proof points (optional)
|
|
219
|
+
- **NEVER hardcode metrics** -- read them from these files at evaluation time
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Ethical Use -- CRITICAL
|
|
224
|
+
|
|
225
|
+
**Applying to jobs via Geometra MCP is NOT impersonation.** The agent uses the user's credentials, CV, and information to apply on their behalf. This is the same as the user applying themselves — the agent is an extension of the user's will. Think of it as a browser automation tool that fills forms with the user's data.
|
|
226
|
+
|
|
227
|
+
**This system is designed for quality, not quantity.** The goal is to help the user find and apply to roles where there is a genuine match -- not to spam companies with mass applications.
|
|
228
|
+
|
|
229
|
+
- **Continuous pipeline for 3.0+ offers:** When scanning, evaluating, and applying — run the full pipeline end-to-end without pausing for confirmation. Scan → evaluate → fill form → submit for any offer scoring 3.0/5 or above. Do NOT stop between steps to ask "want me to continue?" — just do it.
|
|
230
|
+
- **Auto-submit:** For offers scoring 3.0+/5, fill and submit the application automatically. For offers below 3.0/5, mark as SKIP and move on.
|
|
231
|
+
- **Still respect quality:** Only apply where there is a genuine match (3.0+ ensures this). Auto-SKIP anything below 3.0.
|
|
232
|
+
- **Respect recruiters' time.** Every application a human reads costs someone's attention. Only send what's worth reading.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Offer Verification -- MANDATORY
|
|
237
|
+
|
|
238
|
+
**When Geometra MCP is available** (interactive sessions), ALWAYS use it to verify offers:
|
|
239
|
+
1. `geometra_connect` to the URL (via proxy)
|
|
240
|
+
2. `geometra_page_model` to read structured page content
|
|
241
|
+
3. Only footer/navbar without JD = closed. Title + description + Apply = active.
|
|
242
|
+
|
|
243
|
+
**When Geometra MCP is NOT available** (batch workers via `opencode run`, headless environments):
|
|
244
|
+
1. Use WebFetch to retrieve the page content
|
|
245
|
+
2. Check for JD text, job title, and apply button/link in the response
|
|
246
|
+
3. If WebFetch returns only a shell/navbar (no JD content), mark the offer as `**Verification: unconfirmed**` in the report header
|
|
247
|
+
4. Do NOT skip the evaluation — proceed but flag the uncertainty so the user can verify manually before applying
|
|
248
|
+
|
|
249
|
+
The goal is to never waste time on closed offers, but also never silently assume a role is active when verification was incomplete.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## OTP Handling via Gmail MCP -- REQUIRED
|
|
254
|
+
|
|
255
|
+
When a form says "enter the code we sent to your email", you MUST retrieve the code from Gmail. NEVER ask the user to paste it. NEVER mark the application as failed without checking Gmail first.
|
|
256
|
+
|
|
257
|
+
**You have exactly two Gmail tools.** There is NO `gmail_search_messages` and NO `gmail_read_message`. Use only these:
|
|
258
|
+
|
|
259
|
+
| Tool | What it does | Key parameter |
|
|
260
|
+
|------|-------------|---------------|
|
|
261
|
+
| `gmail_list_messages` | Search emails. Returns message IDs + snippets. | `q` — Gmail search query string |
|
|
262
|
+
| `gmail_get_message` | Read one email by ID. Returns full headers + body. | `id` — message ID from step 1 |
|
|
263
|
+
|
|
264
|
+
**Step-by-step recipe (follow exactly):**
|
|
265
|
+
|
|
266
|
+
1. Reach the OTP step in the form. Do NOT close or abandon the session.
|
|
267
|
+
2. Wait ~5-10 seconds for the email to arrive.
|
|
268
|
+
3. Call `gmail_list_messages` with `q` set to the sender query from the Sender Lookup Table. Example:
|
|
269
|
+
```
|
|
270
|
+
gmail_list_messages({ q: "from:greenhouse newer_than:10m", maxResults: 5 })
|
|
271
|
+
```
|
|
272
|
+
4. Take the `id` field from the first result. Call `gmail_get_message` with that `id`. Example:
|
|
273
|
+
```
|
|
274
|
+
gmail_get_message({ id: "19d84d63a273c271" })
|
|
275
|
+
```
|
|
276
|
+
5. Find the code in the snippet or body. It is usually 6-8 characters near words like "security code" or "verification code".
|
|
277
|
+
6. Call `geometra_fill_otp` with the code. Example:
|
|
278
|
+
```
|
|
279
|
+
geometra_fill_otp({ value: "ABC12345", sessionId: "..." })
|
|
280
|
+
```
|
|
281
|
+
7. Submit the form.
|
|
282
|
+
|
|
283
|
+
**Sender Lookup Table:**
|
|
284
|
+
|
|
285
|
+
| Portal | `q` value for `gmail_list_messages` |
|
|
286
|
+
|--------|-------------------------------------|
|
|
287
|
+
| Greenhouse | `from:greenhouse newer_than:10m` |
|
|
288
|
+
| Workday | `from:myworkday newer_than:10m` |
|
|
289
|
+
| Lever | `from:lever newer_than:10m` |
|
|
290
|
+
| Ashby | `from:ashby newer_than:10m` |
|
|
291
|
+
| Unknown | `newer_than:10m subject:(verify OR code OR confirm)` |
|
|
292
|
+
|
|
293
|
+
**Rules:**
|
|
294
|
+
- ALWAYS check Gmail before reporting a submission as failed.
|
|
295
|
+
- If "submit button did nothing", it usually means an OTP step appeared. Check Gmail.
|
|
296
|
+
- If no email after 10 seconds, retry `gmail_list_messages` once more with `newer_than:5m`.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Geometra Form-Fill Patterns
|
|
301
|
+
|
|
302
|
+
### Validation State Lags Behind Actual Field State
|
|
303
|
+
|
|
304
|
+
**This is a known issue across Greenhouse, Ashby, and similar ATS portals.** The frontend validation does not always update synchronously with field input. A field can be correctly filled but still show `invalid: true` or "This field is required" in the schema for 3-10 seconds — or even permanently until the user interacts with another field.
|
|
305
|
+
|
|
306
|
+
**Common false-positive patterns:**
|
|
307
|
+
- `set_checked` / `geometra_set_checked` sets a checkbox to `checked: true`, but the schema still shows `invalid: true` with "This field is required." A known lag affects privacy policy / acknowledgment checkboxes.
|
|
308
|
+
- A dropdown/choice field is correctly picked, but the invalid flag persists.
|
|
309
|
+
- A text field is filled correctly, but validation error text remains until the user tabs or blurs the field.
|
|
310
|
+
- Combobox / autocomplete fields show stale "invalid" overlays after correct selection (Greenhouse, Ashby, Workday, Lever) but submit successfully.
|
|
311
|
+
|
|
312
|
+
**Rule: Do NOT get stuck in a fill loop.** If a field value looks correct (checked=true, value="No", "Yes") but `invalidCount` is unchanged:
|
|
313
|
+
|
|
314
|
+
1. **Try Submit anyway.** The major portals (Greenhouse, Workday, Lever, Ashby) allow submission with stale validation errors as long as the underlying value is correct.
|
|
315
|
+
2. **If Submit is disabled**, try interacting with a nearby field (Tab, click another input) to force validation recalculation.
|
|
316
|
+
3. **If a checkbox still shows invalid after `set_checked`**, try clicking it directly by coordinates (`geometra_click` with x,y) instead of the label-based toggle.
|
|
317
|
+
4. **For combobox fields**, pick the option via `geometra_pick_listbox_option` (preferred) rather than typing — typing into comboboxes often creates a stale autocomplete overlay that blocks confirmation.
|
|
318
|
+
|
|
319
|
+
**Decision tree for "field shows invalid after fill":**
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
Is the visible value correct?
|
|
323
|
+
├── YES → Try Submit (preferred action)
|
|
324
|
+
│ If Submit disabled → Tab away and back, then try Submit
|
|
325
|
+
│ Still blocked → try clicking a nearby field to force recalc
|
|
326
|
+
└── NO → Re-fill the field using the correct field id
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**The `invalidCount` from schema is a heuristic, not ground truth.** Always prefer direct observation of field values over the invalid count. If Submit becomes enabled, ignore any remaining invalid fields — the portal accepted the data.
|
|
330
|
+
|
|
331
|
+
### Nested Scroll Containers (Greenhouse / Ashby)
|
|
332
|
+
|
|
333
|
+
The major ATS portals (Greenhouse, Workday, Lever, Ashby) use nested scrollable regions. A field's `visibleBounds` may show it as off-screen even when it is actually visible within a child scroll container. Geometra's `scroll_to` operates on the outermost page scroll, so it cannot reach fields in inner scroll regions.
|
|
334
|
+
|
|
335
|
+
**Signs you are dealing with nested scroll:**
|
|
336
|
+
- `scroll_to` reports `revealed: false` with `maxSteps` exhausted, but you can see the field in the page model
|
|
337
|
+
- A field's `y` coordinate in `bounds` is far outside the viewport, yet it is visible on screen
|
|
338
|
+
- Wheel events at one `y` coordinate scroll a different region than expected
|
|
339
|
+
|
|
340
|
+
**Workaround:**
|
|
341
|
+
1. Use `geometra_wheel` at a low `y` value (e.g., 360, near the top of the viewport) to scroll the outer container
|
|
342
|
+
2. Alternatively, click directly on the element using `geometra_click` with x,y coordinates derived from the element's `visibleBounds` center
|
|
343
|
+
3. Once in the correct scroll region, `scroll_to` within that region works correctly
|
|
344
|
+
|
|
345
|
+
### Corrupted Fields (Text Typed Into Listbox)
|
|
346
|
+
|
|
347
|
+
Sometimes text typed into the wrong field (e.g., an essay pasted into a listbox search field) corrupts the field state. The listbox shows the typed text as a search query and refuses to clear.
|
|
348
|
+
|
|
349
|
+
**Recovery:**
|
|
350
|
+
1. Find and click the "Clear selections" button (`role: "button"`, `name: "Clear selections"`) — this usually resets the field
|
|
351
|
+
2. After clearing, use `geometra_pick_listbox_option` to select the correct value
|
|
352
|
+
3. If "Clear selections" is not available, try pressing `Escape` multiple times or clicking outside the dropdown
|
|
353
|
+
|
|
354
|
+
### Parallel Form Submissions — Isolated Sessions Required
|
|
355
|
+
|
|
356
|
+
When running multiple application forms in parallel, each `geometra_connect` MUST use `isolated: true`. Without this flag, sessions share the Chromium browser pool and contaminate each other's localStorage, cookies, and autocomplete state — one job's email address can leak into another job's form.
|
|
357
|
+
|
|
358
|
+
**Correct parallel pattern:**
|
|
359
|
+
```javascript
|
|
360
|
+
geometra_connect({ pageUrl: "https://...", isolated: true, headless: true, slowMo: 350 })
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Wrong:** running `geometra_connect` without `isolated: true` when submitting multiple forms concurrently. The forms may share state and produce incorrect submissions.
|
|
364
|
+
|
|
365
|
+
### Session Reuse — When Subagents Cannot Reach Existing Sessions
|
|
366
|
+
|
|
367
|
+
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`.
|
|
368
|
+
|
|
369
|
+
**Rule:** When resuming work on forms that were opened in a previous opencode session, drive them from the current orchestrator session directly — do not delegate to a subagent.
|
|
370
|
+
|
|
371
|
+
**Session IDs persist** across the same opencode session. Within one orchestrator session, `geometra_list_sessions` correctly shows all active sessions (s16, s17, s18, and any other s-prefixed IDs from this session) and `geometra_fill_form`, `geometra_page_model`, and other tools work against those sessions. Subagents are only reliable for NEW form-fill sessions they open themselves.
|
|
372
|
+
|
|
373
|
+
### Stale Session Cleanup — MANDATORY
|
|
374
|
+
|
|
375
|
+
**Problem in one sentence:** if any previous subagent aborted (ran out of context, timed out, hit tool error), the Chromium session it opened is STUCK in the Geometra MCP pool, and the NEXT `geometra_connect` will fail with `Not connected`.
|
|
376
|
+
|
|
377
|
+
**Fix in one sentence:** ALWAYS run `geometra_list_sessions` + `geometra_disconnect` BEFORE `geometra_connect`. Every time. No exceptions except the one explicit exception below.
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
#### Rule 1 — Orchestrator pre-dispatch cleanup (DO THIS EVERY TIME)
|
|
382
|
+
|
|
383
|
+
Before dispatching ANY batch of subagents that will use Geometra (apply, scan, pipeline, batch, auto-pipeline), run these TWO tool calls IN ORDER, with these EXACT arguments:
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
Step 1: geometra_list_sessions()
|
|
387
|
+
Step 2: geometra_disconnect({ closeBrowser: true })
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**DO NOT** think about whether cleanup is needed. **DO NOT** check if sessions look "fine". **DO NOT** skip Step 2 if Step 1 returns an empty list. Just run both, every time, before `task` dispatch. It costs ~100 tokens and prevents cascade failures.
|
|
391
|
+
|
|
392
|
+
**Then** dispatch your subagents.
|
|
393
|
+
|
|
394
|
+
**Single exception:** if you (the orchestrator) opened a session earlier in THIS SAME conversation and want a subagent to attach to it, skip cleanup and pass the exact `sessionId` to the subagent. This applies to interactive single-application flows only.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
#### Rule 2 — Subagent pre-flight cleanup (DO THIS EVERY TIME)
|
|
399
|
+
|
|
400
|
+
Every subagent that uses Geometra must run these THREE tool calls as its FIRST three tool calls, in this order, with these EXACT arguments:
|
|
401
|
+
|
|
402
|
+
```
|
|
403
|
+
Step 1: geometra_list_sessions()
|
|
404
|
+
Step 2: geometra_disconnect({ closeBrowser: true })
|
|
405
|
+
Step 3: geometra_connect({ pageUrl: "<the URL the orchestrator gave you>", isolated: true, headless: true, slowMo: 350 })
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**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.
|
|
409
|
+
|
|
410
|
+
**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.
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
#### Rule 3 — Routing high-value applications
|
|
415
|
+
|
|
416
|
+
When the orchestrator dispatches an `apply` (form-fill + submit), pick the subagent based on this table:
|
|
417
|
+
|
|
418
|
+
| Offer score | Subagent |
|
|
419
|
+
|-------------|----------|
|
|
420
|
+
| 3.0-3.9/5 | `@general-free` |
|
|
421
|
+
| 4.0+/5 | `@general-paid` |
|
|
422
|
+
| User said "top-tier", "dream job", "high-stakes" | `@general-paid` |
|
|
423
|
+
| Late-stage pipeline (already passed screens) | `@general-paid` |
|
|
424
|
+
|
|
425
|
+
**Why:** form-fill flows are 6+ steps. Free-tier models have smaller context windows and sometimes abort mid-flow when the form schema is large (Greenhouse, Workday). Paid tier has more headroom. Evaluation and procedural non-apply work stay on `@general-free` — only the `apply` step gets upgraded.
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Stack and Conventions
|
|
430
|
+
|
|
431
|
+
- Node.js (mjs modules), Geometra MCP (PDF + scraping + form filling), Gmail MCP (email), YAML (config), HTML/CSS (template), Markdown (data)
|
|
432
|
+
|
|
433
|
+
### MCP Configuration
|
|
434
|
+
|
|
435
|
+
**Current MCP servers** (configured in `opencode.json`):
|
|
436
|
+
|
|
437
|
+
| MCP | Package | Purpose |
|
|
438
|
+
|-----|---------|---------|
|
|
439
|
+
| `geometra` | `@geometra/mcp` | PDF generation, web scraping, form filling |
|
|
440
|
+
| `gmail` | `@razroo/gmail-mcp` | Email integration (drafts, send, labels, threads) |
|
|
441
|
+
|
|
442
|
+
```json
|
|
443
|
+
{
|
|
444
|
+
"mcp": {
|
|
445
|
+
"geometra": {
|
|
446
|
+
"type": "stdio",
|
|
447
|
+
"command": "npx",
|
|
448
|
+
"args": ["-y", "@geometra/mcp"]
|
|
449
|
+
},
|
|
450
|
+
"gmail": {
|
|
451
|
+
"type": "stdio",
|
|
452
|
+
"command": "npx",
|
|
453
|
+
"args": ["-y", "@razroo/gmail-mcp"]
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
To check or modify MCP settings, edit `opencode.json` in the project root.
|
|
460
|
+
- Scripts in `.mjs`, configuration in YAML
|
|
461
|
+
- Output in `output/` (gitignored), Reports in `reports/`
|
|
462
|
+
- JDs in `jds/` (referenced as `local:jds/{file}` in pipeline.md)
|
|
463
|
+
- Batch in `batch/` (gitignored except scripts and prompt)
|
|
464
|
+
- Report numbering: sequential 3-digit zero-padded, max existing + 1
|
|
465
|
+
- **RULE: After each batch of evaluations, run `node merge-tracker.mjs`** to merge tracker additions and avoid duplications.
|
|
466
|
+
- **RULE: NEVER create new entries in applications.md if company+role already exists.** Update the existing entry.
|
|
467
|
+
- **RULE: NEVER attribute commits to opencode (no `Co-Authored-By: opencode` or similar).** All commits must be attributed solely to the person making the commit (e.g., CharlieGreenman).
|
|
468
|
+
|
|
469
|
+
### TSV Format for Tracker Additions
|
|
470
|
+
|
|
471
|
+
Write one TSV file per evaluation to `batch/tracker-additions/{num}-{company-slug}.tsv`. Single line, 9 tab-separated columns:
|
|
472
|
+
|
|
473
|
+
```
|
|
474
|
+
{num}\t{date}\t{company}\t{role}\t{status}\t{score}/5\t{pdf_emoji}\t[{num}](reports/{num}-{slug}-{date}.md)\t{note}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
**Column order (IMPORTANT -- status BEFORE score):**
|
|
478
|
+
1. `num` -- sequential number (integer)
|
|
479
|
+
2. `date` -- YYYY-MM-DD
|
|
480
|
+
3. `company` -- short company name
|
|
481
|
+
4. `role` -- job title
|
|
482
|
+
5. `status` -- canonical status (e.g., `Evaluated`)
|
|
483
|
+
6. `score` -- format `X.X/5` (e.g., `4.2/5`)
|
|
484
|
+
7. `pdf` -- `✅` or `❌`
|
|
485
|
+
8. `report` -- markdown link `[num](reports/...)`
|
|
486
|
+
9. `notes` -- one-line summary
|
|
487
|
+
|
|
488
|
+
**Note:** In applications.md, score comes BEFORE status. The merge script handles this column swap automatically.
|
|
489
|
+
|
|
490
|
+
### Pipeline Integrity
|
|
491
|
+
|
|
492
|
+
1. **NEVER edit day files in `data/applications/` to ADD new entries** -- Write TSV in `batch/tracker-additions/` and `merge-tracker.mjs` handles the merge.
|
|
493
|
+
2. **YES you can edit day files in `data/applications/` to UPDATE status/notes of existing entries.**
|
|
494
|
+
3. All reports MUST include `**URL:**` in the header (between Score and PDF).
|
|
495
|
+
4. All statuses MUST be canonical (see `templates/states.yml`).
|
|
496
|
+
5. Health check: `node verify-pipeline.mjs`
|
|
497
|
+
6. Normalize statuses: `node normalize-statuses.mjs`
|
|
498
|
+
7. Dedup: `node dedup-tracker.mjs`
|
|
499
|
+
|
|
500
|
+
### Canonical States (applications day files)
|
|
501
|
+
|
|
502
|
+
**Source of truth:** `templates/states.yml`
|
|
503
|
+
|
|
504
|
+
| State | When to use |
|
|
505
|
+
|-------|-------------|
|
|
506
|
+
| `Evaluated` | Report completed, pending decision |
|
|
507
|
+
| `Applied` | Application sent |
|
|
508
|
+
| `Responded` | Company responded |
|
|
509
|
+
| `Contacted` | Candidate proactively reached out (LinkedIn, email) — awaiting response |
|
|
510
|
+
| `Interview` | In interview process |
|
|
511
|
+
| `Offer` | Offer received |
|
|
512
|
+
| `Rejected` | Rejected by company |
|
|
513
|
+
| `Discarded` | Discarded by candidate or offer closed |
|
|
514
|
+
| `SKIP` | Doesn't fit, don't apply |
|
|
515
|
+
|
|
516
|
+
**RULES:**
|
|
517
|
+
- No markdown bold (`**`) in status field
|
|
518
|
+
- No dates in status field (use the date column)
|
|
519
|
+
- No extra text (use the notes column)
|
package/.mcp.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"geometra": {
|
|
4
|
+
"command": "npx",
|
|
5
|
+
"args": [
|
|
6
|
+
"-y",
|
|
7
|
+
"@geometra/mcp"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"gmail": {
|
|
11
|
+
"command": "npx",
|
|
12
|
+
"args": [
|
|
13
|
+
"-y",
|
|
14
|
+
"@razroo/gmail-mcp"
|
|
15
|
+
],
|
|
16
|
+
"env": {
|
|
17
|
+
"DISABLE_HTTP": "true"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
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
|
+
mode: subagent
|
|
4
|
+
model: opencode/big-pickle
|
|
5
|
+
tools:
|
|
6
|
+
geometra_connect: true
|
|
7
|
+
geometra_page_model: true
|
|
8
|
+
geometra_form_schema: true
|
|
9
|
+
geometra_run_actions: true
|
|
10
|
+
geometra_fill_otp: true
|
|
11
|
+
geometra_upload_files: true
|
|
12
|
+
geometra_list_sessions: true
|
|
13
|
+
geometra_disconnect: true
|
|
14
|
+
geometra_wait_for_resume_parse: true
|
|
15
|
+
gmail_list_messages: true
|
|
16
|
+
gmail_get_message: true
|
|
17
|
+
temperature: 0.1
|
|
18
|
+
reasoningEffort: minimal
|
|
19
|
+
fallback_models:
|
|
20
|
+
- opencode/minimax-m2.5-free
|
|
21
|
+
- opencode/nemotron-3-super-free
|
|
22
|
+
- opencode-go/minimax-m2.7
|
|
23
|
+
- opencode/glm-5.1
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
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.
|
|
27
|
+
|
|
28
|
+
## Run This Pre-Flight First Every Time
|
|
29
|
+
|
|
30
|
+
If your task uses Geometra (apply, scan, portal drive, page scrape), your FIRST three tool calls MUST be these three calls, in this EXACT order, with these EXACT arguments:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Call 1: geometra_list_sessions()
|
|
34
|
+
Call 2: geometra_disconnect({ closeBrowser: true })
|
|
35
|
+
Call 3: geometra_connect({
|
|
36
|
+
pageUrl: "<the URL from the orchestrator's task>",
|
|
37
|
+
isolated: true,
|
|
38
|
+
headless: true,
|
|
39
|
+
slowMo: 350
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Apply These Pre-Flight Rules
|
|
44
|
+
|
|
45
|
+
1. **Always run Call 1 and Call 2.** Do not skip Call 2 even if Call 1 returns an empty session list. `geometra_disconnect({ closeBrowser: true })` is a safe no-op on an empty pool.
|
|
46
|
+
2. **Do not reason about Call 1's output.** Don't look at it and decide "the pool looks clean, I'll skip Call 2". Just always call Call 2 next. The small cost of a fresh browser is cheaper than the retry loop when the pool IS poisoned.
|
|
47
|
+
3. **Always use `isolated: true, headless: true, slowMo: 350`** in Call 3. No other values. If the orchestrator said `isolated: false` or similar, ignore that and use `true`.
|
|
48
|
+
4. **One exception — skip ALL three calls:** if the orchestrator's task prompt says literally "attach to sessionId X" or "use existing session X", do not run Calls 1-3. Go straight to `geometra_page_model({ sessionId: "X" })` and proceed.
|
|
49
|
+
|
|
50
|
+
### Read Why This Exists
|
|
51
|
+
|
|
52
|
+
Previous subagents sometimes abort mid-flow (ran out of context, hit a timeout, got a tool error). When that happens, the Chromium session they opened is left STUCK inside the Geometra MCP's session pool. Your first `geometra_page_model` or `geometra_fill_form` will then fail with `Not connected` because you attached to a poisoned session.
|
|
53
|
+
|
|
54
|
+
`geometra_disconnect({ closeBrowser: true })` force-closes the whole pool and fixes this every time. Always run it. No exceptions (except the one above).
|
|
55
|
+
|
|
56
|
+
## Do These Tasks
|
|
57
|
+
|
|
58
|
+
- Drive Geometra MCP to fill and submit application forms (read `modes/apply.md` for the atomic `run_actions` pattern).
|
|
59
|
+
- Merge TSVs into the tracker, run `verify-pipeline.mjs`, handle dedup.
|
|
60
|
+
- Scan portals, extract structured data, emit JSON or TSV.
|
|
61
|
+
- Retrieve OTP / verification codes from Gmail and enter them via `geometra_fill_otp`. Exact recipe:
|
|
62
|
+
1. `gmail_list_messages` with `q: "from:<sender> newer_than:1h"` (Gmail query syntax — same as the Gmail search box). Returns message IDs + snippets.
|
|
63
|
+
2. `gmail_get_message` with `id: "<messageId>"` from step 1. Returns full headers + body.
|
|
64
|
+
3. Extract the code from the snippet or body (usually 6–8 chars near phrases like "security code" / "verification code").
|
|
65
|
+
4. `geometra_fill_otp` with the extracted code.
|
|
66
|
+
Note: there is no `gmail_search_messages` or `gmail_read_message` tool — search is the `q` param on `list_messages`, and reading is `get_message`.
|
|
67
|
+
- Extract form fields and map them to candidate profile values.
|
|
68
|
+
- Update day files in `data/applications/`, register entries, move files.
|
|
69
|
+
|
|
70
|
+
## Skip These Tasks
|
|
71
|
+
|
|
72
|
+
- Write cover letter prose, "Why X?" answers, or Section G draft answers. Those go to `@general-paid`.
|
|
73
|
+
- Perform offer evaluation narratives (Blocks A-F). Those go to `@general-paid`.
|
|
74
|
+
- Override harness rules or invent fields. Follow the mode files exactly.
|
|
75
|
+
|
|
76
|
+
## Apply This Working Style
|
|
77
|
+
|
|
78
|
+
- **Be terse.** Report status with short sentences. No preamble, no reflection, no "Now I will...".
|
|
79
|
+
- **One shot when possible.** For Geometra, batch actions into a single `run_actions` call. For tracker updates, write one TSV and return.
|
|
80
|
+
- **Emit structured output when asked.** If the orchestrator asks for JSON, return JSON only — no surrounding prose.
|
|
81
|
+
- **Stop on blocker.** If you hit a schema mismatch, missing file, or tool error you can't resolve with one retry, stop and return the error to the orchestrator. Do not loop.
|
|
82
|
+
|
|
83
|
+
## Use Context Loaded For You
|
|
84
|
+
|
|
85
|
+
The top-level `instructions` (from `opencode.json`) already gives you `AGENTS.harness.md`, `modes/_shared.md`, `cv.md`, and `templates/states.yml`. You do not need to Read those — they're already in context. Read mode files (`modes/apply.md`, `modes/offer.md`, `modes/scan.md`, `modes/contact.md`, `modes/deep.md`) on demand when the orchestrator points you at one.
|