skill
job-matcher-skill
Match your CV to live job postings and generate an interactive HTML report. Agent-native skill for Claude Code & Codex (WebSearch + subagents, zero-dependency).
Set up
Skills are installed by placing their folder where your agent loads skills.
Install instructions come from the README
README
synced Oct 14, 2026job-matcher
English | 中文
版本文档:更新记录 · v2.4.0 发布说明 · v2.3.0 发布说明 · v2.2.0 发布说明 · v2.1.0 发布说明 · v2.0.0 发布说明
一个 agent skill(Claude Code 与 Codex 通用):输入简历(CV) + 求职意向,自动抽取简历字段、组合使用模型/Web 搜索与 BrowserOS Neo 或用户浏览器检索匹配职位,生成一份可交互的 HTML 报告。
是 JobRadar 的轻量版——默认同时使用模型搜索和可用的本机浏览器;浏览器提供者优先选择 Agent 已连接的 BrowserOS Neo,再降级到已授权用户浏览器,也可选接入 BYOK 隔离浏览器作为抓取兜底。
✨ 功能
- 📄 简历解析:支持 PDF / DOCX / TXT / MD,或直接粘贴文本(不做 OCR)。
- 🧠 结构化抽取:抽取目标职位、技能、资历(seniority)、地点、语言等,自动按相关年限定级。
- 🌍 全球地区:6 个精选市场(ie / uk / cn / de / us / nl,带本地招聘站与城市目录)+ 212 个通用市场(
geo_countries.json中的每个国家/地区,含主要城市别名)。通用市场经 ATS 结构化通道与 Web 搜索覆盖;职位最多的 10 个通用市场(印度、加拿大、法国、新加坡、日本、西班牙、澳大利亚、波兰、巴西、意大利)另有已验证的本地招聘站,浏览器通道也可用;地点无法识别时仍会停下追问。 - 🗣️ 按市场语言搜索与初筛:法国用法语、日本用日语、巴西用葡语、西语国家用西语、韩国用韩语、意大利用意大利语、波兰用波兰语,荷兰与比利时加上荷兰语,并保留英语;当地语言的职位标题与资历词(Sénior、Pleno、シニア、Stage…)都能识别。词表外的角色由 agent 翻译后经
localized_roles进入搜索和初筛。地点也按各语言写法识别(Varsovie、Múnich、ワルシャワ、밀라노、Monachium),带不带重音都行。报告界面支持同样的 11 种语言。 - 🔎 实时职位检索:默认并行运行浏览器平台搜索与模型/Web 搜索;浏览器按 Neo → 用户浏览器选择提供者,平台由地区来源目录动态规划,不在代码中硬编码站点 selector。
- 🎯 5 维匹配打分:title / seniority / skills / location / must-have,输出五档投递建议(强烈投递→跳过)。资历硬规则与契约校验拦截打分漂移。
- 🗂️ 增量缓存:CV、JD、匹配分三层缓存;多来源同职位自动聚合(含区域平台 job-id 强命中);换 query 自动失效重算。
- 🛡️ 不可信输入隔离:搜索结果与 JD 正文按纯数据处理并忽略其中指令;报告内嵌 JSON 转义、链接限 http(s)。
- 📊 可交互报告:两栏布局(左职位列表 30% + 右详情 70%)+ 评分徽章 + 深色模式 + 排序/搜索 + 市场/来源/验证状态筛选 + 多来源发现证据 + 7/30 天运行健康快照 + 界面支持中/英/德/法/西/葡/日(随
report_language,页内可切换),自包含单文件 HTML。 - 🌐 可选隔离浏览器:Kernel BYOK 作为最终抓取兜底,支持受限列表翻页、视觉控制和 Live View 人工接管;默认关闭,CI 使用 Fake Provider。
🏗️ 架构
- 主 agent = 编排者:调脚本、融合 query、追问用户、spawn subagent。
- subagent 承担重上下文工作(CV 抽取 / 搜索 / 打分):大块原始文本留在 subagent,主上下文只搬「路径 + 小 JSON」,保持整洁。
- Python 脚本承担确定性工作:解析、校验、去重聚合缓存、失效验证、渲染。
- 并行计算、串行提交:第 N 批评估与第 N+1 批搜索并行发出(批间重叠);
jobs_table.json只有一个写入路径,使用评估快照、跨进程锁和原子替换防止丢失更新。超龄未完成的快照会在下次 merge 时作废回收,职位不会永久卡在评估中。
CV + query
│ [脚本] extract_cv → 纯文本 + cv_hash
│ [缓存检查] → 命中则跳过抽取
│ [subagent] 抽取 CVProfile → [脚本] validate_profile
│ [主agent] 融合 query → search_plan + candidate_profile
│ [发现计划] 浏览器平台+模型/Web+可选ATS → [脚本] merge_jobs(去重/聚合/缓存+评估快照)
│ [并行 subagent] 粗排→精排抓JD+5维打分 + 失效验证 → [脚本] 条件化回写
│ [脚本] render_html → report.html + report_data.js(自动打开)
▼
可交互 HTML 报告
容错阶梯(失效验证 & JD 抓取共用):WebFetch → requests 静态抓 → 本机 headless → 可选远程隔离浏览器 → 标注未验证不阻塞。
📁 结构
job-matcher/
├── SKILL.md # 触发描述 + 本运行时的能力映射
├── WORKFLOW.md # agent-中立完整流程(只写做什么,每轮都读)
├── config.json # 配置旋钮
├── docs/rationale.md # 每条编号规则的理由与实测(只在改规则/排查时读)
├── docs/ats-protocol.md # ATS/结构化通道协议(由脚本执行)
├── docs/remote-browser-protocol.md # Kernel 远程浏览器(默认关闭)
├── docs/phase-c-legacy.md # 已被取代的双 route 入口
├── docs/shadow-and-smoke.md # Phase D2 smoke 与 Phase E shadow 门
├── docs/monitoring.md # 运行指标、阈值与健康汇总
├── docs/roadmap.md # 版本更新期望:已知缺口与下一版验收标准
├── tools/ # 门禁与基准;正常一轮一条都不跑
│ ├── _bootstrap.py # 把 scripts/ 放上 import 路径(工具测的就是生产模块)
│ ├── browser_candidate_smoke.py # 临时 store 验证浏览器候选与 merge,不污染正式数据
│ ├── multi_region_smoke.py # 显式、计数型每市场一槽的公开来源 smoke
│ ├── global_coverage_smoke.py # 显式、计数型:注册表 ATS board 实际覆盖哪些国家(精选 vs 加通用市场)
│ ├── shadow_gate.py # Phase E 幂等 shadow 台账与逐市场发布门
│ ├── shadow_compare.py # 只读计算增量、交集、JD 与潜在 Top-N
│ ├── benchmark_pipeline.py # 固定小数据集核心/Fake Provider 基准
│ ├── benchmark_ats.py # 复用生产适配器的公开 ATS 有界回归
│ ├── benchmark_ats_e2e.py # 固定 Web 对照组与 Web+ATS 受控 A/B
│ ├── benchmark_ats_quality.py # 三供应商 JD/评分质量审计
│ └── benchmark_ats_compression.py # ATS HTTP 压缩交错 A/B
├── references/ # subagent 按需读取的指令
│ ├── cv_schema.md # CV 抽取规则
│ ├── scoring_rubric.md # 5 维打分 + 五档阈值
│ ├── search_playbook.md # fan-out / 分市场 / 自适应分批
│ ├── markets.json # 精选市场的城市、语言与 query 模板
│ ├── geo_countries.json # 通用市场:精选市场以外的每个国家/地区,含主要城市、搜索语言、行政区
│ ├── role_taxonomy.json # 角色族:每种语言的标题/同义词(搜索与初筛共用)与技能门槛泛化边
│ ├── languages.json # 可搜索语言:代码、别名、Web 搜索模板(语言集以本文件为准)
│ ├── model_tiers.json # 型号 ↔ 档位映射,供子代理按 min_tier 选型
│ ├── source_seeds.json # 已验证的公开来源种子(不含凭据);市场由 markets.json 决定
│ ├── multi_region_smoke_plan.json # Phase D2 固定来源与请求硬上限
│ ├── shadow_run.schema.json # Phase E 计数型 shadow 证据契约
│ ├── shadow_compare.schema.json # Phase E 临时候选对照输入契约
│ ├── shadow_run_v2.schema.json # Phase E shadow 证据契约 v2
│ ├── shadow_compare_v2.schema.json # Phase E 候选对照输入契约 v2
│ ├── candidate_envelope.schema.json # Phase C 发现候选契约
│ ├── ats_phase1_boards.json # ATS 小基线公开公司样本
│ └── ats_phase5_quality_boards.json # 三供应商质量小样本
├── scripts/ # 确定性 Python 脚本
│ ├── extract_cv.py # 解析 CV → 文本 + hash
│ ├── validate_profile.py # 校验 + seniority→levels 映射
│ ├── market_plan.py # 校验市场资源并生成确定性多地区计划
│ ├── discovery_mode.py # 只读能力状态的发现模式选择与安全降级
│ ├── discovery_plan.py # 连接市场/健康/来源目录并生成多通道执行任务
│ ├── discovery_batch.py # 校验所有任务结果,一次 merge 并决定是否扩展下一批
│ ├── local_browser_probe.py # 校验 Agent 已观察到的本机浏览器工具面
│ ├── local_browser_panel.py # localhost 设置、状态与闪烁人工提醒面板
│ ├── cookie_consent.py # accessibility Cookie 语义分类;仅必要或暂停
│ ├── source_registry.py # 来源种子校验、健康状态、迁移与确定性来源计划
│ ├── candidate_contract.py # 严格校验 Phase C CandidateEnvelope
│ ├── candidate_handoff.py # 【已弃用】旧双 route 入口,被 discovery_batch.py 取代
│ ├── analysis_contract.py # 校验 JDProfile/MatchScore worker 输出
│ ├── merge_jobs.py # 单写入器:去重/缓存/评估快照/条件化回写
│ ├── runtime_metrics.py # PII-safe JSONL 指标与健康计算
│ ├── search_metrics.py # Web Search 页级计数与耗时指标
│ ├── summarize_metrics.py # 7/30 天 Markdown/JSON 健康报告
│ ├── round_timer.py # 整轮计时,按编排模式对比墙钟
│ ├── subagent_metrics.py # 按档位为子代理选型号、记录实际执行指标
│ ├── browser_provider.py # Kernel/Fake Provider 与安全配置
│ ├── browser_control.py # 远程视觉浏览器控制命令;本机浏览器动作的指标自报路径
│ ├── browser_setup.py # 一次性 localhost 配置页面
│ ├── browser_workflow.py # 列表翻页/暂停状态机
│ ├── ats_provider.py # Ashby/Greenhouse/Lever/Workday + amazon.jobs 公开只读适配器与 Fake
│ ├── job_prefilter.py # 角色/地点/资历确定性初筛,三个通道共用一份规则
│ ├── rejected_log.py # 被初筛拒过的身份标记(只用于统计重复,从不用来跳过初筛)
│ ├── ats_pipeline.py # ATS 标识库、同步与候选归一化
│ ├── ats_handoff.py # ATS 正文内存直送统一 merge,避免主上下文暴露
│ ├── source_reverify.py # 过期网页来源续期:查 robots 后 GET 入口页一次,经注册表事件写回
│ ├── board_harvest.py # 从职位 URL 反推 ATS board,复验后写入来源注册表
│ ├── seed_promotion.py # 把已复验的采集来源提升进版本控制的种子目录
│ ├── cp_hash.py # 稳定的 candidate_profile hash
│ ├── verify_jobs.py # 失效职位状态码检测
│ ├── check_robots.py # 按 RFC 9309 读 robots.txt,回答某个 URL 站点是否允许抓取
│ ├── fetch_rendered.py # headless 渲染兜底(复用系统浏览器)
│ ├── render_html.py # 渲染 HTML 报告
│ ├── _jobutil.py # 共享:归一化/去重键/URL 规范化
│ ├── _filelock.py # 共享:跨进程独占文件锁(所有本地存储)
│ └── _stdio.py # 共享:stdout/stderr 钉成 UTF-8;stdin 读取有上限,不会无限等待
├── assets/template.html # 静态报告模板(Tailwind + 纯 JS),原样复制为 report.html
├── assets/report_strings.json # 报告界面文字,每种语言一张表,每轮写入 report_data.js
└── data/ # 运行时数据(.gitignore,含 PII)
多地区 Phase A/B/C 目前仍是显式入口:python scripts/market_plan.py validate
校验版本化市场/角色配置,python scripts/market_plan.py plan 从 stdin 接收
{cv_profile,user_intent} 并生成 markets.json 所列市场的市场、语言、地点与
Web query 计划,同时报告 target_roles 与 roles_source;
python scripts/market_plan.py effective-profile 吃同一份输入,输出把已解析角色写入
roles 键的 profile——确定性初筛读的就是这一份,不经过它就会出现「按用户意图去搜、
按 CV 角色去筛」的两套答案。python scripts/source_registry.py validate 校验公开来源种子;init 在
data/source_registry.json 原子合并种子,并在旧 data/ats_companies.json 存在时只读迁移;
plan --markets ie uk 只返回 enabled + verified + TTL 未过期 的确定性来源(结构化来源过期仍列入,抓取即复验;网页来源由 source_reverify.py 续期),每个全球来源
只出现一次。它们都不执行搜索或修改职位表,因此旧单地区 Web Search/ATS/merge/报告流程
保持兼容。Phase C 编排者可让 regional_registry 与 agent_web_search 同时开始,待两条 route
都回报成功、失败或跳过后,把同一 batch_id 的严格 CandidateEnvelope 交给
candidate_handoff.py;它先经唯一 merge_jobs.py 写职位与评估快照,再幂等提交来源状态。
契约与回滚见 docs/multi-region-phase0-baseline.md、
docs/source-registry-phase-b.md 和
docs/multi-region-phase-c.md。
Phase D1 的离线报告契约见 docs/multi-region-phase-d1.md。
Phase D2 的有界公开来源 smoke、隐私边界与本次证据见
docs/multi-region-phase-d2.md;它必须显式传
--live,不会进入默认 CI 或生产发现路径。
Phase E 的 count-only shadow 契约、逐市场门槛和当前未达标状态见
docs/multi-region-phase-e.md。
multi_region_enabled 默认仍是 false;因此 Phase C 不会自动替换现有单地区流程。
发现模式 Phase 1+2 提供 discovery_mode.py plan|event 与
local_browser_probe.py probe 的确定性契约。默认 coverage 同时运行模型/Web 搜索与一个
已连接、已授权且可执行的浏览器提供者,浏览器内部按 BrowserOS Neo → 用户浏览器选择;旧
auto 保留单 route 兼容行为,browser_only 绝不静默改用模型搜索。discovery_plan.py 再把
market plan、URL-free 健康计划和公开来源目录连接成有界 browser/Web/structured 任务。探测基于
Agent 已暴露的工具面,不扫描端口、浏览器配置、Cookie 或现有标签页;实际发现使用专用标签页,
候选仍进入同一 CandidateEnvelope/merge 主表。详见
docs/discovery-mode-phase1.md 与
docs/local-browser-phase2.md。浏览器 route 可运行
python scripts/local_browser_panel.py serve 打开 loopback 控制面板:它只保存模式和低基数状态,
登录/验证/consent/限流时闪烁并等待用户恢复;详细契约见
docs/local-browser-phase3-panel.md。现有 Kernel 远程浏览器仍只是
单独的可选抓取兜底,不会代替用户本机的登录会话。
DiscoveryPlan 把跨通道任务分成确定性波次;Agent 先只执行 initial_wave_id。每个当前波次 task
完成后,discovery_batch.py 要求其恰有一个终态,校验候选与 task 的
route/source/market/language 一致性,再把该波次所有通道候选一次性交给现有单写入 merge。
它的脱敏 manifest 支持中断恢复,并按新增唯一候选、计划内剩余波次和现有停止阈值决定是否返回
next_wave_id;调用方不能自行覆盖。该决策只控制发现扩展,不等同于 JD 完整或 CV 匹配合格。
2026-09-21 的有界运行时 smoke 已分别验证 BrowserOS Neo MCP 主路径和用户 Chrome 降级路径的
专用标签页 create/navigate/read/close;当 Neo 可用时浏览器提供者会选择 Neo。LinkedIn 实测还验证了
登录暂停、面板提醒/恢复、登录态复用和同标签页受限搜索;后续单候选 smoke 已通过受限主区域读取、
详情存活验证、CandidateEnvelope 和临时 merge;三候选同页批次也完成 3/3 provenance 保留。
2026-09-22 又完成了一次真实 CV、Dublin-first Ireland 的三波生产运行:12 个任务全部到达终态,
两个强身份候选进入正式 merge、JD 评分和 HTML 报告。该运行同时发现,Neo MCP 直接操作尚未写入
仓库 browser 指标,因此报告健康状态会正确显示 unknown;站点 host alias、歧义 consent、
自定义 combobox、跨页翻页、CAPTCHA 和限流恢复仍是 gate。证据边界见
docs/local-browser-phase4-smoke.md。确定性首波的 Neo + Web Search
联合 smoke、consent 暂停、三波继续/停止决策以及临时单写入 merge 的证据见
docs/discovery-wave-live-smoke.md;完整生产试运行见
docs/browseros-neo-production-trial-2026-09-22.md。
脚本分工
scripts/ 共 19,900 行,不是一条路径。改动前先看清一个脚本属于哪一类:
| 类别 | 行数 | 是什么 |
|---|---|---|
| 关键路径 | ~13,700 (69%) | 一轮匹配真正会跑的:CV 解析、market/discovery 规划、来源注册表、初筛、ATS 拉取、merge/update、渲染、指标 |
| 门禁与基准 | ~4,300 (22%) | benchmark_*(公开小样本回归)、shadow_* 与 multi_region_smoke(发布门禁)、browser_candidate_smoke(首次接入验证)。正常一轮不跑,改解析器或提版本时才跑 |
| 运维 | ~1,900 (9%) | 本机面板、浏览器配置、种子提升、board 采集、版本检查、指标汇总 |
弃用中:candidate_handoff.py(旧双 route 入口,已被 discovery_plan.py + discovery_batch.py 取代,输出带 deprecated: true)、search_metrics.py(只剩不经 discovery batch 的独立诊断用途)。
🚀 使用
克隆到个人 skill 目录(目录名用 job-matcher,与 skill 名一致):
# Claude Code
git clone https://github.com/sangowu/job-matcher-skill ~/.claude/skills/job-matcher
# Codex
git clone https://github.com/sangowu/job-matcher-skill ~/.agents/skills/job-matcher
agent 会自动识别。然后在对话里:
这是我的简历
D:\cv.pdf,帮我找远程后端职位
或直接粘贴简历文本 + 求职意向。skill 会走完整流程并在浏览器打开报告。
⚙️ 配置
config.json 集中所有旋钮:
| 键 | 默认 | 说明 |
|---|---|---|
top_n | 15 | 最终展示职位数 |
precise_buffer | 5 | 精排多抓缓冲 |
version_check_enabled | true | 启动时是否检查本地 skill 与 GitHub main 是否同步 |
version_check_interval_hours | 24 | GitHub 版本检查缓存时长;缓存期内不发网络请求 |
version_check_timeout_seconds | 3 | 单次只读 GitHub 请求超时秒数 |
max_parallel_subagents | 3 | 批内并行上限 |
subagent_profiles | 见配置 | 各角色的档位下限 min_tier、reasoning effort 与隔离上下文策略;可选 model 钉死型号 |
max_websearch_calls | 6 | WebSearch 总次数上限 |
discovery_mode | coverage | coverage 默认并行浏览器+模型搜索;auto 保留单 route 兼容;也支持 model_only、browser_only、combined |
cookie_consent_policy | necessary_only | 只在唯一明确语义按钮上自动拒绝可选 Cookie;也可设 ask_every_time |
discovery_max_waves | 3 | 单次计划最多生成的确定性发现波次数 |
browser_sources_per_market | 4 | 每个市场、每个波次的浏览器来源上限;浏览器首个波次内先保证来源类型多样性 |
browser_first_wave | 2 | 浏览器从第几个波次开始;默认让结构化通道先跑一波,浏览器退为兜底 |
web_first_wave | 3 | Web Search 从第几个波次开始;实测 22 次调用只换来 2 个新候选、最近五轮为 0,因此排在结构化与浏览器之后 |
browser_queries_per_source | 2 | 单个浏览器来源本轮执行的地区化查询上限 |
web_queries_per_market_per_wave | 2 | 每市场、每波次执行的 Web Search 查询上限 |
web_source_hints_per_task | 6 | 每条 Web Search 任务携带的公开来源提示上限 |
multi_region_enabled | false | 只管 Phase E shadow 门禁;只有 shadow_gate.py 读它,live 发现路径不读,因此它不是多市场总闸 |
multi_region_rollout | 已列出的市场均 off | 每市场独立设置 off / shadow / opt_in / default;default 必须通过 Phase E 门禁。同样只作用于 shadow 门禁 |
stop_threshold | 20 | 累计唯一候选达标即停(continuation.reason=target_reached)。计的是 merge 新增的唯一候选,发生在评分与 JD 补全之前;取 top_n + precise_buffer,使刚好触线停下的一轮仍够填满报告 |
consecutive_empty_stop | 2 | 连续 N 批 0 结果则停止 |
ats_enabled | true | 是否启用公开 ATS 增强管道 |
ats_defer_jd | true | 先读不含 JD 正文的职位列表,再只为本轮保留的候选各取一次正文;false 恢复每个 board 一次请求、正文随列表一起下载 |
ats_max_concurrency | 3 | 跨 ATS board 并发硬上限 |
ats_boards_per_round | 60 | 单轮每波次 board 数上限;超出的 board 记入 omitted_by_wave_budget.structured |
ats_requests_per_round | 100 | 单轮 ATS HTTP 请求硬上限 |
ats_page_size | 50 | Lever 每页请求数量 |
ats_max_pages | 10 | 单个 Lever board 顺序翻页硬上限 |
ats_timeout_seconds | 30 | 单次公开 ATS GET 超时秒数 |
ats_registry_ttl_days | 30 | unavailable board 的重试退避间隔 |
ats_fetch_interval_minutes | 60 | 已验证 board 再次取职位的最小间隔(分钟);0 表示每轮都取 |
jd_ttl_days | 30 | JD 缓存有效期 |
verify_ttl_hours | 24 | 链接有效性核对的复用时长;alive 且未过期的行不再复验 |
seniority_mode | balanced | strict / balanced / stretch |
enable_headless_fallback | true | headless 兜底开关 |
headless_budget | 3 | 每次运行 headless 上限 |
remote_browser_enabled | false | 是否启用远程隔离浏览器最终兜底 |
browser_provider | kernel | kernel;fake 仅供测试 |
browser_max_concurrency | 2 | 远程浏览器并发硬上限 |
browser_max_pages | 3 | 单个招聘列表串行翻页硬上限 |
browser_min_source_interval_ms | 5000 | 同一来源两次浏览器动作的最小间隔,只能调高;间隔不足的动作记为失败且不计入轮次完整性 |
browser_jitter_ms | 2000 | 在最小间隔之上叠加的随机等待上限,用于分散请求;只会让等待变长,不用于伪装流量 |
browser_max_requests_per_minute | 120 | 同一来源滚动 60 秒内的请求数上限,只能调低;这是站点实际感受到的单位——一次点击实测 14 个请求,所以动作间隔并不是请求速率上限 |
browser_assumed_requests_per_action | 10 | 未实测请求数的动作按这个数计费,只能调高;120 / 10 = 每分钟 12 个动作,正好等于 5000ms 的间隔,两条上限同时到顶 |
browser_session_budget | 10 | 单轮新建远程会话硬上限 |
browser_cost_limit_usd | 1.0 | 单轮估算费用硬上限(美元) |
browser_handoff_timeout_minutes | 10 | 人工接管等待硬上限(分钟) |
browser_allow_handoff | true | 是否允许通过临时 Live View 交给用户处理 |
browser_timeout_seconds | 600 | 单个远程会话超时硬上限 |
browser_headless | false | Provider 是否隐藏浏览器;默认保留可接管视图 |
browser_stealth | false | stealth 开关;默认关闭且不用于绕过验证 |
table_lock_timeout_seconds | 10 | 等待主表写锁的最长秒数 |
stale_lock_seconds | 120 | 回收异常遗留锁的时间阈值 |
eval_run_stale_hours | 2 | 作废未完成评估快照的时间阈值 |
monitoring_default_window_days | 7 | 默认健康报告窗口 |
monitoring_thresholds | 见配置 | 冲突、拒绝、成功率、锁等待和积压阈值 |
python scripts/version_check.py 会比较本地 pyproject.toml 版本、Git commit 与 GitHub main,结果缓存到已忽略的 data/version_check.json。它不读取或上传 CV/JD/搜索数据,不需要 GitHub token,也不会自动更新文件;离线、超时或限流只返回 unknown,不阻断职位流程。需要绕过缓存做只读诊断时使用 python scripts/version_check.py --force。
运行时只保留一个职位主表 data/jobs_table.json。record_id 是稳定记录/评估主键,identity_keys 保存平台职位 ID;公司 + 标题 dedup_key 仅作兼容弱匹配。两个不相交的强 ID 不会因同公司同标题而误合并,弱键匹配还要求地点兼容且结果唯一。旧表会在下一次 merge/update 时原位补齐身份字段。每轮待评估职位写入最小化快照 data/eval_runs/<run_id>.json;ATS 已返回 JD 时,正文只临时存在对应任务快照,主表只保存内容 hash,worker 可直接分析并跳过网页抓取。单个任务完成即清除正文,整轮完成后快照释放,仅在 history.jsonl 留下不含 CV/JD 正文的摘要。每次 merge/update 另写一条 PII-safe data/metrics.jsonl 事件。
远程浏览器为可选功能。安装依赖后运行一次性本地设置页;页面只绑定 127.0.0.1,连接测试成功后把 API Key 保存到系统密钥库,非敏感设置保存到已忽略的 data/browser_provider.json:
python -m pip install "kernel>=0.94,<1" keyring
python scripts/browser_setup.py
python scripts/browser_control.py test
无界面环境也可通过 KERNEL_API_KEY 提供密钥。控制脚本的 create/screenshot/click/type/press/scroll/close 供 browser 子代理进行视觉控制;Live View URL 只临时返回,不写入指标或文件。
📈 运行监控
python scripts/summarize_metrics.py --days 7 --format markdown
python scripts/summarize_metrics.py --days 30 --format json
python scripts/summarize_metrics.py --fail-on-breach
报告包含 run 完整性、Web Search 调用/有效候选、吞吐/缓存、评估成功/拒绝/冲突率、子代理实际模型/effort/token/成本覆盖、浏览器与 ATS 计数、命令与锁等待分位数及积压状态。阈值违规为 degraded;缺失必需事件时健康状态为 unknown,不会误报 healthy。CLI 的 --fail-on-breach 对两种情况都返回退出码 2。字段定义见 运行时监控文档,设计与后续 A/B 门槛见 run-scoped 指标契约。
整轮墙钟另行采集——脚本级耗时相比编排者在两次调用之间的搜索与评估工作可以忽略,无法回答「批间重叠值不值」:
python scripts/round_timer.py start # → {"run_id": "round-...", "round_id": "round-..."}
python scripts/search_metrics.py --ok --run-id <R> --query-slot q1 --duration-ms 120
python scripts/round_timer.py finish --round-id <R> --orchestration overlapped|serial --expect subagent
汇总按编排模式给出 p50/p95 与 overlap_saving_pct;两种模式都有样本前显示 n/a。
monitoring_thresholds.unfinished_run_age_minutes_max 控制未完成 run 何时被判为陈旧并令健康状态变为 unknown;默认 120 分钟。
版本性能回归使用固定 15 职位冷数据集和 10 个 Fake 会话:python tools/benchmark_pipeline.py --output <json> --baseline docs/performance/v2.2.0-small-baseline.json。输出同时包含原始迭代、p50/p95、绝对变化和相对变化;不会调用真实 Web Search 或云 Provider,并验证三家 Fake ATS 的 JD 均进入临时任务、主表零正文。强身份迁移基准见 docs/performance/strong-job-identity-baseline.md,三家 ATS 离线管道基准见 docs/performance/ats-phase2-fake-baseline.md,Phase 4 交接基准见 docs/performance/ats-phase4-jd-handoff.md,真实三条五维抽检见 docs/performance/ats-phase4-live-quality.md。固定 Web 候选对照组与受限真实 ATS 的 discovery-to-merge A/B 使用 python tools/benchmark_ats_e2e.py --web-candidates <json> --profile <json> --output <json>;它会发出公开 ATS 请求,必须显式提供本地输入并遵守生产硬上限。结果与限制见 docs/performance/ats-phase3-controlled-e2e.md。三供应商 JD 质量复核可先用 python tools/benchmark_ats_quality.py collect ... 创建不提交的本地样本,再用 audit 生成计数型门禁报告;本次小样本结果与限制见 docs/performance/ats-phase5-multiprovider-quality.md。ATS HTTP 压缩可用 python tools/benchmark_ats_compression.py --output <json> --pairs 3 做相同结果集的交错 A/B;本次三供应商实测中位传输量减少 79.31%,内容指纹、职位数与请求数均相同,详见 docs/performance/ats-http-compression-ab.md。
ATS Phase 2 的生产增强管道由 ats_enabled 控制(当前默认开启;是否派发 structured 任务由本轮市场在来源目录里有没有 structured 来源决定,false 只是关闭开关)。Web Search 或浏览器候选里的官方 ATS 职位 URL,由 discovery_batch.py 在每波合并后自动经 board_harvest 复验并登记进来源注册表,无需手动操作;不经 discovery batch 的旧路径仍可用 python scripts/ats_pipeline.py discover 写入本地标识库;Greenhouse 同时识别 job-boards.eu.greenhouse.io 的公开职位页,但公开 API 仍使用官方 boards-api.greenhouse.io。启用后用 sync --profile <cv-profile.json> 同步已到期 board,或用 run --profile ... 一次完成发现与同步。管道只做公开 GET,默认请求 gzip 并同时限制压缩响应与解压后正文大小,按标题/地点/资历确定性初筛;单独的 AI 产品/团队后缀不是有效岗位匹配,明确的 AI evaluation、AI systems、agent systems 等岗位短语才作为 AI 方向信号。Phase 4 会把 ATS 已提供的 JD 清洗并限制为 50,000 字符,通过同一 merge_jobs.py 的本地 run 快照交给精排 worker;有正文的任务跳过网页抓取,没有正文的任务继续走原容错阶梯。Web 与 ATS 仍共用职位主表和分析缓存,主表只留 JD hash。Phase B 初始化通用 data/source_registry.json 后,ATS 控制状态只写通用 registry,旧 data/ats_companies.json 保持只读;通用 registry 不存在时仍回退旧文件。data/ats_sync_state.json 继续保存低敏同步摘要。ATS 预算独立于 Web Search;跨 board 可并发,Lever 单 board内顺序翻页。Greenhouse 的 content=true 响应超过 25 MB 时可在同一全局请求预算内降级重试不含正文的列表,并记录 content_fallback。公开 API 回归仍使用 python tools/benchmark_ats.py --output <json> --page-size 50 --max-pages 10,且脱敏证据不保存职位正文、标题或 URL。详见 docs/ats-provider-phase1.md。
🔧 依赖
- Python 3.10+
- 必需:
pdfplumberpython-docxrequests - 可选:
playwright(headless 兜底,复用系统已装的 Chromium 系浏览器,无需playwright install) - 可选远程浏览器:
kernel、keyring
pip install pdfplumber python-docx requests
pip install playwright # 可选
pip install "kernel>=0.94,<1" keyring # 可选远程浏览器
Built with Claude Code.