--- name: triage-jobs description: Triage one or more job vacancies using Oleg's canonical apply/skip playbook (vacancy-filter-and-triage-2026.md). Applies 4 hard gates, classifies the CV track, runs the research protocol, checks for prior applications, and outputs a consolidated verdict. For batch runs from telegram_inbox.json, stratifies by priority and delegates p2/p3 bulk to a Haiku subagent to protect the main session. --- # triage-jobs Operational implementation of `base/reference/vacancy-filter-and-triage-2026.md`. That file is the authoritative playbook — read it before running this skill if you don't have it in context. Posture: **trying-friendly, not filtering-friendly.** Hard skip is rare and requires a concrete objective blocker. On every ambiguous case, default to "подаём". --- ## When to use - Oleg provides a job posting URL, raw JD text, or says "разбери вакансию". - After running `scripts/fetch_telegram_jobs.py` — triage `tracking/telegram_inbox.json`. - Any "should we apply?" question about a specific role. --- ## Input modes | Mode | What Oleg provides | |---|---| | **Single URL** | One job posting link — fetch, triage, output verdict | | **Raw JD** | Pasted text — triage directly | | **Batch (inbox)** | No specific job — triage all entries in `tracking/telegram_inbox.json` | --- ## Step 0 — Read the playbook (if needed) If `vacancy-filter-and-triage-2026.md` is not already in context: ``` Read: base/reference/vacancy-filter-and-triage-2026.md ``` --- ## Step 1 — Fetch the JD For URL input: 1. Try `WebFetch` first. 2. If blocked (403, bot wall, login wall, JS-only, empty body) → switch to `chrome-session` skill immediately, no confirmation needed. 3. If neither works → halt and report. **Never reconstruct a JD from memory.** For Greenhouse / Lever / Ashby / Freshteam ATS: SSR usually works; confirm the "Apply" button is active (not a JS overlay saying "not accepting applications"). --- ## Step 2 — Extract fast facts (< 1 min) Pull from the JD: - Role title and seniority signals - Must-have stack (vs. nice-to-have) - AI involvement: **must-have** / nice-to-have / not mentioned - Location & remote policy - Timezone / overlap requirements (explicit or inferable from location) - Compensation signals (range, equity framing, "scrappy" language, funding stage) --- ## Step 3 — Run 4 hard gates Evaluate gates in order. First failure → `SKIP` with one objective reason. All four pass → continue. ### Gate 1 — Geography / hiring Passes if the role can hire Oleg via **any** of: - Global remote (non-US/UK/EU-restricted) - Contractor via Deel or equivalent EOR covering Thailand - B2B contract for remote work - Russian-speaking team / Russian-speaking founders, hiring remotely (excluding RU/BY companies explicitly requiring presence) - Russian companies hiring remotely (presence in RU not required) - Relocation offered **AND** comp ≥ $10k/month - Temporary contract fully geography-agnostic (Mira, Algery, similar aggregators) Skip if: location-locked / US-only / UK-only / EOR doesn't cover Thailand and no contractor option. ### Gate 2 — Timezone (comp-scaled, not binary) Oleg's flexible window: **9:00–22:00 GMT+7**. Comfortable day: 11:00–20:00. | Required overlap | Pass condition | |---|---| | Fits within 9:00–22:00 GMT+7 | Passes at any comp ≥ floor | | Partially outside the window | Passes if comp > $9k | | Full US-day / PST core hours / night work | Passes if comp > $10k | Skip only if: comp ≤ $9k **AND** required overlap falls outside 9:00–22:00 GMT+7. ### Gate 3 — Compensation | Tier | Range | Treatment | |---|---|---| | Target | $8–12k/month | Main funnel | | Floor | $6–8k/month | Normal lower bound | | Fallback | $4.5–6k/month | Temporary bridge only — штучно и осознанно | | Skip | < $4.5k/month | Hard skip | No explicit comp? Read signals: pre-seed + equity-heavy framing + "scrappy team" → likely below floor. Founding-engineer at Series A–B AI startup can stretch to $15k+ if profile matches. ### Gate 4 — Seniority Passes for: Senior / Staff / Tech Lead / experienced middle+. - **Skip** if: explicitly Junior or mid with no growth ceiling. - **Do NOT skip** if: role title is Lead / Principal / Head of — treat as "they don't have that person internally, try it." - **Do NOT skip** if: role looks like middle but comp fits floor/target AND the role offers interesting new experience (AI, client-facing, English immersion). --- ## Step 4 — Anti-pattern self-check Before marking skip, run through the false-blocker list. If you're about to skip on any of these, escalate to the next tier instead: 1. Role looks senior-heavy (Lead/Principal/Head) → that's a try signal. 2. One missing skill → ask Oleg first (see Step 6), not skip. 3. "Not an AI-first company" → Track B or Hybrid, not skip. 4. Superficial domain mismatch → look at actual technical tasks (orchestration, LLM workflows, structured extraction). 5. "Not a Russian-speaking scale-up" → neutral, not negative. 6. Timeline outside 8-week window → affects sequencing, not eligibility. 7. Comp below $8k but in floor/fallback → consider, especially strategic or interesting roles. 8. Tech stack mismatch with fast ramp → lean toward apply, mention ramp honestly. 9. Doesn't fully pass triage but ≥50% match + low application effort (LinkedIn Easy Apply, simple form, DM) → **quick-apply** mode: note it explicitly as "fast/training apply." --- ## Step 5 — Research protocol Run for all roles that cleared the gates (and for borderline cases to resolve ambiguity). ### 5.0 — Check for prior application Before deep research, check whether Oleg already applied to this role or company. **Trello is the source of truth** (cards may sit in any column depending on stage): - **Trello (primary)** — `mcp__trello__set_active_board(boardId="6a1a9a5af082cb0526b22704")`, then `get_cards_by_list_id` for the vacancy-bearing lists and match the company against card titles: - TODO `6a1aa59555aab72a261c42aa` · In Progress `6a1aa59a5e7e651b1352895b` · In touch `6a267e2e9ed02737346dc047` · Applyed `6a1aa5a1d8bcb0ed7234987b` · Rejected `6a245a328452976c0d1fcca1` - Include **Rejected** too — a previously-rejected company still counts as "already processed", don't re-surface it as new. - Extract **only** each card's `name` + `shortUrl` — don't carry the full card JSON forward (see the context note in the batch pre-step). - **Telegram** — search in job-tracking chats if applicable. - **Gmail** — search for company name if accessible. - `tracking/applications.md` is **frozen/legacy** — kept for backward compatibility only. Do **not** read or write it. If already applied → note it in the output, don't re-triage as new. ### 5.1 — Research the company What the company does, product, stage, funding, team size, recent news, reputation (Glassdoor / Blind if available). Goal: context + ghost/red-flag filter. ### 5.2 — Cross-platform posting check Find the same vacancy on: company careers page, LinkedIn, ATS link (Greenhouse/Lever/Ashby/etc.), job boards, aggregators. Consolidate: - **Posting date** — earliest appearance. 90+ days without update = ghost signal. - **Comp range** — different platforms often show different bands. Collect all. - **Geo restrictions** — any platform saying US Only / UK Only / location-locked / eligible countries list? ### 5.3 — Restriction priority rule **Restrictions are authoritative.** If one source says "remote" and another says "US only" → US only wins → Gate 1 skip. Less restrictive wording wins **only** if it explicitly and clearly overrides/removes the restriction. --- ## Step 6 — Missing skills One missing skill is **not** a skip if it isn't flagged as explicit MUST HAVE. Action: **ask Oleg before applying** — "JD asks for X, I don't see it in your materials — did you have any experience with it?" Many skills exist but were trimmed from CV (examples: Redux Toolkit/RTK Query, Vitest, Playwright, Jest, eCommerce/CMS work). Real gaps — can name without checking: Go, Rust, Java, C++, Ruby, PHP, heavy DevOps (K8s orchestration at scale, SRE), native mobile (Swift, Kotlin). React Native and Python are bridgeable, not gaps. **Never flag a gap explicitly in a cover letter** — don't write "one thing to flag" or "I notice a gap here." --- ## Step 7 — Classify track (for roles that cleared gates) | JD condition | Track | CV file | |---|---|---| | AI in **must-have**: LLM / agents / MCP / RAG / orchestration / embeddings in core requirements | **Track A** | `base/cv-2026-05-base.md` | | Pure Node.js / backend, AI as nice-to-have or "significant plus" | **Backend-first** | `base/cv-backend-variant-hostinger-2026-05.md` | | Senior / full-stack / frontend, no AI in must-have | **Track B** | `base/oleg_proskurin_fullstack_techlead_cv.md` | | Senior + AI-tooling fluency as requirement (build with AI assistance, not AI product) | **Hybrid** | CV-B with AI moved up 2–3 positions in Skills | Russian-speaking scale-ups (Manychat, inDrive, Wheely, Joom, Adapty, similar) → **Track B by default**, even if AI not mentioned. Tiebreakers: - Track B vs. skip → Track B wins. - Track A vs. Track B for hybrid JD → AI in must-have → A; otherwise → B. --- ## Step 8 — Output format One consolidated block per vacancy. No separate research section — merge everything. ``` ## [Company] — [Role Title] **Verdict:** APPLY (Track A) | SKIP | QUICK-APPLY **Gate check:** - Gate 1 (Geo): ✅ global remote / ❌ US-only - Gate 2 (TZ): ✅ EMEA overlap fits / ⚠️ partial, comp > $9k ok / ❌ - Gate 3 (Comp): ✅ $X–Yk (target) / ⚠️ floor / ❌ - Gate 4 (Level): ✅ Senior / ❌ Junior **Track:** A / B / Backend-first / Hybrid — CV: [filename] **Strong matches:** [2–4 bullets: specific tech/scope from JD that map directly to Oleg's background] **Open questions:** [missing skills to ask Oleg about; 1–2 items max] **Research notes:** - Company: [stage, product, funding, red flags if any] - Posting date: [earliest found] — [fresh / stale signal] - Comp across platforms: [consolidate bands found] - Geo/restrictions: [what each platform says; which is authoritative] **Prior application:** none found / ⚠️ already applied on [date], see Trello card [name] **Proof-points for letter:** [2–3 concrete bullets that directly answer the JD's key asks] ``` For SKIP: one-liner after gate check explaining the objective blocker. No research section needed unless it was needed to determine the gate outcome. --- ## Batch mode — stratified triage from telegram_inbox.json When triaging `tracking/telegram_inbox.json`: ### Pre-step: build prior-applications list (do this once before spawning any subagent) **Trello is the source of truth.** Build `known_applied` from card titles in the vacancy-bearing lists: ``` mcp__trello__set_active_board(boardId="6a1a9a5af082cb0526b22704") mcp__trello__get_cards_by_list_id(listId="6a1aa59555aab72a261c42aa") # TODO mcp__trello__get_cards_by_list_id(listId="6a1aa59a5e7e651b1352895b") # In Progress mcp__trello__get_cards_by_list_id(listId="6a267e2e9ed02737346dc047") # In touch mcp__trello__get_cards_by_list_id(listId="6a1aa5a1d8bcb0ed7234987b") # Applyed mcp__trello__get_cards_by_list_id(listId="6a245a328452976c0d1fcca1") # Rejected ``` From the results keep **only** each card's `name` (the "Company — Role" title); discard everything else. That titles-only list is `known_applied` — pass it into every Haiku subagent prompt (see template). Match new vacancies by company-name substring. (Rejected is included on purpose — a rejected company is still "already processed".) **Context safety (important).** This MCP has no full-text card search and no field-limited fetch — `get_cards_by_list_id` returns the full ~1.5 KB JSON per card. So: - Fetch only the five vacancy lists above (not Base / Boards / Artifactes), and do it **once** per run. - Distill to titles immediately; never feed full card JSON into a subagent. - When the **Applyed** list grows large (hundreds of cards), use the lean equivalent instead — one Trello REST call returning names only: ```bash source .env && curl -s "https://api.trello.com/1/boards/6a1a9a5af082cb0526b22704/cards?fields=name,idList&filter=visible&key=$TRELLO_API_KEY&token=$TRELLO_TOKEN" \ | jq -r '.[].name' ``` This returns every card name on the board in one compact payload — the direct successor to the old `applications.md` grep. `tracking/applications.md` is **frozen/legacy** — no longer read for dedup. ### Stratification rules 1. Read the file. Group entries by channel `priority` field (`p1` / `p2` / `p3`). 2. **All p1, p2, and p3 entries**: delegate to a Haiku subagent first pass. - Pass the raw entries, the playbook summary, and the `known_applied` list to the subagent. - Subagent returns a structured verdict list (APPLY / SKIP / QUICK-APPLY / VERIFY). - SKIP verdicts from Haiku are accepted without re-review. 3. **Main session reviews only** APPLY and VERIFY verdicts from Haiku — runs full Step 1–8 per entry. 4. After all verdicts: promote confirmed APPLY entries to **Trello** (a card per application — see "After triage — update tracking"). Never write to `tracking/applications.md`. 5. For QUICK-APPLY entries: note explicitly, ask Oleg if he wants to proceed. 6. For VERIFY entries: run a fast WebSearch `"{company} remote hiring countries"` to resolve geo; then apply the gate. **Why Haiku-first for everything:** p1 channels can have 50–100+ messages per run. Sending them straight to the main session risks context overflow and losing genuine APPLY roles in the flood. Haiku filters down to a manageable APPLY+VERIFY set; main session then does quality deep-dive on only those. A few extra false VERIFYs from Haiku are fine — main session catches them. Missing a real APPLY because main session ran out of context is not fine. ### Haiku subagent prompt template ``` You are triaging job vacancies for Oleg Proskurin (Tech Lead / AI Engineer, based in Thailand GMT+7, available for remote work). Apply these 4 hard gates — SKIP on first failure: Gate 1 (Geo): Must allow: global remote non-US/UK/EU, contractor via Deel/EOR from Thailand, B2B remote, Russian-speaking teams hiring remotely (not RU/BY presence-required), relocation with comp ≥$10k, geography-agnostic temp contracts. Gate 2 (TZ): Oleg's window 9:00–22:00 GMT+7. Outside window: ok if comp > $9k. Night/PST core: ok if comp > $10k. Skip only if comp ≤ $9k AND overlap is outside window. Gate 3 (Comp): Skip if < $4.5k/month. Floor $6–8k, target $8–12k. Gate 4 (Level): Skip if explicitly Junior or mid with no growth ceiling. Posture: trying-friendly when data IS PRESENT and borderline. When key gate data is ABSENT from the message text, return VERIFY — not APPLY. Lean toward APPLY only when geo and comp are explicitly stated and borderline (e.g. "global remote", "$7k/month"). Geo rules (Gate 1): - Message explicitly says "global remote" / "worldwide" / "anywhere" / "любой город" / contractor-friendly → Gate 1 passes, proceed. - Message says "United States" / "US only" / "United Kingdom only" / specific country list excluding Thailand → SKIP Gate 1. - Message says "Remote" with no geo qualifier → VERIFY geo (cannot confirm Thailand eligibility without JD). - Message has no location info at all → VERIFY geo. Comp rules (Gate 3): - Explicit salary ≥ $4.5k/month or ≥ $54k/year → Gate 3 passes, proceed. - Explicit salary < $4.5k/month → SKIP Gate 3. - No salary mentioned → VERIFY comp. - Timezone offset explicitly stated (e.g. GMT-GMT+6) → treat as geo restriction, apply Gate 1. Known applied companies — flag these as SKIP (already applied): [INSERT known_applied LIST HERE — one line per company] For each vacancy below, output one line: APPLY | SKIP | QUICK-APPLY | VERIFY — [company/role] — [gate failed or what's missing] — [track: A/B/Backend-first/Hybrid] Vacancies: [paste entries from telegram_inbox.json] ``` --- ## After triage — update tracking **A Trello card is the application record** (this replaces the old `applications.md` row — that file is frozen/legacy, do not write to it). For every APPLY verdict, create one card in the TODO column (`6a1aa59555aab72a261c42aa`) on board `6a1a9a5af082cb0526b22704`: - **Title:** `Company — Role (Track X)` - **Column = status.** The funnel is the columns: TODO (to apply) → In Progress (applying) → In touch (recruiter contact / screening) → Applyed (submitted); Rejected (closed). New cards land in TODO; Oleg moves them as stages change. - **Description:** the triage verdict block, plus a `Tracking` footer carrying everything the old applications.md row held: ``` --- **Tracking** - Date added: - Channel: - CV used: - JD: - Status: ``` For SKIP: no card needed unless Oleg says to keep it for reference.