jobseek-label-daily
GitHub自动化采集并标注每日职位数据,通过编排Codex子代理执行HTML清洗、分块与提取,经QA验证后上传至HuggingFace数据集。
触发场景
安装
npx skills add colophon-group/jobseek --skill jobseek-label-daily -g -y
SKILL.md
Frontmatter
{
"name": "jobseek-label-daily",
"description": "Run Jobseek's daily labelled-postings gold-dataset routine with Codex custom subagents and the existing labeller CLI. Use when asked to sample recent job postings, label postings for the HuggingFace dataset, run or debug the labelled-postings routine, or orchestrate the normalizer\/splitter\/extractor pipeline under apps\/crawler."
}
Jobseek Label Daily
Overview
This routine samples public job postings from the last 24 hours, labels them with Codex project custom agents, validates the outputs, merges accepted records, and uploads accepted gold data to viktoroo/jobseek-postings-labelled.
The Python labeller code is deterministic orchestration only: database reads, Jinja task rendering, JSON Schema/custom validation, merge, QA, and HuggingFace upload. Do not add provider SDK calls or call OpenAI or Anthropic endpoints from apps/crawler/src/labeller. LLM judgment happens through the already-running, subscription-backed Codex session and project custom agents configured in the active harness; the repo mirrors durable subagent contracts under .agents/labeller/.
Agents
Use these project custom agents:
jobseek-labeller-normalizer: GPT-5.6 Luna at low reasoning; raw HTML task input ->normalized.html.jobseek-labeller-splitter: GPT-5.6 Luna at medium reasoning;split_sectionstask input ->split-out.json.jobseek-labeller-extractor: GPT-5.6 Terra at high reasoning;extract_alltask input ->extract-all-out.json.
The committed .codex/agents/*.toml files pin these settings. Do not let the
labeller agents inherit the Sol/high orchestrator default: normalization and
splitting are clear, repeatable tasks, while combined extraction needs more
semantic judgment without requiring the flagship model.
Each agent invocation message must be exactly:
INPUT: <rendered-input-path>
OUTPUT: <output-path>
The Jinja task prompts in apps/crawler/src/labeller/prompts/tasks/ own task-specific rules and schema phrasing. Do not duplicate or rewrite those prompts in the orchestrator.
Workflow
Run commands from apps/crawler with uv run labeller .... Default RUN_DATE is today; default accepted-record target is 10 unless the user provides overrides. Start with SAMPLE_SIZE=10; if QA rejections leave fewer than 10 accepted local records for the date, sample additional candidates and continue until the accepted target is met or the sampling pool is exhausted.
- Sample postings:
uv run labeller sample --date "$RUN_DATE" --count "$SAMPLE_SIZE" \
--out "data/postings-labelled/_runs/$RUN_DATE/sample.json"
-
For each sampled posting ID, set
RUN_DIR=data/postings-labelled/_runs/$RUN_DATE/$POSTING_ID. -
Prepare raw input:
uv run labeller prepare-pre-llm "$POSTING_ID" --date "$RUN_DATE"
Skip the posting if the command exits non-zero.
- Render and run normalization:
uv run labeller render-task --task normalize_html \
--input "$RUN_DIR/raw_input.json" \
--out "$RUN_DIR/normalize-in.md" \
--output-path "$RUN_DIR/normalized.html"
Invoke jobseek-labeller-normalizer with the two-line INPUT/OUTPUT message. Then check that normalized.html exists, is non-empty, and starts with <.
- Finalize the deterministic input:
uv run labeller prepare-post-llm "$POSTING_ID" --date "$RUN_DATE"
Skip the posting if coverage validation fails.
- Render, run, and validate section splitting:
uv run labeller render-task --task split_sections \
--input "$RUN_DIR/input.json" \
--out "$RUN_DIR/split-in.md" \
--output-path "$RUN_DIR/split-out.json"
Invoke jobseek-labeller-splitter, then validate:
uv run labeller validate --kind sections \
--file "$RUN_DIR/split-out.json" \
--context "$RUN_DIR/input.json"
On validation failure, re-render with --previous-error "<validator output>" and retry the same agent up to two times.
- Render, run, and validate combined extraction:
uv run labeller render-task --task extract_all \
--input "$RUN_DIR/input.json" \
--sections "$RUN_DIR/split-out.json" \
--out "$RUN_DIR/extract-all-in.md" \
--output-path "$RUN_DIR/extract-all-out.json"
Invoke jobseek-labeller-extractor, then validate:
uv run labeller validate --kind extract_all \
--file "$RUN_DIR/extract-all-out.json" \
--context "$RUN_DIR/input.json"
Use the same two-retry protocol on validation failure.
- Merge and validate the posting:
uv run labeller merge --posting "$POSTING_ID" --date "$RUN_DATE" \
--verdict accepted \
--out "data/postings-labelled/postings/$RUN_DATE/$POSTING_ID.json"
uv run labeller validate --kind posting \
--file "data/postings-labelled/postings/$RUN_DATE/$POSTING_ID.json"
uv run labeller validate --kind qa \
--file "data/postings-labelled/postings/$RUN_DATE/$POSTING_ID.json" \
--report "$RUN_DIR/qa.json"
If merge or final validation cannot be repaired, use labeller merge --verdict rejected --rationale "<reason>" rather than hand-crafting a record. If QA fails after a valid merge, update only labelling_meta.qa_verdict and labelling_meta.qa_rationale from the QA report.
- Upload accepted postings only when the user asked to run the routine, not during surface verification:
uv run labeller upload --date "$RUN_DATE"
Use --dry-run for upload verification.
- End the final response with exactly one single-line runner outcome:
JOBSEEK_ROUTINE_RESULT={"status":"completed","phase":"upload-verification","primary_error":null}
For a fail-closed run, use status: "failed", name the first failing phase,
and put a concise redacted description of the first causal error in
primary_error. A missing sample artifact or remote date partition is a
downstream consequence when an earlier database/model/validation command
already failed, so it must not replace that causal error. Emit the marker even
when the runner was invoked manually; the Hetzner runner consumes it from the
Codex JSONL trace.
Legacy Fallback
The older per-section extractors (extract_team, extract_role, extract_requirements, extract_preferred, extract_benefits, extract_globals) remain available for compatibility. Prefer extract_all unless the user explicitly asks for granular retries or debugging of a specific section kind.
Focused Verification
For changes to this orchestration surface, run:
git status --short
uv run --with 'pyyaml>=6' python /Users/Viktor/.codex/skills/.system/skill-creator/scripts/quick_validate.py .agents/skills/jobseek-label-daily
python3 - <<'PY'
import pathlib
expected = {
".agents/labeller/normalizer.md",
".agents/labeller/splitter.md",
".agents/labeller/extractor.md",
}
missing = [path for path in expected if not pathlib.Path(path).exists()]
assert not missing, missing
print("labeller agent contracts present")
PY
legacy_terms="$(printf '%s' 'son' 'net|op' 'us|Agent[(]|[.]Codex|Claude Code ' 'session|Anthropic ' 'API')"
rg -n "$legacy_terms" .codex .agents .claude || true
cd apps/crawler && uv run labeller --help
Do not run expensive uploads as verification.
版本历史
- f7b7bdf 当前 2026-08-04 18:49
- 7517cfd 2026-08-02 21:45


