Skip to content

Repository files navigation

Job Search OS

A Markdown-based operating system for running a job search — designed for both humans and AI agents.

A serious job search has two halves, and most tools only handle one.

The outbound half is pipeline management: a dozen companies at different stages, each with its own recruiter, conversation history, follow-up clock, tailored resume, and next action. Most of that state lives in scattered emails, browser tabs, and memory — which is exactly where it gets dropped.

The inbound half is where opportunities come from. Left alone it is accidental: a recruiter emails, a job alert fires, you happen to be browsing. Roles you would have wanted are simply never seen, and the funnel quietly narrows to whatever inbound produces.

Job Search OS covers both. Radar scans public job boards every day and hands you a ranked, reasoned review queue. The pipeline takes it from there. All of it is plain Markdown you can read in any editor, browse as an Obsidian vault, and hand to any AI agent as a shared source of truth.

This repository is a public starter template. It contains only fictional placeholder content. A real job search involves recruiter names, compensation data, and interview details — keep your copy in a private repository.

How It Works

Three ideas carry the whole system:

  1. One file per company is the source of truth. Everything about a company lives in Companies/[Company].md: role details, contacts, the full conversation log, follow-ups, resume notes, and a status history. Nothing about a company is stored anywhere else — so neither you nor an agent ever has to reconcile conflicting copies.
  2. The pipeline is a status board, not a database. PIPELINE.md is a single Markdown table with one row per active track: status, last contact, next action, due date. Closed tracks move to a ## Closed section so the live table only ever shows open work. It is glanceable in any editor and trivially parseable by tools.
  3. Agents follow written protocols. AGENTS.md tells any AI agent how to log a conversation, update the pipeline, surface overdue follow-ups, and close out a track — so every agent session leaves the system more organized, not less.
  4. Discovery is a daily job, not a mood. Radar scans public job boards on a schedule and writes a ranked record you triage in a couple of minutes, so which opportunities you see stops depending on who happened to email you.

