agentsclimarketplace

Job search runbook

Skill agent-data/job-search/skills/job-search-runbook

Not user-facing; job-search takes user questions. Mechanics the job-search skills read before a run: the workspace-discovery and validate-workspace scripts, the file table, how a run opens and closes.From its SKILL.md

Install
npx -y skills add agent-data/job-search --skill job-search-runbook

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

9.2 KB, as published. Nobody here has run it

job-search-runbook — workspace, run contract, scratch

The mechanics every flow needs: where the user's workspace is, what each file in it holds, how one run opens and closes, and what stays off disk.

Find the workspace

Every path below that names a file in the user's workspace is relative to the workspace this step prints. The few that name a file inside the plugin instead say so where they are written. Run the plugin's skills/job-search-runbook/scripts/workspace-discovery.sh.

It prints three key=value lines on stdout:

workspace=/Users/dana/job-search
source=legacy
first_run=false

It writes one more line to stderr, naming the registry path and saying what it found there, so a terminal or a tool result that merges the two streams shows four lines. Read it together with the parse-check rule at the end of this section.

first_run=true means that path has no config.yaml yet and setup creates it. source=legacy means the workspace sits at the older visible path: keep using it, and record it in the registry as active_workspace so later runs find it. An existing config.yaml, preferences.md, or jobs.jsonl stays as it is — adopting a workspace adds only the missing runs/ and reports/ directories.

The registry file — the one to read here and to write for adoption or scheduling — is $JOBSEARCH_OS_REGISTRY when set, otherwise ${XDG_CONFIG_HOME:-${JOBSEARCH_OS_HOME:-$HOME}/.config}/job-search/config.json.

Where no shell runs, read that file and apply the same order yourself. In the rules below, $H is $JOBSEARCH_OS_HOME when set and $HOME otherwise (tests and evals set it to move the home base). First match decides:

  1. The registry parses as JSON and holds a non-empty active_workspace — use it and stop, even with no config.yaml there (that is a first run); falling through could move the user elsewhere.
  2. $H/.job-search/config.yaml exists — workspace $H/.job-search.
  3. $H/job-search/config.yaml exists — workspace $H/job-search.
  4. Neither exists — first run, workspace $H/.job-search, which setup creates.

A registry file that exists but does not parse as JSON stops the run: report that the file at that path cannot be read, rather than picking a workspace it might not name. A registry the script could not look at stops the run the same way, and the workspace on stdout came from the rules below rather than from the registry.

What each file holds

FileWritten byRead by
config.yaml — queries, sources, schedulesetup, then the user by handevery run, the home view
preferences.md — the brief, in prosethe preference interviewevery run, every fit judgment
jobs.jsonl — append-only event log, one JSON object per line. A run writes a call event for each agent-data request and surfaced, queued, detail and evaluated events about postings. A posting has several lines, all carrying the same source and source_id, and its current state is its last evaluated line.every runthe home view, duplicate checks
runs/<run_id>.json — one record per run; <run_id> is the run's UTC start time with dashes for the colons, like 2026-07-30T15-04-02Z, so it works as a filenameskills/job-search-runbook/scripts/close-run.shthe home view, the agent skill
runs/.started-<run_id> — empty marker: this run is openskills/job-search-runbook/scripts/open-run.sh, deleted by skills/job-search-runbook/scripts/clear-run.shthe next run
reports/<date>-digest.md — the digest the user readsa run at closethe user, the home view
.gitignore — copied from the plugin's skills/job-search/templates/workspace.gitignore; denies everything but itselfsetupgit
~/.config/job-search/config.json — the registry, which sits outside the workspace: active_workspace, plus scheduling holding the booleans installed and verified — both true only after a canary proved the job — and the strings mechanism (cron, launchd, or the harness's own) and scheduler_idsetup, schedule changesdiscovery, the home view

One run, start to close

  1. Look for a leftover marker — ls <workspace>/runs/.started-* 2>/dev/null. A marker there belongs to a run that stopped before it could close. Say that the last run did not finish, then close and clear it before going on with this run: first skills/job-search-runbook/scripts/close-run.sh <workspace> <run_id> --trigger manual --close-state interrupted, then skills/job-search-runbook/scripts/clear-run.sh <workspace> <run_id>, taking <run_id> off the marker's name. Pass manual because nothing on disk records what started a run that never closed — trigger is written only into the run record, which is the file that run never wrote — and scheduled is what the scheduling canary reads as proof the scheduler ran. Deleting the marker instead leaves nothing recording that the run happened, which is why skills/job-search-runbook/scripts/clear-run.sh refuses a run with no record.
  2. Open the run — run the plugin's skills/job-search-runbook/scripts/open-run.sh <workspace>. It prints run_id, started_at and brief_revision on stdout, in that order; carry all three through the run. Exit 1 means the run is open — the marker is on disk and those three lines are printed — and something is wrong that this run cannot fix: read stdout after the three lines for the findings about the workspace files, and stderr for the case where the brief's revision could not be taken. Report whichever you got in plain language and close the run blocked. Exit 2 means the run did not open and nothing was written; stderr says why. A run already open means an earlier run's marker is still in runs/: go back to step 1, which says what to tell the user, then open this one.
  3. Do the run's work, recording events as you go.
  4. Close, in this order. First skills/job-search-runbook/scripts/close-run.sh <workspace> <run_id> --trigger … --close-state …, with --brief-revision from step 2, --sources and --queries from the run, and --scheduler-id when a scheduler started it. It works out run_health and prints it. Then the digest, which the run skill defines. Then skills/job-search-runbook/scripts/clear-run.sh <workspace> <run_id>, which deletes the marker and the scratch directory. Then skills/job-search-runbook/scripts/validate-workspace.sh <workspace> --post-close <run_id>, fixing whatever it prints on stdout.

The digest is written between the record and the clearing because it carries run_health off the record and may still need the responses in the scratch directory.

trigger, scheduler_id and close_state are the only values in the record you decide. Every count and both timestamps come from jobs.jsonl and the clock.

A run that has to stop early closes the same way: close_state is blocked when you can name what stopped it, and interrupted when it ends unfinished and its work cannot be reconstructed.

Running it unattended

A scheduler starts a run by invoking this pack's job-search-run skill through the harness's non-interactive command instead of a live session. Compose the command from what your harness documents: its non-interactive runner (the subcommand or flag that takes a prompt without a terminal), the permissions that let the run write the workspace and call the agent-data CLI with nobody watching, and the name your harness lists the job-search-run skill under — harnesses differ on whether the pack's name prefixes it. Write into the job's prompt that a scheduler started it and that job's id, so the record closes with trigger: scheduled and that scheduler_id. A harness with no non-interactive command has nothing for a schedule to start: say so, and an in-session loop is the fallback.

A cron or launchd process has no login session, so the job authenticates the way your harness's documentation says a non-interactive run authenticates — an environment variable the job's definition carries, a stored token, or the same store the session uses. Put the credential where that documentation says it lives, and nowhere in the workspace. Whether the credentials actually reach a scheduled process is settled by the job-search skill's canary. Judge the scheduled run by its record rather than the process exit code: harnesses differ on whether that code reflects the run.

Scratch

A run's own working files go in runs/.scratch/<run_id>/: each search row as it arrived, id and source_url paired for the detail read.

What stays off disk

The workspace holds a private job search. Write none of these to a file: API keys and auth headers, pagination cursors, preference text anywhere but preferences.md. A posting's own text is not on that list: it is stored on the posting's detail event, so a changed brief can be re-applied by judging that stored text again instead of paying to read the same postings a second time.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.