Releases: vbkatarnaware/careeros
Release list
v1.3.2 — PDF-by-default for Resume/Cover, fix silent Markdown fallback
[1.3.2] - 2026-07-12
Fixed
-
Resume/Cover Letter silently uploaded to Drive as Markdown instead of
PDF. The[pdf]extra (fpdf2) was a separate, easy-to-forget install
step from[drive]— a missingfpdf2degraded every Resume/Cover
upload to.mdwith only a per-file warning, easy to miss.fpdf2is
now bundled into the[drive]extra, sopip install -e ".[drive]"
alone gets PDF rendering by default;careeros doctor(with
drive.enabled: true) now also proactively checks for it and FAILs with
a clear fix if it's ever missing, instead of only surfacing a buried
warning duringdaily. -
Only Resume and Cover Letter ever attempt PDF now. Application
Answers, Evaluation, and Deep Report always upload as Markdown — not
"PDF with a fallback," simply never attempted, since only Resume/Cover
are ever attached to a job application. -
A Resume/Cover previously uploaded as
.md(before PDF rendering was
available) is no longer left orphaned in Drive once PDF rendering
works. Drive matches existing files by exact filename, so a new
.pdfupload didn't replace an old.md— both used to sit side by
side in the flat Drive folder. Re-uploading now explicitly deletes the
stale.mdwhen a.pdfsupersedes it for the same job. -
Edge case, found during this release's own verification: the stale-
.mdcleanup above now also handles two different jobs that happen to share the exact same Company/Role string (a genuine name collision, disambiguated with a numeric suffix like... (2).md) — the cleanup previously only checked the un-suffixed base filename.
v1.3.1 — Applyability scoring contract, live provider quota, anti-scripting guardrail
[1.3.1] - 2026-07-12
Added
careeros doctor --live. Opt-in flag that actually verifies
Fantastic Jobs and every configured Apify token against their real APIs
right now (a small, bounded amount of real quota: one 1-record fetch,
plus a free account-usage check per Apify token, no actor run) instead of
only reporting local/stored state — catches a bad or exhausted key before
your firstdailyrun, not mid-discover.- Live provider-quota surfacing. The discovery summary table now shows
each provider's real, provider-reported remaining quota (e.g. Fantastic
Jobs'x-ratelimit-*headers) when available, next to its record count
and timing.
Fixed
- Score/recommendation could disagree.
evaluate'sscoreis meant to
mean applyability — a green (≥ threshold) score should always mean the
candidate can actually apply — but a job with a real deal-breaker (an
onsite location outside your accepted cities, a stated preference
violation) could still show a high score alongside
recommendation: "skip".careeros evaluate --finalizenow
deterministically caps a skip-recommendation eval's stored score below
threshold, so the two signals can never disagree, regardless of which
agent/model produced the evaluation. The 5 rubric dimensions themselves
stay honest and untouched — only the final score is clamped — so the
"why" behind a skip is still legible.prompts/eval_v2.mddocuments the
contract explicitly, including an anchor scale for scoringlogistics
honestly as a preference-ranking signal rather than a pass/fail gate. - Fantastic Jobs' weekly quota counter was locally calculated, not
live-verified. The local.careeros/discovery_budget.jsonweekly
record counter is independent of which API key is configured, so
rotating in a fresh key did nothing to clear a "quota exhausted" state.
The counter is now advisory-only; the live API call (and its
x-ratelimit-*response headers) is authoritative, so a fresh key works
immediately. - An exhausted Apify token stayed "exhausted" for the rest of the billing
cycle, even after a mid-month top-up on the same token, with zero
re-verification. A token now only pre-emptively skips on the same day
it was marked exhausted — any other day gets one fresh live retry before
being trusted as exhausted again. Old on-disk state (the pre-1.3.1
bare-list format) migrates automatically. - AI reasoning stages could be silently replaced by scripts. A new
standing rule inAGENT_GUIDE.md("Reasoning stages must be reasoned,
never scripted"), referenced fromprompts/gate_v1.md,
prompts/eval_v2.md, andskills/daily.md's Gate/Evaluate steps, makes
explicit that Gate/Evaluate/Resume/Cover/Application-Answer output must
always come from real per-job reasoning — never a keyword-matching or
formulaic script standing in for it, even under batch-size pressure.
v1.1.0
Added
- Automatic Application Answers. For every Apply-tier job,
dailynow
drafts real answers to that job's actual application questions — no
waiting until you've manually opened the form. A new background form-reader
(careeros/apply/browser.py) fetches the form's visible text: a
zero-dependency HTTP tier first, an optional headless-Playwright fallback
only when the form genuinely needs JavaScript to render. Neither tier uses
per-site scraping selectors — the agent identifies the real questions from
plain text and drafts grounded answers the same "selector, not writer" way
as the resume. careeros publish <job-id>— upload one job's current artifacts
(resume, cover, evaluation, deep report, application answers — whichever
exist) to Drive and patch just that Sheet row, without a fulldailyrun.
Theprepand on-demandapply <job-id>skills now run this
automatically as their last step.- A specific status for every unreadable form, replacing one generic
fallback: ✅ Generated, 🔒 Login Required, ❌ Closed, ⚙️ Playwright
Missing, 📄 No Essay Questions, 🌐 Network Error, 🛡️ Bot-Blocked, or the
generic Manual review required only when none of those match. Each is
detected generically (login walls, closed-posting notices, Cloudflare-style
bot challenges, and pages that render real text but never reach an actual
form) — no per-ATS logic. - A
Statuscolumn in the Sheet (dropdown:Not Appliedby default,
Applied,Received Call,Interview,After Interview,
Ongoing / In Process,Offer,Rejected) for tracking your own
application progress by hand. The pipeline sets the default on a new row
and never touches it again, exactly likeNotes. - New Sheet columns:
Evaluation (Drive),Deep Report (Drive),
Application Answers (Drive)— direct, per-job links to artifacts that
were already being generated (and, for Evaluation, already uploaded) but
had nowhere to show up. - Newest-on-top Sheet ordering. New rows are now inserted directly below
the header instead of appended at the bottom, so each day's run reads at
the top without scrolling past a growing history. careeros sheets sync-status— patch the Application Answers status
of existing Sheet rows after re-runningapply --prepare/--finalize
(e.g. reclassifying old jobs into the newer, more specific statuses)
without appending a duplicate row.- Personal/logistics questions, asked once. Notice period, work
authorization/visa status, salary expectations, earliest start date, and
employment type are the same answer on every application. The first time
one is genuinely needed and missing, it's asked once and saved to
profile.yaml's newlogistics:section — every later application,
batch or on-demand, reuses it automatically. Voluntary EEO/demographic
self-identification questions are deliberately excluded and always left
for you to answer yourself. careeros --version— prints the installed version and exits.careeros doctornow checks Playwright independently at two levels: the
[apply]extra's Python package, and thechromiumbrowser binary
(playwright install chromium) — "package installed but browser binary
missing" and "package not installed at all" are reported as distinct,
actionable messages instead of one opaque failure.
Changed
- Sheet columns removed:
Resume Path,Cover Letter Path,
Report Path(local filesystem paths, useless outside your own machine)
andDrive Folder(redundant once every artifact has its own direct
link).careeros sheets migrate/sheets appendremove these
automatically from an existing Sheet — see Migration notes below. - CI now installs the optional
[apply]extra and runs
playwright install chromium --with-deps, so the real headless-browser
fetch path runs in automated tests instead of being skipped. - The package version now has a single source of truth
(pyproject.toml);careeros.__version__reads it back via
importlib.metadatainstead of duplicating the string.
Fixed
- The Playwright fallback used
wait_until="networkidle", which never
fires on pages with persistent background network activity (analytics
beacons, a bot-check's own verification polling) — it could hard-timeout
with zero text captured even though the real content rendered within a
second or two. Switched towait_until="load"plus a short fixed buffer. - PDF rendering crashed instead of falling back to Markdown when resume,
cover, or answers content contained a non-Latin currency symbol (₹, €, £,
¥) — blocking the entire Drive upload for that job. Now sanitized to a
latin-1-safe equivalent, and any other still-unmappable character falls
back to Markdown instead of failing the upload. careeros sheets sync-statuscould overwrite a just-published
Application Answers link with a blank cell, because the local
drive_links.jsoncache isn't refreshed bycareeros publish. It now
never touches agenerated-status job's cell — onlypublishdoes.- A login-wall, closed-posting, or bot-blocked page returns real,
substantial (non-empty) text — it's just the wrong page. The batch apply
stage now checks for these BEFORE treating any non-empty fetch as a
readable form, so a LinkedIn login-wall page can no longer be silently
sent to the drafting step as if it were the real application form.
Migration notes: upgrading an existing Sheet
If you were running CareerOS before this release, your Google Sheet needs
one one-time cleanup pass:
careeros sheets migrate
This removes the four deprecated columns, adds the four new ones, applies
header/Score/Status formatting, backfills Status to Not Applied on
existing rows, and sorts your Sheet's existing rows by Date descending (a
one-time fix for history that was written bottom-up before this release).
It's safe to re-run — every step is idempotent, and a Sheet already on the
current schema is a no-op. After this one pass, every future daily run
keeps the Sheet current automatically; you never need to run it again
unless you skip several releases at once.
CareerOS v1.0.0
CareerOS v1.0.0
An AI-powered, deterministic job discovery and recommendation engine.
Deterministic where possible, AI only where reasoning adds value.
This is the definitive v1.0.0 — hardened through a full production audit
(P2.8) after the initial public commit, with discovery now permanently
frozen on evidence.
Highlights
- End-to-end daily pipeline: discover → normalize → dedupe → constraints →
AI gate → evaluate → two-tier threshold → resume/cover artifacts → daily
report → Google Sheets — driven from your host coding CLI (Claude Code,
Codex, Gemini CLI, etc.), zero server, zero database. - Discovery (frozen default): queries both Fantastic Jobs endpoints —
active-ats(career sites/ATS) andactive-jb(+LinkedIn/YC/Wellfound) —
every run, splitting the record budget 50/50 rather than doubling it.
Backed by a full 107-job production acceptance audit: the two sources
score a statistically equal ~8% ≥4.0 rate but are 92% disjoint, so
querying both roughly doubles interview-worthy jobs found at the same
quota cost. A single endpoint remains selectable via config. - Quota guard: recommends a daily discovery limit from your plan/quota/
goal, warns before exhaustion, and never silently overrides your
configured limit — fully config-driven, no hardcoded numbers. - Two-tier selection: jobs scoring ≥ threshold (default 4.0) get the
full pipeline (resume, cover, report, Drive, Sheet row); jobs in the
Consider band (default 3.5–3.9) get a Sheet row with score + a concise
reason only — no AI artifacts, no Drive cost — so near-misses stay
visible for free. - Profile-driven segmented discovery — one query per work-mode tier instead
of a single broad fetch. - Cache-everything AI stages, fingerprinted on job content + profile version
- prompt version — an unchanged re-run costs zero tokens.
- Optional Google Drive backup of shortlisted artifacts, additive and off
by default. New step-by-step setup guide:docs/google-setup.md. - 204 tests, including a parity test proving the REST and legacy-actor
providers map identical raw data to an identicalJobshape, and a
regression suite for the two-tier selection, quota guard, and Sheets
schema-migration logic.
What changed in the P2.8 hardening pass
- Fixed a real Sheets bug: dedup and row-writes now key columns by header
name, not position, with additive self-healing migration for sheets
created under an older column layout (dedup was silently broken on drift). - Fixed the day-level summary re-deriving Apply/Consider from raw evals
independently of the actual selection — it now renders the same
partitioned lists the pipeline already computed, so the report can never
disagree with what's in your Sheet. - Fixed the quota guard crashing
discoveron a non-ISO--datelabel. - Removed dead code found during the audit (an unused selection function,
unused helper constants) and corrected several stale docs.
Installation
pip install -e .
See the README for Quickstart and provider setup.
Advanced configuration
The default REST provider supports two interchangeable transports for the
same Fantastic Jobs dataset: the direct API
(developer.fantastic.jobs) or RapidAPI's
"Active Jobs DB" listing — pick whichever fits your pricing/free-tier needs
via config.api.transport. Discovery endpoint (both/active-ats/
active-jb) and the quota guard's plan/limit are also configurable — see
careeros/providers/README.md and templates/config.example.yaml.
Discovery is now frozen
No further discovery changes (endpoint, query strategy, provider set) unless
a production issue is found. Future work moves to P3 (outcome tracking) and
the v1.1 onboarding/Drive+PDF overhaul.