job-forge 2.0.1 → 2.0.2
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/.cursor/rules/main.mdc +10 -1
- package/AGENTS.md +10 -1
- package/CLAUDE.md +10 -1
- package/iso/instructions.md +10 -1
- package/modes/auto-pipeline.md +6 -3
- package/modes/pipeline.md +4 -3
- package/modes/scan.md +50 -7
- package/package.json +1 -1
package/.cursor/rules/main.mdc
CHANGED
|
@@ -15,8 +15,17 @@ The Hard Limits below are non-negotiable numeric rules. If you catch yourself ab
|
|
|
15
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
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
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
|
+
7. **URLs passed to downstream subagents must come from a file, not from a prior subagent's prose.** When an orchestrator dispatches a subagent with a URL (for evaluation, apply, verification, etc.), the URL MUST originate from:
|
|
19
|
+
- `data/pipeline.md`
|
|
20
|
+
- `data/scan-history.tsv`
|
|
21
|
+
- `batch/scan-output-*.md` or similar structured output file
|
|
22
|
+
- A report file (`reports/{num}-*.md`) with an authoritative `**URL:**` header
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
URLs mentioned in a subagent's return message are NOT trustworthy by default — they may be hallucinated or reconstructed. Before passing any URL from a subagent report to another subagent, cross-check it exists in one of the authoritative files above, OR instruct the dispatching subagent to write its output to a structured file and re-read from that file.
|
|
25
|
+
|
|
26
|
+
**Why**: a scan subagent once reported 30 plausible-looking Greenhouse IDs in its return message that did not exist in the Greenhouse API. The orchestrator dispatched 30 downstream subagents that all failed verification. Trusting prose-form URLs cost ~2 hours of wasted work and corrupted the tracker.
|
|
27
|
+
|
|
28
|
+
Everything below is context and rationale. These seven numbers are the rules.
|
|
20
29
|
|
|
21
30
|
---
|
|
22
31
|
|
package/AGENTS.md
CHANGED
|
@@ -10,8 +10,17 @@ The Hard Limits below are non-negotiable numeric rules. If you catch yourself ab
|
|
|
10
10
|
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.
|
|
11
11
|
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.
|
|
12
12
|
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.
|
|
13
|
+
7. **URLs passed to downstream subagents must come from a file, not from a prior subagent's prose.** When an orchestrator dispatches a subagent with a URL (for evaluation, apply, verification, etc.), the URL MUST originate from:
|
|
14
|
+
- `data/pipeline.md`
|
|
15
|
+
- `data/scan-history.tsv`
|
|
16
|
+
- `batch/scan-output-*.md` or similar structured output file
|
|
17
|
+
- A report file (`reports/{num}-*.md`) with an authoritative `**URL:**` header
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
URLs mentioned in a subagent's return message are NOT trustworthy by default — they may be hallucinated or reconstructed. Before passing any URL from a subagent report to another subagent, cross-check it exists in one of the authoritative files above, OR instruct the dispatching subagent to write its output to a structured file and re-read from that file.
|
|
20
|
+
|
|
21
|
+
**Why**: a scan subagent once reported 30 plausible-looking Greenhouse IDs in its return message that did not exist in the Greenhouse API. The orchestrator dispatched 30 downstream subagents that all failed verification. Trusting prose-form URLs cost ~2 hours of wasted work and corrupted the tracker.
|
|
22
|
+
|
|
23
|
+
Everything below is context and rationale. These seven numbers are the rules.
|
|
15
24
|
|
|
16
25
|
---
|
|
17
26
|
|
package/CLAUDE.md
CHANGED
|
@@ -10,8 +10,17 @@ The Hard Limits below are non-negotiable numeric rules. If you catch yourself ab
|
|
|
10
10
|
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.
|
|
11
11
|
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.
|
|
12
12
|
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.
|
|
13
|
+
7. **URLs passed to downstream subagents must come from a file, not from a prior subagent's prose.** When an orchestrator dispatches a subagent with a URL (for evaluation, apply, verification, etc.), the URL MUST originate from:
|
|
14
|
+
- `data/pipeline.md`
|
|
15
|
+
- `data/scan-history.tsv`
|
|
16
|
+
- `batch/scan-output-*.md` or similar structured output file
|
|
17
|
+
- A report file (`reports/{num}-*.md`) with an authoritative `**URL:**` header
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
URLs mentioned in a subagent's return message are NOT trustworthy by default — they may be hallucinated or reconstructed. Before passing any URL from a subagent report to another subagent, cross-check it exists in one of the authoritative files above, OR instruct the dispatching subagent to write its output to a structured file and re-read from that file.
|
|
20
|
+
|
|
21
|
+
**Why**: a scan subagent once reported 30 plausible-looking Greenhouse IDs in its return message that did not exist in the Greenhouse API. The orchestrator dispatched 30 downstream subagents that all failed verification. Trusting prose-form URLs cost ~2 hours of wasted work and corrupted the tracker.
|
|
22
|
+
|
|
23
|
+
Everything below is context and rationale. These seven numbers are the rules.
|
|
15
24
|
|
|
16
25
|
---
|
|
17
26
|
|
package/iso/instructions.md
CHANGED
|
@@ -10,8 +10,17 @@ The Hard Limits below are non-negotiable numeric rules. If you catch yourself ab
|
|
|
10
10
|
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.
|
|
11
11
|
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.
|
|
12
12
|
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.
|
|
13
|
+
7. **URLs passed to downstream subagents must come from a file, not from a prior subagent's prose.** When an orchestrator dispatches a subagent with a URL (for evaluation, apply, verification, etc.), the URL MUST originate from:
|
|
14
|
+
- `data/pipeline.md`
|
|
15
|
+
- `data/scan-history.tsv`
|
|
16
|
+
- `batch/scan-output-*.md` or similar structured output file
|
|
17
|
+
- A report file (`reports/{num}-*.md`) with an authoritative `**URL:**` header
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
URLs mentioned in a subagent's return message are NOT trustworthy by default — they may be hallucinated or reconstructed. Before passing any URL from a subagent report to another subagent, cross-check it exists in one of the authoritative files above, OR instruct the dispatching subagent to write its output to a structured file and re-read from that file.
|
|
20
|
+
|
|
21
|
+
**Why**: a scan subagent once reported 30 plausible-looking Greenhouse IDs in its return message that did not exist in the Greenhouse API. The orchestrator dispatched 30 downstream subagents that all failed verification. Trusting prose-form URLs cost ~2 hours of wasted work and corrupted the tracker.
|
|
22
|
+
|
|
23
|
+
Everything below is context and rationale. These seven numbers are the rules.
|
|
15
24
|
|
|
16
25
|
---
|
|
17
26
|
|
package/modes/auto-pipeline.md
CHANGED
|
@@ -8,9 +8,12 @@ Fetch the JD content once. If the input is a **URL** (not pasted JD text), fetch
|
|
|
8
8
|
|
|
9
9
|
**Pick exactly one method, in this priority order:**
|
|
10
10
|
|
|
11
|
-
1. **
|
|
12
|
-
2. **
|
|
13
|
-
3. **
|
|
11
|
+
1. **Greenhouse JSON API (first try, if the URL is Greenhouse-backed):** If the pipeline.md entry carries `| gh={slug}/{id}` OR the URL host matches `*.greenhouse.io` / a known Greenhouse customer front-end (`*.pinterestcareers.com`, `okta.com/company/careers/opportunity/*`, `samsara.com/company/careers/roles/*`, `zoominfo.com/careers?gh_jid=*`, `collibra.com/.../?gh_jid=*`, `careers.toasttab.com/jobs?gh_jid=*`, `careers.airbnb.com/positions/*?gh_jid=*`, `coinbase.com/careers/positions/*?gh_jid=*`, `instacart.careers/job/?gh_jid=*`), extract `slug` and `id` and WebFetch `https://boards-api.greenhouse.io/v1/boards/{slug}/jobs/{id}`. 200 + JSON with `content` is the authoritative JD. 404 = genuinely closed (mark CLOSED and stop). **If 200, STOP — do not fall back to Geometra or WebFetch of the front-end.** The API is faster, cheaper (no Geometra session), and never returns a bot-shell.
|
|
12
|
+
2. **Geometra MCP:** Most non-Greenhouse job portals (Lever, Ashby, Workday) are SPAs. Use `geometra_connect` + `geometra_page_model` to render and read the JD. **If this returns non-empty JD text, STOP — do not WebFetch the same URL.**
|
|
13
|
+
3. **WebFetch (only if Geometra is unavailable OR returned only a shell with no JD text):** For static pages (ZipRecruiter, WeLoveProduct, company career pages).
|
|
14
|
+
4. **WebSearch (only if methods 1–3 all failed):** Search for the role title + company on secondary portals that index the JD in static HTML.
|
|
15
|
+
|
|
16
|
+
**Do NOT mark a Greenhouse-sourced offer CLOSED based on a WebFetch shell or a 403 from a customer-skinned careers domain.** Pinterest, Okta, Samsara, ZoomInfo, Collibra, Toast, Airbnb, Coinbase, Instacart all serve bot-hostile fronts. The Greenhouse JSON API (step 1) is the ground truth for their offer state. A previous scan run fed 60 live Greenhouse URLs through WebFetch-only verification and 100% of them were wrongly marked CLOSED; if you see a high stale rate, you are skipping step 1.
|
|
14
17
|
|
|
15
18
|
**Rule:** Each URL gets fetched at most once per session. If you already have the JD text in context — from Geometra, a previous WebFetch, or pasted by the candidate — do not fetch again.
|
|
16
19
|
|
package/modes/pipeline.md
CHANGED
|
@@ -33,9 +33,10 @@ Processes accumulated job offer URLs from `data/pipeline.md`. The user adds URLs
|
|
|
33
33
|
|
|
34
34
|
## Detect JD From URL
|
|
35
35
|
|
|
36
|
-
1. **
|
|
37
|
-
2. **
|
|
38
|
-
3. **
|
|
36
|
+
1. **Greenhouse JSON API (FIRST, when the entry has `| gh={slug}/{id}` OR the host looks Greenhouse-backed):** WebFetch `https://boards-api.greenhouse.io/v1/boards/{slug}/jobs/{id}`. 200 + JSON with `content` = LIVE, use it as the JD; 404 = genuinely CLOSED (mark `- [!]` and continue). Bot-hostile customer fronts (`pinterestcareers.com`, `okta.com`, `samsara.com`, `zoominfo.com`, `collibra.com`, `careers.toasttab.com`, `careers.airbnb.com`, `coinbase.com`, `instacart.careers`, `careers.toasttab.com`) MUST be verified via this API first — WebFetch/Geometra of those domains returns a shell or 403 and causes false CLOSED marks.
|
|
37
|
+
2. **Geometra MCP:** `geometra_connect` + `geometra_page_model`. Works with non-Greenhouse SPAs (Lever, Ashby, Workday), uses fewer tokens than raw DOM snapshots.
|
|
38
|
+
3. **WebFetch (fallback):** For static pages or when Geometra is not available.
|
|
39
|
+
4. **WebSearch (last resort):** Search on secondary portals that index the JD.
|
|
39
40
|
|
|
40
41
|
**Special cases:**
|
|
41
42
|
- **LinkedIn**: May require login → mark `[!]` and ask the user to paste the text
|
package/modes/scan.md
CHANGED
|
@@ -68,8 +68,12 @@ The levels are additive — all are executed, results are merged and deduplicate
|
|
|
68
68
|
5. **Level 2 — Greenhouse APIs** (WebFetch can batch freely — it's cheap and doesn't use Geometra sessions):
|
|
69
69
|
For each company in `tracked_companies` with `api:` defined and `enabled: true`:
|
|
70
70
|
a. WebFetch the API URL → JSON with job list
|
|
71
|
-
b. For each job extract: `{title, url, company}`
|
|
72
|
-
|
|
71
|
+
b. For each job extract: `{title, url, company, gh_slug, gh_id, updated_at}`
|
|
72
|
+
- **`url`**: ALWAYS record the canonical Greenhouse URL: `https://job-boards.greenhouse.io/{gh_slug}/jobs/{gh_id}`. Do **NOT** use `absolute_url` when it points to a customer-skinned front-end (e.g. `pinterestcareers.com/jobs/?gh_jid=N`, `okta.com/company/careers/opportunity/N`, `samsara.com/company/careers/roles/N`, `zoominfo.com/careers?gh_jid=N`, `collibra.com/.../?gh_jid=N`, `careers.toasttab.com/jobs?gh_jid=N`, `careers.airbnb.com/positions/N`, `coinbase.com/careers/positions/N`, `instacart.careers/job/?gh_jid=N`, `pinterestcareers.com/jobs/?gh_jid=N`). These customer front-ends return shells or 403 to bots and cause downstream WebFetch-based verification to wrongly mark the role CLOSED.
|
|
73
|
+
- **`gh_slug`**: the Greenhouse board slug (from the API URL that was fetched).
|
|
74
|
+
- **`gh_id`**: `jobs[].id` from the API response.
|
|
75
|
+
- **`updated_at`**: `jobs[].updated_at` — record for staleness detection (skip if older than 90 days, flag if older than 30).
|
|
76
|
+
c. Accumulate in candidates list (dedup with Level 1). The pipeline.md entry MUST carry `| gh={gh_slug}/{gh_id}` at the end of the metadata so downstream evaluators can fall back to `https://boards-api.greenhouse.io/v1/boards/{gh_slug}/jobs/{gh_id}` when the canonical URL renders as a shell.
|
|
73
77
|
|
|
74
78
|
6. **Level 3 — WebSearch queries** (WebSearch is parallel-safe; batch freely):
|
|
75
79
|
For each query in `search_queries` with `enabled: true`:
|
|
@@ -102,7 +106,7 @@ The levels are additive — all are executed, results are merged and deduplicate
|
|
|
102
106
|
- When a fuzzy match is found but the URL is new, log it as `skipped_repost` (not `skipped_dup`) with a note referencing the original entry number.
|
|
103
107
|
|
|
104
108
|
8. **For each new offer that passes filters**:
|
|
105
|
-
a. Add to `pipeline.md` section "Pending": `- [ ] {url} | {company} | {title}`
|
|
109
|
+
a. Add to `pipeline.md` section "Pending": `- [ ] {url} | {company} | {title}` — append `| gh={gh_slug}/{gh_id}` when the offer came from the Greenhouse API (Level 2) so downstream verification can hit the JSON endpoint.
|
|
106
110
|
b. Record in `scan-history.tsv`: `{url}\t{date}\t{query_name}\t{title}\t{company}\tadded`
|
|
107
111
|
|
|
108
112
|
9. **Offers filtered by title**: record in `scan-history.tsv` with status `skipped_title`
|
|
@@ -137,6 +141,30 @@ https://... 2026-02-10 Greenhouse — SA Junior Dev BigCo skipped_title
|
|
|
137
141
|
https://... 2026-02-10 Ashby — AI PM SA AI OldCo skipped_dup
|
|
138
142
|
```
|
|
139
143
|
|
|
144
|
+
## Structured Output — Required for Downstream Dispatch
|
|
145
|
+
|
|
146
|
+
Scan mode MUST write its ranked candidate list to a file, not just return it in prose. Downstream subagents (evaluators, applyers) must read URLs from this file, not from the scan subagent's return message. This prevents any hallucinated URL or ID from propagating.
|
|
147
|
+
|
|
148
|
+
**File location**: `batch/scan-output-{YYYY-MM-DD}.md`
|
|
149
|
+
|
|
150
|
+
**Format**: one markdown table per scan run, ordered by archetype-fit rank:
|
|
151
|
+
|
|
152
|
+
| rank | company | role | gh_slug | gh_id | url | updated_at |
|
|
153
|
+
|------|---------|------|---------|-------|-----|------------|
|
|
154
|
+
| 1 | Webflow | Lead AI Engineer | webflow | 7689676 | https://job-boards.greenhouse.io/webflow/jobs/7689676 | 2026-04-14 |
|
|
155
|
+
| ... | ... | ... | ... | ... | ... | ... |
|
|
156
|
+
|
|
157
|
+
Every row MUST have:
|
|
158
|
+
- `gh_slug` and `gh_id` copied verbatim from the Greenhouse API response (not reconstructed)
|
|
159
|
+
- `url` in the canonical form `https://job-boards.greenhouse.io/{gh_slug}/jobs/{gh_id}` (matching the suffix in `data/pipeline.md`)
|
|
160
|
+
- `updated_at` in `YYYY-MM-DD` form (the most recent `updated_at` in the API response)
|
|
161
|
+
|
|
162
|
+
The scan subagent's return message MUST:
|
|
163
|
+
- Reference the file path (so orchestrators know where to read)
|
|
164
|
+
- Omit the ranked URL list from prose entirely (summary counts only)
|
|
165
|
+
|
|
166
|
+
**Rationale**: in a prior run, a scan subagent returned correct IDs in `scan-history.tsv` but hallucinated plausible-looking fake IDs in its prose-form top-30 list. The orchestrator trusted prose and dispatched 30 downstream subagents against fake URLs. File-based handoff prevents this class of error.
|
|
167
|
+
|
|
140
168
|
## Output Summary
|
|
141
169
|
|
|
142
170
|
```
|
|
@@ -148,12 +176,27 @@ Filtered by title: N relevant
|
|
|
148
176
|
Duplicates: N (already evaluated or in pipeline)
|
|
149
177
|
New added to pipeline.md: N
|
|
150
178
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
179
|
+
NEXT STEP RECOMMENDATION:
|
|
180
|
+
- Structured candidate list written to: batch/scan-output-{YYYY-MM-DD}.md
|
|
181
|
+
- Downstream subagents MUST read URLs from that file, not from this return message
|
|
182
|
+
- Run /job-forge pipeline to evaluate the new offers.
|
|
155
183
|
```
|
|
156
184
|
|
|
185
|
+
## Verify Before Marking CLOSED (downstream rule)
|
|
186
|
+
|
|
187
|
+
**DO NOT mark a Greenhouse offer CLOSED based on a WebFetch/Geometra result alone.** Customer-skinned careers pages (`pinterestcareers.com`, `okta.com`, `samsara.com`, `zoominfo.com`, `collibra.com`, `careers.toasttab.com`, `careers.airbnb.com`, `coinbase.com`, `instacart.careers`, etc.) serve bot-hostile shells — a 403, a navbar-only response, or a client-side-only render. WebFetch sees "no JD" and mis-classifies as CLOSED.
|
|
188
|
+
|
|
189
|
+
**Correct verification order for any Greenhouse-sourced URL** (identified by a `| gh={slug}/{id}` suffix in `pipeline.md` or a `boards-api.greenhouse.io` / `job-boards.greenhouse.io` / `boards.greenhouse.io` host):
|
|
190
|
+
|
|
191
|
+
1. Try `https://boards-api.greenhouse.io/v1/boards/{slug}/jobs/{id}`. This is the authoritative source.
|
|
192
|
+
- **200 + JSON with `title` and `content`** → offer is LIVE. Use the JSON content as the JD. Do not mark CLOSED.
|
|
193
|
+
- **404** → offer is genuinely closed. Mark CLOSED.
|
|
194
|
+
- **Other non-2xx** → treat as transient (network/rate-limit); retry once. If still failing, mark `**Verification: unconfirmed**` and continue evaluation from whatever text is available. Do NOT mark CLOSED.
|
|
195
|
+
2. Only then fall back to WebFetch of the canonical `job-boards.greenhouse.io/{slug}/jobs/{id}` URL.
|
|
196
|
+
3. Only then fall back to Geometra on the same canonical URL.
|
|
197
|
+
|
|
198
|
+
**Rule of thumb:** Greenhouse postings with valid `gh_slug`/`gh_id` should be verified via the API first. A WebFetch failure on a customer-skinned domain is NOT evidence the role is closed.
|
|
199
|
+
|
|
157
200
|
## Update careers_url
|
|
158
201
|
|
|
159
202
|
Each company in `tracked_companies` MUST have a `careers_url` — the direct URL to its job listings page. The stored URL avoids searching for it every time.
|