Parallel applying / how the fleet actually runs

One VS Code window. Several terminals. One Claude Code session in each. A private Chrome tab per session. And a hard stop before anything is sent.

one Chrome, N tab groups one worker per host atomic claiming never submits

The whole pipeline

Left to right: openings become scored matches, matches become packages, packages become queued tasks, and the queue fans out into as many workers as the work and the machine justify. The last hop is the one that never happens automatically.

opportunity-engine to agent-fleet parallel pipeline Prepared application packages enter a shared queue. Three parallel workers each claim a task and fill a form in their own Chrome tab. Each worker then stops at its own wall — a login, a CAPTCHA, a question only a person can answer, or the submit button — and leaves that tab open. A person clears each tab separately; fleet blocked is only the index of what is waiting. prepare queue workers · parallel each tab stops at its own wall you opp scan opp match opp apply --fleet you answer MISSING .agents/queue/inbox p3 greenhouse p3 lever p5 ashby terminal 1 own Chrome tab terminal 2 own Chrome tab terminal 3 own Chrome tab login required tab 1 · greenhouse.io captcha · I am not a robot tab 2 · lever.co ready to submit tab 3 · ashbyhq.com you — every wall, every send fleet blocked · only the index you open each tab: log in, pass the check, answer, press submit
Green is a machine carrying the work; red is work that has stopped for a person. Each worker hits its own wall in its own tab and leaves that tab open — the walls are not pooled, and neither is your attention. Wherever the appears, nothing advances until you act.

Requirements

LayerNeedsIf missing
opportunity-enginepython3 ≥ 3.10, curlnothing runs / scans get very slow
PDF CVspdflatexyou still get .tex
Queueing (--fleet)nothing extra
Running the fleetagent-fleet--fleet writes fleet-batch.txt instead
Terminal workersX11, xdotool, a VS Code windowfalls back to headless — no browser
Browser workersClaude for Chrome, terminal modetasks come back blocked, never faked
# the tool
git clone https://github.com/MSKazemi/opportunity-engine ~/opportunity-engine
~/opportunity-engine/bin/install

# the fleet (optional, needed only to run workers in parallel)
git clone https://github.com/MSKazemi/agent-fleet ~/agent-fleet
~/agent-fleet/bin/install

# your data — a private directory, NOT a clone of a public repo
cd ~/your-private-dir && opp init && opp ingest ~/cv.pdf

How many workers?

The number is never guessed. It is the smallest of four ceilings, re-checked on every run:

The work

Only tasks that actually need a rendered page take a terminal. Research and drafting run as in-session subagents with no tab at all.

One per host

Ten postings across three boards become three terminals — so no single site sees parallel traffic from you.

Your cap

max_workers in .agents/config.json. An explicit ceiling always wins.

This machine

Free RAM, cores and current load, measured now — not assumed. fleet plan shows the arithmetic.

The lane with no terminal

Not everything needs a browser, and work that does not need one should not take a tab. Checking whether a confirmation email arrived is the clearest case: it runs as a subagent in the agent lane, alongside the browser workers, costing no terminal and no Chrome tab.

opp inbox              # what is awaiting confirmation, and exactly what would be read
opp inbox --fleet      # queue one reconciler in the agent lane

$ fleet plan
  agent      1 task(s)   in-session subagent, no terminal (parallel)
terminals needed: 0

One reconciler, not one per worker. Several sessions racing on the same mailbox buys nothing and multiplies who can read it.

What it is allowed to read

The boundary is drawn from where you actually applied: messages after your earliest pending application, from the ATS domains you applied through, or with a subject naming one of those employers. opp inbox prints that boundary before you queue anything.

what a reconciler would be allowed to read
  after       2026-08-16T00:18:25+00:00
  from        ashbyhq.com, greenhouse.io, lever.co
  or subject  ACME, Globex, Initech

A company's display name is never turned into a domain — an allow-list entry nobody verified is not an allow-list. Nothing outside the scope is opened, and nothing is ever replied to, forwarded, labelled, archived or deleted.

Never automated, in any mode: clicking a link in an email, following an account-activation or address-verification link, and retrieving a one-time code, PIN or 2FA code.

A code an agent fetches and types is not a second factor — the entire point of the second channel is that a person holds it. The reconciler reports that a code arrived and stops. Email from unknown senders is untrusted input, and a job search generates plenty of it: anything the reconciler reads is data, never instructions, and text addressed at an automated reader is quoted back to you rather than obeyed.

Why the tabs do not collide

Each Claude session gets its own per-session tab group. A worker sees only the tabs it created — it cannot navigate, read or close another worker's page, because the other worker's tabs are not in its context at all. That isolation is enforced by the platform, not by asking workers to behave.

Measured on 2026-08-16: two interactive Claude Code sessions held live connections to the same Chrome simultaneously, each reporting a distinct tabGroupId and listing only its own tabs. Print mode (claude -p) has no browser at all, which is why the browser lane needs fleet run --terminals rather than --headless.

Where it stops — once per tab, not once overall

A worker fills the form and stops. It screenshots the completed form and blocks with a precise reason. It never clicks submit, never types a password, never creates an account, never solves a CAPTCHA, never enters a one-time code, and never enters bank, card or tax details.

Those rules travel inside each task (<package>/fleet-task.json) rather than living in the worker, so a worker cannot be configured into breaking them. A field that cannot be filled truthfully from your own profile is escalated to you, never guessed — most forms ask those questions under a declaration.

Sites defend themselves differently, so each worker hits a different wall. The moment it does, it leaves its own tab open on that page and claims the next task, so the fleet keeps moving while that one waits for you. The tab is the handover — you walk into a page already loaded and already filled, and do only the part a person has to do.

What the tab is showingReason it blocked withWhat only you can do
A sign-in pagelogin required: <site>Log in. Passwords are never typed by a worker.
“Verify you are human”captcha: <site>Pass the check yourself. Never bypassed, never automated.
A one-time code prompt2fa: <site>Enter the code from your own device.
A question not in your profileneeds answer: <question>Answer it truthfully. A guess here is a false statement in your name.
Identity or payment documentsneeds human: <what it wants>Decide whether to continue at all — a form demanding these to apply is suspicious.
A completed formready to submit: <org> — <title>Read it, then press submit.

Two modes — and the one that does not change

The diagram above shows the default: a worker fills the form and stops at all three walls. At ten or fifty packages that review becomes the bottleneck, so sending can be automated instead — in .opp/config.json:

{ "auto_submit": true, "auto_submit_max": 10 }

Look at what that actually changes in the picture. Only the third lane. Lanes one and two are a sign-in page and a “verify you are human” check — those are not automated in either mode, because passing them for you is exactly the thing this tool will not do. Turning auto_submit on speeds up the applications that had no wall in front of them; the ones behind a login still wait for you.

auto_submit: false (default)auto_submit: true
Fills the formyesyes
Screenshot before anything irreversibleyesyes
Presses submitno — blocks for youyes, if the package qualifies
Login / CAPTCHA / 2FAalways you — no setting changes this
A question not in your profilealways you — a guess would be a false statement
Per-run ceilingauto_submit_max

auto_submit buys speed. It does not buy a false statement: a package with an unanswerable field, private notes detected in its CV, or a knockout from your own rules is prepared and left waiting whatever the setting says — and a submit with no visible confirmation is still reported as blocked, never as sent.

fleet blocked is the index of what is waiting, grouped by reason — not a place the work happens. The work happens in each tab:

fleet blocked          # what is waiting, and why, grouped by reason
                       # → then switch to each tab and clear it yourself
fleet unblock --all    # release them back once you have dealt with them
opp applied <id> --evidence "confirmation #12345 shown on screen"

An application is recorded as applied only with the confirmation the system actually returned. A worker that cannot see one blocks with what it did see, rather than reporting success it cannot prove.