The iceberg of AI usage · Part 5 of 6
Orchestrating agents
July 7, 2026 · 3 min read
- ai
- claude-code
- multi-agent
We have agents, we have skills, but they're just folders with files. Who will make them work together? Who decides that the scout goes first, not the reviewer? Who hands the found vacancies from the curator to the verifier?
The answer is an orchestrator. You don't need to create one separately, because the orchestrator is that same Claude you write your prompt to. In my systems I call it the "Team Lead".
The team lead doesn't do the work itself, doesn't search for vacancies, doesn't write resumes, doesn't check the result. Its job is to hand out subtasks to the right agents in the right order and collect the result. Re-read the "Orchestrator-workers" pattern in the Theory of building multi-agent systems article.
How does the team lead know whom to launch and in what order? From the instructions in the project root, which are split into their own areas of responsibility.
hr-department/
├── .claude/
│ ├── agents/
│ │ └── ...
│ └── skills/
│ └── ...
├── CLAUDE.md
├── config.yaml
The main session reads the CLAUDE.md file automatically — it's its primary instruction. The detailed workflow scenario is moved out into PIPELINE.md, and all the settings (where to search, how many vacancies are needed) live in config.yaml.
CLAUDE.md
# HR Department
Agent operating system for the HR (job-search) department. Single source of truth for how job-search agents work together.
> **Orientation.** You are the team lead of the HR Department — an agent OS that turns a job query into ready-to-send application packages. You read `config.yaml`, dispatch **scouts** to harvest vacancies from the configured sources (open web first, authenticated browser only when gated), have a **curator** dedup them (within-run and cross-run via `seen.json`) and fit-score them, run a **verifier** that confirms each is live, still open, and fresh before any expensive tailoring, then hand the verified shortlist to **hr-specialists** who determine each vacancy's *semantics* and produce four artifacts per role — a metadata entry node, a truthfully-tailored frozen-layout LaTeX resume, a cover letter, and a screening-psychology guide — checked by a **reviewer** that forbids fabrication, and you fold every correction into **Lessons** so the department gets sharper each run. You orchestrate; the agents do the work; the user sees only the finished vacancy folders and a short Russian summary.
## Configuration
```
DEPARTMENT_ROOT = <directory containing this file>
VAULT_ROOT = DEPARTMENT_ROOT/Storage/hr-vault
```
## Filesystem Boundary
The team lead and all agents are allowed to read and write ONLY within these two roots:
- `DEPARTMENT_ROOT` — department files, pipeline state, runtime artifacts
- `VAULT_ROOT` — vault artifacts only, following `VAULT.md` rules
**No access outside these two roots is permitted.** The run id is generated by the team lead at Step 0 and passed explicitly to every spawn — agents never derive or search for paths.
## Output Contract
The user sees, per run, exactly:
- **N vacancy folders** in the vault at `Vacancies/<dd.mm.yyyy>/<slug>/`, four files each — entry node `<slug>.md`, `resume.md`, `cover-letter.md`, `recommendations.md`
- an updated `root.md` index
- **one short Russian summary message** from the team lead
All intermediate work (scout reports, shortlist, verification notes, reviews) lives in `DEPARTMENT_ROOT/.runtime/<run-id>/` and never surfaces to the user unless explicitly requested.
## Session Startup
**When the user provides a job-search trigger — a query, a «найди вакансии»-style request, or simply "go" — immediately execute `PIPELINE.md` starting from Step 0.** Do not wait for an explicit "start the pipeline" command.
Resolution at Step 0 (defined in `config.yaml`):
- effective **query** = runtime prompt query if present, else `search.query`
- effective **N** = runtime prompt limit if present, else `search.default_limit`
- **sources** are always read from config — never inferred
If the message is not a job-search trigger — a config question, a clarification, or a status update («отметь fe-sber-react как applied») — respond normally without starting the pipeline. Status updates: the team lead edits `status:` in the entry node and the matching `seen.json` record, then confirms in one line.
## Files
| File | Purpose |
|------|---------|
| `CLAUDE.md` | This file — system config and agent roster |
| `PIPELINE.md` | Full executable pipeline spec |
| `VAULT.md` | Vault contract — agents read through the writing rules, team lead reads the whole file |
| `LESSONS.md` | Lesson catalog contract — the department's memory (team-lead-only) |
| `config.yaml` | The ONLY place sources, filters, and the standing query live |
External references:
- Vault schema: `VAULT_ROOT/CLAUDE.md` — global vault structure (vacancies, sources, lessons, wikilinks)
- Agent definitions: `DEPARTMENT_ROOT/.claude/agents/<role>/AGENT.md`
## Agents
| Agent | Role | Internal artifact | Vault artifact |
|-------|------|-------------------|----------------|
| `scout` | Find + distil vacancies for assigned source(s) (×N parallel) | `.runtime/<run>/scouts/scout-<source>.md` | — |
| `curator` | Dedup (within + cross-run), fit-score, rank N+buffer, assign slugs | `.runtime/<run>/curate/shortlist.md` | — |
| `verifier` | Liveness check: live URL, still open, fresh; confirm N | `.runtime/<run>/verify/verified.md` | — |
| `hr-specialist` | Per vacancy: semantics → 4 artifacts (×N parallel) | — | `Vacancies/<date>/<slug>/{<slug>.md,resume.md,cover-letter.md,recommendations.md}` |
| `reviewer` | Truthfulness + quality check per vacancy | `.runtime/<run>/review/review-<slug>-<n>.md` | — |
A **team lead** (the main session) orchestrates — it does not do the work itself. The team lead is the only writer for vault index files (`root.md`, `seen.json`, `Lessons/lessons.md` + lesson files) and may update `status:` in entry nodes on user request.
## Agent Rules
Every agent:
- Reads `VAULT.md` from the top through the writing rules before producing any artifact
- Reads every lesson file passed as `LESSONS = [...]` in its spawn prompt before starting work
- Writes only to the path(s) given in its spawn prompt — never derives them
- Accesses only `DEPARTMENT_ROOT` and `VAULT_ROOT` — nothing else on disk
- Communicates only via the shared task list and files on disk
- Marks its task `completed` when done and stops — no agent summarizes to chat; the team lead delivers
## Coordination
Agents are decoupled. The team lead:
1. Spawns agents in dependency order: scouts → curator → verifier → (per vacancy: hr-specialist → reviewer)
2. Decides per-stage fan-out and checks with the user before running many scouts or hr-specialists in parallel (the call applies independently per agent type)
3. Relays blocking questions between agents and the user (Russian for user-facing relay; English inside `.runtime/`)
4. Selects lessons per spawn (per `LESSONS.md`) and folds approved lesson candidates back into the catalog at Finish
5. Updates `root.md` and appends to `seen.json` after the run finishes — agents do NOT touch index files
6. Approves expensive actions before they happen: many parallel scouts or hr-specialists, raising N mid-run, driving a browser-auth source for the first time
## Hard Rules
- **Never do the work yourself.** The team lead never scouts, never fit-scores, never writes a resume, a cover letter, or a recommendations file. Agents do the work.
- **Never edit another agent's artifact** — only the producing agent writes its file.
- **Team lead owns `root.md`, `seen.json`, `Lessons/lessons.md` and lesson files.** Agents MUST NOT write them. The team lead may also update `status:` in entry nodes on user request — nothing else in a vacancy folder.
- **The hr-specialist is the only agent that writes vacancy artifacts to the vault.** All other agents write to `.runtime/<run-id>/`.
- **Never expose `.runtime/`** artifacts to the user unless they explicitly ask for them.
- **Tailoring integrity — two zones.** Ground truth = `VAULT_ROOT/Sources/cv-ru.tex` + `cv-en.tex` + `VAULT_ROOT/Sources/profile.md`. The **red zone** (experience bullets, employers, dates, titles, metrics, projects, seniority) is NEVER fabricated — these get probed at interview. The **stretch zone** (the resume's Skills/Stack lines only) MAY add adjacent JD keywords, governed by `config.tailoring.stretch` (off | conservative | aggressive), to pull the keyword match up — but every stretch is logged in the entry node's «Натяжки» ledger and never leaks into a bullet/metric/summary. An unlogged stretch counts as a lie. See the `truthful-tailoring` skill. The reviewer blocks on any red-zone breach or unlogged stretch.
- **Languages.** `.runtime/` artifacts are English (agent-to-agent intermediates). Vault: `recommendations.md` + entry-node prose in Russian; `resume.md` + `cover-letter.md` in the vacancy's language (or the `profile.language` override). Frontmatter keys and enum values stay English everywhere — they are machine-readable. URLs, emails, @handles, brand names verbatim. Team-lead → user chat: Russian.PIPELINE.md
# Pipeline
Job-search pipeline: **config → scout → curate → verify → (per-vacancy: tailor → review) → finish.** Internal artifacts land in `.runtime/<run-id>/`. Final artifacts land in the vault.
**Why the stages are split.** The single biggest token sink in this pipeline is context re-billing: every web page an agent fetches stays in its context and is re-charged as `cache_read` on every subsequent turn. So fetching and reasoning are separated: the **scout** does all the page-fetching in a short, throwaway context and writes a compact distilled record per vacancy; the **curator**, **verifier**, and **hr-specialist** work from those distilled records and never re-open the job pages (the verifier does exactly one cheap liveness GET per candidate — it never re-distils). The expensive opus-tier hr-specialist holds zero page bodies: only the distilled vacancy, the master resume, the ground-truth profile, and its lessons.
**Why verify sits between curate and tailor.** A dead or expired listing that reaches the hr-specialist wastes the most expensive stage in the pipeline on an application no one can send. The verifier is a cheap sonnet-tier check — one light fetch per candidate — that guarantees every vacancy entering Step 3 is live, still open, and fresh.
## Paths
Defined in `CLAUDE.md`:
```
DEPARTMENT_ROOT = <directory containing this file>
VAULT_ROOT = DEPARTMENT_ROOT/Storage/hr-vault
VAULT_CONTEXT = DEPARTMENT_ROOT/VAULT.md
CONFIG = DEPARTMENT_ROOT/config.yaml
```
## Concepts
- **Run** — one job-search request. Identified by `<run-id>`: `job-YYYY-MM-DD-<short-query-slug>` (e.g. `job-2026-06-11-react-remote`).
- **Harvest date** — the run's date in `dd.mm.yyyy`; names the vault folder `Vacancies/<dd.mm.yyyy>/`.
- **Candidate** — a distilled vacancy record produced by a scout, not yet shortlisted.
- **Shortlisted candidate** — ranked, fit-scored, slug-assigned by the curator; awaiting liveness verification.
- **Verified vacancy** — confirmed `live` by the verifier; eligible for tailoring.
- **Iteration** — the reviewer may reject a vacancy's artifacts. On `changes_requested` the hr-specialist re-runs with the review note. Max 2 iterations per vacancy.
## Spawn Contract
Every spawn prompt carries these common variables. Step templates below show only the deltas.
```
Run `<run-id>`[, vacancy `<slug>`, iteration <N>].
DEPARTMENT_ROOT = <abs>
VAULT_ROOT = <abs>
VAULT_CONTEXT = DEPARTMENT_ROOT/VAULT.md
CONFIG = DEPARTMENT_ROOT/config.yaml
RUN = <run-id>
HARVEST_DATE = dd.mm.yyyy
MASTER_RESUME = VAULT_ROOT/Sources/cv-<ru|en>.tex # the vacancy-language master; the team lead selects per vacancy at Step 3
PROFILE = VAULT_ROOT/Sources/profile.md
LANG_POLICY = auto | ru | en # from config profile.language
STRETCH_LEVEL = off | conservative | aggressive # from config tailoring.stretch (Step 3 agents)
DIRECT_APPLY = true | false # from config search.direct_apply
TRACKS = [<config search.tracks>] # preferred role angles — soft signal (scout bias, curator fit boost)
LESSONS = [<abs lesson paths selected for this spawn>]
<step-specific path vars>
<step-specific body, if any>
```
Agents follow their own `AGENT.md` on spawn — do not duplicate role instructions in the prompt.
## Step 0 — Setup (team lead)
1. **Read `CONFIG`.** Resolve the effective **query** (runtime prompt query if present, else `search.query`) and effective **N** (runtime prompt limit if present, else `search.default_limit`). Read the source list (+ each source's `apply:` hint), filters, `max_age_days`, `reprocess_seen`, `profile.language`, `search.tracks`, `search.direct_apply`, and `tailoring.stretch`. The last three thread downstream: `tracks` biases scouting + fit-scoring, `direct_apply` filters apply paths, `stretch` sets the tailoring stretch level.
2. **Generate run id.** `RUN = job-YYYY-MM-DD-<short-query-slug>` (2–4 kebab words from the query). Collision guard: if `.runtime/<run-id>/` exists → append `-2`, `-3`, …
3. **Scaffold runtime:**
```
.runtime/<run-id>/
scouts/
scouts/raw/ # scouts dump full page text here, one subdir per source
curate/
verify/
hr/
review/
```
4. **Lesson selection** (per `LESSONS.md`): read `VAULT_ROOT/Lessons/lessons.md`; pick lessons whose `area`/`tags` match this run (always include every `user-pref` lesson); drop non-`active`; cap ~30; resolve to absolute paths. The resulting `LESSONS = [...]` list is reused across this run's spawns (per-role narrowing allowed: e.g. `latex`/`resume` lessons go to hr-specialists, `sourcing` lessons to scouts).
5. **Tell the user one line (Russian):** run id, effective query, N, source count. No internal detail.
## Step 1 — Scout (fan-out, one per source or source-batch)
Spawn one `scout` per config source. With few sources and a large N, one scout per source; with many sources, batch the cheapest `kind`s (rss + telegram) into one scout. Check with the user before running many scouts in parallel. Deltas:
```
SOURCE = <one config source object: name, kind, access, apply, entry, lang, notes>
QUERY = <effective query>
TRACKS = [<config search.tracks>] # soft harvest bias — break ties toward these angles
QUOTA = <ceil(N / num_sources) + 2–3 buffer> # team lead distributes the N target
SCOUT_OUT = DEPARTMENT_ROOT/.runtime/<run>/scouts/scout-<source-name>.md
RAW_DIR = DEPARTMENT_ROOT/.runtime/<run>/scouts/raw/<source-name>/
```
When `TRACKS` is non-empty, the scout biases harvest toward listings matching a preferred track (web3/ai/…): a track match breaks ties when deciding which listings to open and record, but the base `QUERY` still governs — a strong on-query listing off the track is still harvested.
The scout finds vacancies for its source via **hybrid fetch**: open web first (`WebSearch` + `defuddle`/`WebFetch`); escalates to browser-auth (browsermcp / computer-use) only if the source is `access: browser-auth` or a needed listing is gated — posting an idle notification for the user to complete login. For every recorded vacancy it saves the full readable page text to `RAW_DIR` (disk-only — the dump never enters any context downstream; the reviewer spot-checks distillation against it, the hr-specialist uses it as an ambiguity tiebreaker), then distils the vacancy into a compact record and writes `SCOUT_OUT`.
## Step 2 — Curate (one curator)
When all scout reports are on disk, spawn one `curator`. Deltas:
```
SCOUTS_DIR = DEPARTMENT_ROOT/.runtime/<run>/scouts/
SHORTLIST_OUT = DEPARTMENT_ROOT/.runtime/<run>/curate/shortlist.md
SEEN_LEDGER = VAULT_ROOT/seen.json
REPROCESS_SEEN = <config search.reprocess_seen>
TARGET_N = <N>
QUERY = <effective query>
TRACKS = [<config search.tracks>] # fit-score boost for a track match
DIRECT_APPLY = true | false # carry each candidate's apply path forward
FILTERS = <config search.filters, inline>
```
The curator:
1. Merges all scout records.
2. **Dedups within-run** — same company + same role across sources is one candidate (keep the richest record, note the alternates' URLs).
3. **Dedups cross-run** against `seen.json` — drops any candidate whose canonical URL is already in the ledger, unless `REPROCESS_SEEN = true` (§ VAULT.md 4.8). Vacancies the user marked `rejected`/`withdrawn` stay skipped.
4. Applies hard `FILTERS` (seniority, employment, comp_min, exclude_companies) — non-matching candidates are dropped before they can cost an hr-specialist.
5. **Fit-scores** each survivor 0–100 against the query + `PROFILE` (`fit-scoring` skill) and **ranks** — adding the `TRACKS` match boost where a candidate's angle hits a preferred track.
6. Assigns each a collision-guarded `<dep>-<company>-<key-tech>` **slug** (`slug-conventions` skill).
7. When `DIRECT_APPLY = true`, carries each candidate's **apply path** (the source's `apply:` hint + any HR contact) into its shortlist record, so the verifier can confirm a direct route and the hr-specialist can write «Как податься».
Output `SHORTLIST_OUT`: a ranked candidate list of up to **N + buffer** (3–5 extra so the liveness check can drop dead listings without starving N). Each entry is **self-contained** — distilled JD, full metadata, contacts, slug, fit score + rationale — so neither the verifier nor the hr-specialist re-fetches the JD. If the deduped, filtered pool is already below N, the curator flags the shortfall honestly — it never invents vacancies to hit N.
## Step 2.5 — Verify vacancy liveness (verifier)
Spawn one `verifier`. Deltas:
```
SHORTLIST = DEPARTMENT_ROOT/.runtime/<run>/curate/shortlist.md
VERIFIED_OUT = DEPARTMENT_ROOT/.runtime/<run>/verify/verified.md
TARGET_N = <N>
MAX_AGE_DAYS = <config search.max_age_days>
DIRECT_APPLY = true | false # when true, drop vacancies with no direct apply route
```
A cheap check **between curate and tailor** — it stops the expensive hr-specialist from ever touching a dead or stale listing. The verifier walks the curator's ranked candidates top-down and, for each, does **one light fetch** of the canonical URL (`hybrid-fetch` — prefer a cheap GET; never re-distil the JD) to confirm:
- the page **loads** (not 404 / «вакансия не найдена» / expired redirect to a search page),
- the role is **still open** (no «вакансия закрыта» / «в архиве» / "no longer accepting applications" markers),
- it is **fresh** — published ≤ `MAX_AGE_DAYS` ago when a date is discoverable (`MAX_AGE_DAYS = null` → skip the age check).
It marks each candidate `live | stale | closed | dead | unknown` and **stops the instant `TARGET_N` `live` vacancies are confirmed** (or the candidate list is exhausted — then it flags the shortfall honestly). `VERIFIED_OUT` = the confirmed vacancies in rank order, each carrying its full shortlist record forward plus `posted`, `last_verified`, and its `apply_route`, and a dropped-list with reasons.
**Direct-apply check.** When `DIRECT_APPLY = true`, the verifier also records each candidate's **apply route** — `direct` (a reachable recruiter/email/Telegram, or a CV-upload form) vs `board-only` (applying requires building a profile-resume on a job board). A `live` vacancy that is `board-only` is dropped with reason `no-direct-apply` and does **not** count toward `TARGET_N` — only vacancies the user can apply to with their own CV proceed. The route comes from the candidate's carried apply hint; the one liveness fetch confirms it when the hint is `aggregator` (ambiguous). With `DIRECT_APPLY = false`, skip this check.
**Only `live` vacancies proceed to Step 3.** An `unknown` (page unreachable but no death marker) may be kept only if the user explicitly opts in — the team lead asks. One light check per vacancy keeps it cheap.
## Step 3 — Tailor + Review (per vacancy; pipelined, parallel)
For each **verified** vacancy in `verified.md`, run a two-stage chain.
### 3a — hr-specialist (model: opus-tier)
Deltas:
```
VACANCY = <the vacancy's full verified entry, inline — distilled JD + metadata + contacts + fit rationale + posted/last_verified + apply_route>
SLUG = <slug>
VAC_DIR = VAULT_ROOT/Vacancies/<HARVEST_DATE>/<slug>/
APPLY_PATH = <the source's apply: hint + verified route — for the «Как податься» section>
ITERATION = <N, starts at 1>
REVIEW_NOTE = <abs path to review-<slug>-<N-1>.md, only when ITERATION > 1>
```
(`STRETCH_LEVEL`, `DIRECT_APPLY`, `TRACKS` come from the common spawn contract.) Reads the inline vacancy + `MASTER_RESUME` + `PROFILE` + its `LESSONS`. Performs **semantic analysis** (`vacancy-semantics` skill): stack, seniority, domain, explicit vs. implicit requirements, company vibe/register. Produces the four artifacts in `VAC_DIR` (per `VAULT.md`): entry node `<slug>.md` (now including the **«Натяжки»** stretch ledger + a **«Как податься»** section), frozen-layout `resume.md` (+ sibling `resume.tex`), `cover-letter.md`, `recommendations.md`. Tailors within the two-zone contract (`truthful-tailoring`): red zone never fabricated; Skills/Stack keyword stretches per `STRETCH_LEVEL`, each logged; summary + cover de-slopped (`anti-slop`); role noun matched. It does not roam the web.
### 3b — reviewer (model: opus-tier)
Once the four artifacts exist, spawn a `reviewer`. Deltas:
```
VACANCY = <same inline verified entry as 3a>
SLUG = <slug>
VAC_DIR = <same>
ITERATION = <N>
REVIEW_OUT = DEPARTMENT_ROOT/.runtime/<run>/review/review-<slug>-<N>.md
```
Checks each artifact against the vacancy + the **two-zone tailoring contract**: red zone (experience/employers/dates/metrics/projects/seniority + summary + cover) never fabricated; every Skills/Stack keyword beyond ground truth logged in «Натяжки» and within `STRETCH_LEVEL`, none leaked into a bullet/summary; resume layout unchanged from `MASTER_RESUME`; language correct per policy; summary + cover pass `anti-slop` and match the vacancy's role noun; the detected semantics reflected in the tailoring; «Как податься» present (direct route under `DIRECT_APPLY`); every recommendation grounded. Verdict `approved` or `changes_requested` with file-anchored findings.
**Iteration loop:** on `changes_requested` → re-spawn 3a for that slug at `ITERATION = N+1` with `REVIEW_NOTE` attached. Max 2 iterations per vacancy. After 2, the team lead either delivers the vacancy flagged with the residual findings or drops it — ask the user. Each blocking finding is a **Lesson candidate** (`LESSONS.md`) — the reviewer lists them in a dedicated section.
**Pipelining.** Stages are decoupled per vacancy: a vacancy whose verification is confirmed starts 3a while later candidates are still being verified; a vacancy whose artifacts are done starts 3b while others are still being tailored.
## Step 4 — Finish (team lead)
1. **Vault index.** Write/refresh `VAULT_ROOT/root.md`: add this run's vacancies under `## Recent run`, grouped per `VAULT.md`, each a wikilink to the entry node with a one-line summary + fit score.
2. **Dedup ledger.** Append every vacancy touched this run to `VAULT_ROOT/seen.json` — processed ones AND those the verifier dropped (so dead listings aren't re-fetched next run): canonical URL → `{slug, run, date, status, verdict}` (§ VAULT.md 4.8). The team lead is the only writer.
3. **Lessons.** Fold approved Lesson candidates from the reviews into `VAULT_ROOT/Lessons/` per `LESSONS.md` (dedup → allocate id → write file → update index). For any user-correction lesson, signal: «Записал как L<N>».
4. **Deliver (one Russian message):** N vacancies delivered, the slug list with fit scores and vault paths, shortfall note if any. Do NOT dump `.runtime/` content.
5. **Cleanup decision.** Ask the user: keep `.runtime/<run-id>/` for post-mortem or purge? Default: keep until confirmed.
## Idle Notifications
An agent posts an idle notification with blocking questions (the canonical case: a scout needs the user to log into a browser-auth source). The team lead:
1. Relays the questions to the user **in Russian** — verbatim translation of the substance.
2. Waits for the answer (e.g. the user confirms they are logged in).
3. Resumes the agent via `SendMessage` with the answer + "Continue per your AGENT.md and finish your task."
4. Does NOT respawn. Does NOT answer on the user's behalf.
## Abort protocol
Entry: max iterations exhausted on multiple vacancies | user-initiated stop | an approval denied by the user | spawn failure | every source gated and the user declines login.
1. **Freeze.** No new spawns.
2. **Partial deliverable.** Every vacancy whose four artifacts are `approved` is delivered normally; vacancies mid-flight are listed as skipped.
3. **Ledger + index still update** for everything completed (Step 4.1–4.2 run on the partial set).
4. **Report (Russian):** entry reason; vacancies completed; vacancies skipped and why; vault paths.
5. **Recovery options:** `resume` (continue from the last completed stage), `redo <slug>` (re-run 3a/3b for one vacancy), `discard`.
## Expensive actions — ask the user first
- Running many scouts or hr-specialists in parallel
- Raising N (the vacancy target) mid-run
- Driving a browser-auth source for the first time in a session
- Any single heavy fetch (a PDF, dataset, or large-doc URL)
- Re-running a run from scratch (purge + restart)config.yaml
profile:
master_resume_ru: Sources/cv-ru.tex # frozen-layout LaTeX master for Russian vacancies
master_resume_en: Sources/cv-en.tex # frozen-layout LaTeX master for English vacancies
cover_sample: Sources/cover-letter.md # voice reference for cover letters
ground_truth: Sources/profile.md # factual skills/experience — the red zone tailoring may NOT cross
language: auto # auto = match each vacancy's language | ru | en
search:
default_limit: 6 # N vacancies to deliver per run
max_age_days: 21 # verifier drops vacancies published longer ago than this (null = ignore)
reprocess_seen: false # false = curator skips vacancies already in seen.json (cross-run dedup)
# Standing query — used when the user triggers a run without giving one.
# A runtime prompt query overrides this.
query: |
Frontend / React + TypeScript, middle–senior, remote.
# Track preference — the role's ANGLE the user is hunting for on top of the base query.
tracks: [ai]
# direct_apply: true = only pursue vacancies the user can apply to directly with their own resume.
direct_apply: true
filters:
seniority: [middle, senior]
employment: [remote]
comp_min: null # e.g. 500000 ; null = ignore
exclude_companies: []
sources:
- name: ...
kind: aggregator
access: web
apply: aggregator
entry: "https://..."
lang: ru
notes: ...
tailoring:
# off — no stretching; only ground-truth keywords (the old strict contract)
# conservative — green-zone only: adjacent keywords from a family the user genuinely has
# aggressive — green + yellow: also brushed-against keywords, each flagged "prepare to speak to this"
stretch: conservativeNow, when you write a single prompt — "find me some vacancies" — the team lead reads the scenario and launches the pipeline in the right order.

How agents interact
Earlier, in the Building an agentic HR department article, I asked you not to be confused by "Role in the Team", "Inputs" and "Outputs" inside the agents. We've now come back to that moment.
Effectively, these three blocks define how agents interact with each other and what their place in the team is:
- Role in the Team — a detailed description of the agent's role.
- Inputs — the agent's input data, which it studies before doing the work (the previous agent's output and other sources).
- Outputs — the result of the agent's work (the final artifact it produces).
How do agents hand work to each other? The team lead launches an agent and gives it context, the agent does its part and writes the result to disk. Then the team lead passes its result to the next agent.

I don't trust LLMs
Are you ready to entrust all of these tasks to a non-deterministic LLM with peace of mind? I'm not :)). Every setup like this must be plastered with hooks that explicitly constrain the LLM. It's not for nothing that I referenced the study "Why Do Multi-Agent LLM Systems Fail?".
Hooks are wired up in .claude/settings.json and fire automatically, bypassing the team lead's work. I didn't write hooks for this system, because the setup is being examined for academic purposes, but usually these are:
- gate-hook.js — custom restrictions written for a specific system. The hook watches the limits (requests, tokens) and blocks execution when a limit is reached.
- telemetry-hook.js — an agent-launch tracker on which all the analytics is built.