The Full Loop

  RADAR (inbound)                        PIPELINE (outbound)
  ─────────────────                      ───────────────────
  public ATS boards                      Companies/*.md
        ↓  scan daily                          ↓
  title + location filter                 PIPELINE.md (generated)
        ↓  seen-set dedupe                     ↓
  score vs the 10-dimension rubric        follow-up + report sweeps
        ↓                                      ↓
  Radar/YYYY-MM-DD.md   ──you tick──▶     resume fork  ──▶  you apply
     (ranked, with reasoning)  Promote

One rubric spans both halves. Radar scores a posting on the same ten dimensions job_search_os.py scores a company record on, so a discovered role and a hand-entered one are directly comparable and a promoted role carries its score across without translation.

Nothing is automated past the point where judgment matters. Radar finds and ranks; you decide; the tooling drafts. It never submits an application.

Automation Layer

The repository includes a dependency-free automation layer that keeps the Markdown system auditable:

python3 tools/job_search_os.py check
python3 tools/job_search_os.py pipeline --write
python3 tools/job_search_os.py report --write
python3 tools/job_search_os.py resume-manifest --company "Acme Robotics" --write
python3 tools/job_search_os.py application-draft --company "Acme Robotics" --write

It adds the CareerOps-inspired capabilities this template is meant to demonstrate:

  • opportunity scoring with a visible 10-dimension rubric;
  • pipeline generation from company frontmatter;
  • duplicate, metadata, and pipeline-drift checks;
  • optional posting URL liveness checks;
  • follow-up and open-loop reporting;
  • funnel analytics by status and source;
  • ATS/posting scan ranking without submitting applications;
  • resume edit manifests that preserve your preferred external document renderer;
  • review-only application drafts grounded in the story bank.

The generated job-search-os-report.md, resume manifests, and application drafts are safe sample artifacts when run against the fictional data in this public template.

Radar — the inbound half

Radar scans a configured list of company job boards every day, keeps only roles matching your targets, scores what's new, and writes a dated Markdown record you triage with checkboxes.

python3 tools/job_search_os.py radar verify                       # check every board resolves
python3 tools/job_search_os.py radar collect --dry-run            # preview; creates nothing
python3 tools/job_search_os.py radar collect --write              # write today's record
python3 tools/job_search_os.py radar collect --score --write      # add model reasoning
python3 tools/job_search_os.py radar process --write              # act on ticked boxes

How it stays free and unblocked

Every source is a public, unauthenticated ATS endpoint — Greenhouse, Ashby, Lever, and Workday. No API keys, no proxies, no scraping, and no LinkedIn, whose terms prohibit automated access and whose postings mostly originate from these same boards anyway. Collection, filtering, and deduplication cost nothing.

Configuration

Everything personal lives in radar.config.yml: work authorization, location tiers, role families, exclusions, the company list, and the scoring model chain. No location or job title is hardcoded anywhere in the code.

Location scoring is remote-first by default and encodes something worth stating plainly: a posting reading "Canada (Remote)" means remote within Canada and still requires local work rights. It is scored below a domestic on-site role rather than as remote, and flagged 🛂, so the queue never implies a role is more reachable than it is. Postings that explicitly offer visa sponsorship get a bonus and a different callout.

Scoring

Radar always produces a deterministic keyword score. Set OPENROUTER_API_KEY and it additionally scores each new role with a model against the same ten rubric dimensions, writing a short rationale per role — which is what makes the record reviewable in two minutes instead of twenty.

The model chain is tried in order and falls back to the deterministic score if every model fails, so a bad model day degrades the record but never fails the run. location_fit is always computed deterministically and the model's value discarded: it encodes work authorization the model cannot know.

Treat model rationale as a directional read, not a citable claim. Free models confabulate specifics; the posting link is one click away.

Integrity gates

Three checks run on the record you actually read, not on a function's return value. Every one of them exists because a fully green unit suite shipped the bug anyway.

Attribution. Models rarely invent an employer. They take a real one from your background and pair it with an achievement that belongs to a different one — "proven experience deploying ML at <somewhere you worked for six weeks>" — written fluently enough to read as evidence. No allowlist of company names catches that, because the company name is right; the pairing is wrong.

So the scorer must emit a structured evidence array of employer-and-claim pairs beside its prose, and every pair is checked against the scoring.evidence ledger in radar.config.yml. Prose naming an employer without declaring a claim for it is rejected too, since that is the obvious way around the check. A violation is treated like a parse failure: the run advances to the next model in the chain, which is a free reroll. Leave the ledger out and scoring runs unchecked, exactly as before.

An employer listed with no claims can be named as somewhere you worked but never cited as proof — useful for a role too short to have delivered anything you would defend in an interview.

Metadata. Boards return placeholder employers: Adzuna sends the literal string UNAVAILABLE when a listing hides its company. Those postings are held before scoring, listed separately, and refused by promote — otherwise the tracker gains a company file named UNAVAILABLE.md and treats it as real forever.

Duplicates. The seen-set keys on ats:company:job_id, which cannot tell that "Globex Corporation, N.A.", "G.L.O.B.E.X.", and "Globex" are one employer posting one role three times. Identity is rebuilt from the normalized name and the destination URL. Company matching is deliberately loose and will also fold a parent into a subsidiary, so the surviving row lists every key it absorbed and the folded copies stay promotable.

A fourth check is advisory: a rationale that flatly denies evidence your ledger holds ("no documented ML work") raises a review flag in the day record without changing the score. Whether that denial is wrong or is a fair statement about a specific posting's bar is a judgment call, and a rule strict enough to catch it also fails rationales that name a real gap well.

Triage

Each day's record is a scannable table linking down to a block per role with its score, per-dimension reasoning, and two checkboxes:

- [ ] Promote `greenhouse:Acme:12345`
- [ ] Dismiss `greenhouse:Acme:12345`

Ticking a box does nothing on its own — markdown checkboxes are inert. radar process --write reads them, acts, and rewrites each handled line as plain text so a second run finds nothing to do. Promoting writes a Companies/*.md file that passes check with zero errors, regenerates PIPELINE.md, and produces a resume manifest.

There is no rolling inbox — per-day records are the queue. An earlier design kept both, and the inbox was regenerated each run, so un-actioned roles silently vanished: exactly the failure it existed to prevent. Day records give the same queue plus the daily delta, and miss a day and its file is still there with live checkboxes.

Running it daily

.github/workflows/radar.yml runs Radar on a weekday cron, commits the record, and optionally posts a short digest to Telegram. It installs nothing — Radar is standard-library only. Set OPENROUTER_API_KEY, and TELEGRAM_BOT_TOKEN plus TELEGRAM_CHAT_ID if you want the push.

The cron is commented out in this template — the workflow is manual-only here, and you uncomment the schedule in your private instance. Run it from the Actions tab to watch it work end to end.

This is worth doing deliberately rather than by default. The workflow commits its own output back to the repository, so on a public repo a daily run publishes which boards you watch, which roles you were shown, and the model's written assessment of your fit for each one. None of that is secret individually; together it is a detailed picture of your job search, accumulating on a schedule, under your name. Enable the cron where the repository is private.

The Pipeline Dashboard

The repository ships with a visual dashboard that renders PIPELINE.md as a Kanban board in your browser:

python3 tools/pipeline_board.py --open
  • Columns by stage: Prospect → Outreach → Applied → Screen → Interview → Offer, plus Paused.
  • Company radar: one health card per company (At Risk / Active / Waiting / Clear, open roles, nearest due date). Click to filter the board to that company.
  • Cards show the role, a priority badge derived from stage, due dates with overdue and due-soon highlighting, a stale flag when a track has gone quiet too long, and the next action — with the card title deep-linking to the company file in Obsidian.
  • Filters: priority chips and live search, combinable with the company filter.

The board is read-only by design: the Markdown stays the single source of truth, and the generated tools/dashboard.html is a gitignored build artifact. The script is dependency-free Python 3 and the HTML is fully self-contained (no network requests), so it works on a fresh clone with zero setup. Run the tests with python3 -m unittest tools.tests.test_pipeline_board.

Getting Started

  1. Click Use this template on GitHub and create a new private repository.

  2. Clone it and open the folder in your editor (or as an Obsidian vault).

  3. Run setup:

    python3 setup.py

    It asks what roles you're after, where you can work, which companies to watch, and how you want scoring configured, then writes radar.config.yml, seeds GOALS.md, and clears out the fictional example data. Nothing is written until you confirm, and re-running it is safe. You can skip it and edit radar.config.yml by hand.

  4. Check every board resolves. A wrong slug returns nothing and looks exactly like a company with no open roles, so this step is worth doing before you trust an empty result:

    python3 tools/job_search_os.py radar verify
  5. Preview your first scan without writing anything:

    python3 tools/job_search_os.py radar collect --dry-run
  6. Fill in the Positioning section of GOALS.md. Radar sends it to the scoring model as context, so concrete beats aspirational.

  7. Point your AI agent at the repo and say:

    Read AGENTS.md completely, then look at PIPELINE.md and tell me my most urgent follow-ups.

Directory Structure

job-search-os/
├── setup.py               # Interactive first-run setup
├── AGENTS.md              # Agent protocols: logging, pipeline upkeep, follow-ups
├── GOALS.md               # Target roles, positioning, and priority rules
├── PIPELINE.md            # Live status board — every active track at a glance
├── radar.config.yml       # Radar's only config: geography, roles, boards, evidence ledger
├── Companies/             # One file per company: the source of truth
├── Radar/                 # One dated record per scan — the inbound review queue
│                          #   (ships with one example day; delete it after your first run)
├── Interview Prep/        # Story bank and question prep that work across companies
├── Resumes/               # Baselines, fork specs, and tailored resume tracking
├── data/                  # Radar state: the seen-set and the candidate store
├── .github/workflows/     # Daily Radar run
└── tools/
    ├── job_search_os.py   # One CLI entry point for everything below
    ├── pipeline_board.py  # Generates the visual pipeline dashboard
    ├── resume_fork.py     # Forks a baseline resume via the Google Docs API
    └── radar/             # Providers, filters, scoring, integrity gates, triage

data/radar-seen.tsv is what stops a role reappearing every day, so it must persist between runs — commit it, or the queue repeats itself.

Core Workflows

Triage the day's roles

Open the newest file in Radar/, scan the table, click through to a role, and tick Promote or Dismiss. Then python3 tools/job_search_os.py radar process --write (or ask your agent to process the day). Promoting creates the company file, updates the pipeline, and writes a resume manifest — from there it is an ordinary track.

Log a conversation

After any call, email, or interview, tell your agent what happened. It appends a dated entry to the company's Conversation Log, extracts follow-ups as checkboxes with owners and due dates, updates the frontmatter (last_contact, next_action, next_action_date), and refreshes the generated PIPELINE.md.

Surface follow-ups

Ask "what are my job search follow-ups?" The agent scans every company file for open checkboxes and overdue next_action_dates, groups them by company, and flags anything that has gone quiet. python3 tools/job_search_os.py report --write creates the same view as Markdown.

Tailor a resume

Keep a baseline per track and fork a copy per company. python3 tools/job_search_os.py resume-manifest --company "Acme Robotics" --write creates a reviewable edit manifest before you touch the final document.

If your baselines live in Google Docs, resume-fork does the fork itself — copying the baseline so every style survives, then swapping text with the Docs API and verifying each replacement changed exactly one occurrence. Resumes/README.md covers the spec format, credentials, and why this is safer than driving Find & Replace by hand.

For an agent workflow that maps the job description to verified candidate evidence, proposes each material change for approval, and checks the rendered result, use the companion Generate Resume skill. Job Search OS owns role and pipeline state; Generate Resume owns the evidence-backed resume transformation. The skill writes the final document link or path back to the company record's resume_ref, then Job Search OS regenerates the pipeline.

Close out a track

When a company rejects, ghosts, or you withdraw: set the company file's status, add a Status Log entry with the outcome, and move the pipeline row to the ## Closed section. History is preserved; the live board stays clean.

Design Principles

  • Markdown is the database. Everything is portable, diffable, and human-readable.
  • One canonical location per fact. Company files own company facts; the pipeline is a view.
  • Agents operate under written protocols, not improvisation.
  • Follow-up discipline is the whole game — the system exists to make dropped loops visible.
  • Dashboards are generated artifacts, never sources of truth.
  • Private data belongs only in a private instance.
  • Drafts are review-only. The tooling never sends messages or submits applications.
  • Discovery is deterministic first. Filtering and deduplication are free and reproducible; a model only ever sees what survived, and never overrides a fact it cannot know.
  • Every automated edit is verified. A step that silently did nothing is the failure mode worth engineering against, so the tooling checks its own work and aborts loudly.

Related

Job Search OS is a companion to Personal OS, a general-purpose Markdown knowledge system for humans and AI agents. They share the same philosophy: shared context, durable memory, and auditable workflows. Run them separately or drop this structure into a Personal OS vault as a subsystem.

Generate Resume is the optional resume-tailoring companion. It can run independently with any baseline and evidence bank, or consume the company context and resume manifest produced here.

License

This work is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

You may share and adapt it with attribution for noncommercial purposes. Adaptations must use the same license.

Copyright © 2026 [Your Name].

About

A Markdown-based operating system for running a job search with AI agents — company files as the source of truth, a live pipeline board, and a visual Kanban dashboard.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages