
OpenHire · 哨兵
io.github.gzchenhao由 GitHub 账号 gzchenhao 发布v0.8.63更新于 Nov 7, 2026
Jobs with the employer's real posting date, from employers' own ATS. HR family. Résumé stays local.
概览
本地求职 MCP 服务器,让助手检索雇主在架岗位、盯岗、查薪资区间并在投递前核查公司。
- 可以做什么
- 从雇主自有招聘系统检索并筛选在架岗位,附发布日期、在架天数和投递链接
- 登记长期求职意向,按需拉取上次检查后新增的岗位
- 查询同类在架岗位雇主公布的薪资区间
- 跟踪已投岗位,报告哪些已下架、长期未出现或仍在架
- 核查雇主可信度信号,并实时读取任意公开招聘页
- 把自选雇主加入本机索引,并按需刷新单个雇主
- 适用场景
- 适合正在求职、希望助手找出带真实日期的在架岗位、判断哪些投递不必再等,或在投递前核实公司的情况。也可用于直接读取某家公司的公开招聘页。
- 运行要求
- 通过 uvx 在本机运行(需要 Python/uv)。无需账号或注册;首次启动会自动下载约 30 MB 的公开索引。可选环境变量 OPENHIRE_DATABASE_URL 可改用 Postgres 而非本地 SQLite 索引。可选的工商信息核查需自备 TIANYANCHA_API_KEY。下载索引、实时读取招聘页和刷新都需要网络。
安装
在 SourceWeft 中
- 打开 控制台中的 OpenHire · 哨兵,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
OpenHire · 开聘
Companies screen you with AI. Screen them back. 已读不回?先看岗位还在不在。
Let your AI assistant find jobs straight from employers' own systems, with your résumé never leaving your computer. Is the job still open? Is the company real? Check both before you apply. 让 AI 助手直接从雇主自己的系统里替你找工作,简历不出你的电脑。岗位还在不在,公司真不真,投之前先查一遍。
[MCP 1.0] [privacy: local-first] [python ≥ 3.11] [license: MIT] [216 employers hiring] [OpenHire on Glama]
投了没回音,该等还是该走? 对你的 AI 助手说一句「我投的这 12 个岗位,哪些已经下架、不用再等?」,它从雇主自己的招聘系统里答(2026-10-26 索引上真跑的结果,节选): Applied and heard nothing? Ask your assistant "which of the 12 postings I applied to have closed?" and it answers from the employers' own systems (a real run on the 2026-10-26 index, trimmed):
It tells you whether the posting is still there, never whether the employer read you: no index can see an inbox. 只说岗位在不在,不说雇主看没看:谁也看不到雇主的收件箱。投的时候也不交简历给任何服务器。
In Claude Code, the plugin is a whole local job-search workflow: find postings, check the company, tailor your CV, build interview stories, follow what you applied to. Your CV stays in your own folder (cv.md) and is read by your own assistant; OpenHire tools only ever get a job id. /plugin marketplace add gzchenhao/openhire, then /plugin install openhire@openhire.
在 Claude Code 里装上插件,就是一套完整的本机求职工作流:找岗、查公司、改简历、备面试故事、盯投递,简历留在你自己的文件夹里,OpenHire 的工具只拿到岗位编号。
[30-second quickstart: pipx install openhire, ohp bootstrap, ohp search]
Real terminal output — install from PyPI, download the public index, search. No account, no signup.
Every number here is checkable. Open-duration report (per-employer medians, named, monthly) · numbers.json (the provenance of every figure we quote, regenerated each refresh) · raw report data. 我们引用的每个数字都能核:岗位在架时长月报 · numbers.json
Is your company in here, and you did not put it here? Your postings are in this index because your own careers page serves them publicly. We can measure how long a role has been open; we cannot see why, and a long-open role is a question, not a verdict. Claim your company — free, no payment, ever — and say why in your own words, or ask us to remove you and we will, without arguing. 贵公司被收录了、而且不是贵公司提交的?点这里认领,免费, 可以用自己的话解释,也可以直接要求我们移除。
三不原则 · Three things OpenHire never does
- 不存简历 · No résumé, ever. 简历留在你的电脑上。过网的只有一个你自己生成的匿名指纹和岗位号;协议里没有简历字段,多传一个
resume参数会被拒绝,CI 测试钉死。 Your résumé stays on your machine. Only an anonymous, client-generated fingerprint and a job id ever cross the wire; the protocol has no résumé field, an extraresumeargument is refused, and CI pins it. - 不刷假单 · Never applies for you. 它只把你送到雇主自己的投递页,从不向任何 ATS 提交申请;连表单长什么样,我们的测试也只用一次普通 GET 看,到此为止。 It hands you the employer's own apply page and never submits anything to any ATS. Even our own tests only GET the form once and stop.
- 不追踪 · No tracking. 不埋点、不统计谁看了什么。服务器只记一条匿名授权(指纹 + 岗位号),没有你是谁;索引本身跑在你自己的机器上。 No analytics, no view counting, no telemetry. The server keeps one anonymous authorization record (fingerprint + job id) and nothing about who you are; the index itself runs on your machine.
还有一条不在这三条里,但同样锁死:排序买不到,它是(匹配度,新鲜度)的纯函数,见下文三条隐私红线。 One more that is locked the same way: ranking cannot be bought; it is a pure function of (match, freshness). See the three red lines below.
What your agent actually sees
You ask your assistant a question in plain language. It calls search_jobs, and every row comes
back carrying the employer's real posting date — so the agent can reason about staleness
instead of guessing.
You: Any senior Python roles that are actually still open? Skip the stale ones.
Assistant: This one has been open 166 days with a ghost_score of 0.61 — I'd deprioritise it. Here are four posted in the last three weeks instead…
ghost_score measures how long a posting has been open, not whether the employer still intends
to hire. A long-open role can equally mean "hard to fill". Treat it as a reason to ask, not a verdict.
An MCP server that turns any MCP-speaking assistant — Claude, 豆包 Doubao, DeepSeek (through an MCP client such as Cherry Studio or Chatbox), Cursor, 腾讯 WorkBuddy, TRAE, Windsurf, Cline, ChatGPT (through the hosted endpoint below) — into a private radar for AI / Infra, autonomous-driving and embodied-AI jobs — pulled straight from 216 employers' own career sites and public ATS APIs (Greenhouse / Lever / Ashby / 北森 Beisen / Moka, plus first-party employer career sites such as Li Auto's), across the US, Europe and China (Waymo, Figure, Zoox — and Unitree, XPeng, UBTECH, Mech-Mind…). No account. No signup. No résumé upload. Ever.
Three things a job board won't do for you:
- Surfaces how long each role has really been open. Every listing carries a
ghost_scoreaged off the employer's real posting date where the employer's system reports one (otherwise the day this index first saw it, flaggeddate_signal): the "2 days ago" a board shows you can be 300 days old in the ATS. - Structural privacy, not a pinky-promise. There is no résumé field in the protocol; a CI test fails the build if anyone adds one. Matching runs on your machine — only an anonymous fingerprint reaches the server.
- Ranking you can't buy. Order is a locked pure function of (match, freshness). No sponsored slots, no bidding — the signature is frozen by a test.
This is the 「哨兵 / Sentinel」 reference implementation — see
design_handoff_openhire_v01/README.md for the full protocol spec.
Quickstart — under a minute
Nothing to install, and no crawl to sit through. Add this to your MCP client's config:
Then ask your assistant for a job. That is the whole setup. The server downloads the ~30 MB public index by itself in the background on first start, so searches fill in within a couple of minutes while you are already talking to it. No account, no signup, no résumé upload.
Not sure which of these steps are yours? The connect guide (国内:openhire-vlny.onrender.com/connect) asks which assistant you use (ChatGPT, Claude, 豆包, DeepSeek, Cursor, 腾讯 WorkBuddy, VS Code) and shows only that one's steps, with the one block to paste. 选你用的助手,只看那一种的步骤。
Or paste this one block and let the assistant do the install and the first search (the check in the middle matters: an assistant that has just added a server usually cannot use it until it restarts, and left alone it will fake the result with a web search):
What to say to it. No tool names needed; plain sentences work. Paste any of these:
Clients that show MCP prompts (Claude Desktop's "+" menu, a slash command in Claude Code or VS Code) also list twelve ready-made ones: 求职清单 · Job-search checklist, 找工作雷达 · Job radar, 盯一家公司 · Track one employer, 查这家公司靠不靠谱 · Check an employer, 投之前查一遍 · Check before you apply, 海投改精投 · Shortlist, don't mass-apply, 评估这个岗位 · How a posting fits me, 按岗位改简历 · Tailor my CV, 面试故事库 · Interview story bank, 打招呼草稿 · Outreach draft, 看看我的雷达 · What's new on my radar, and 本周新岗 · New this week. Each one renders in Chinese when you type Chinese into it.
New graduates (应届生) start here. Say what year you graduate and which sector; the
assistant passes entry_level: true, which keeps only postings that say campus hiring,
internship or no experience required, or whose title says intern or junior. Every result
carries requirements: the lowest degree the posting requires (a degree it only prefers does
not count), the fewest years it asks for, and whether it is a campus or intern track, read from
the posting's own words. It also carries visa_sponsorship (offered / not_offered / null) with
the posting's own sentence, so a seeker who needs a work visa sees what the posting says and
asks the recruiter when it says nothing. On the 2026-11-03 index that is 1074 live postings
(numbers.json, live_postings_open_to_new_grads).
应届生直接说「我是 2027 届,找收应届的具身岗位」:助手会只留写明校招、实习、经验不限,或岗位名带实习 / 初级的岗位,
每条都写出启事里要求的最低学历(只写「优先」的不算要求)和最少年限,不用一条条点开 JD 找「3 年以上」。
没写的就是没写,不等于不要求。
Three things a first-time user gets without asking:
- A free job-search checklist: every step from target to offer, what the AI does for you, where you do the rest, and the three anti-scam checks, with the official addresses. Bookmark it: addresses move and scams change, and this page is kept current. Chinese and English, switch at the top right. gzchenhao.github.io/openhire/checklist.html 找工作每一步该做什么、哪步能让 AI 替你做、查公司和防骗去哪个官网,一页写全,免费,不用注册: openhire-vlny.onrender.com/checklist(国内直接开)
- HR and recruiting roles have their own family (
role_family: hr): a recruiter, HRBP or people-ops person can search their own market instead of digging through "ops". HR、招聘岗单独成类,做 HR 的人也能直接搜自己的岗。 - Works from mainland China without a proxy: when GitHub is unreachable the index downloads from a mirror on CNB (Tencent) by itself, same file, same checks. 国内网络不用代理:GitHub 不通时自动从腾讯 CNB 镜像拿索引,同一份数据。
The one prerequisite is uv (it provides uvx). Without it the
config above fails with nothing but "server failed to start", so install it first:
On Claude Desktop you can skip even that — download
openhire-0.8.63.mcpb and
double-click it. No terminal, no Python, no uv.
On Cursor, one click (it still needs uv on your PATH): [Install MCP Server in Cursor]
Per-client config paths and the trade-offs of uvx vs a one-time install are in
Works with below.
Prefer a terminal? The same index from a CLI.
ohp bootstrap downloads the public snapshot (~30 MB, no account) and then runs one
incremental crawl to refresh verified_at and catch delistings. The crawl is the slow
part — a line per employer, 20+ minutes on a cold index — and you can stop it once the
snapshot is in; the index is already usable, just verified as of the last weekly refresh
rather than today. The MCP path above needs none of this: serve fetches the snapshot by
itself in the background.
Works with
All clients use the same MCP entry. The config below works in every MCP client and pulls the package on demand — but it does need uv present first.
New to MCP? Two shortcuts before the config below. Claude Desktop: download
openhire-0.8.63.mcpband double-click it. No terminal, no Python. Cursor — [Install MCP Server in Cursor] (needsuvinstalled). 腾讯 WorkBuddy / Cursor / Claude Code / TRAE — paste the one-block prompt from the Quickstart above: it installs, checks the tools are callable, and does the first search. ChatGPT (paid plans) — Settings → Connectors → Advanced → Developer mode → Create, nameOpenHire, URLhttps://openhire-vlny.onrender.com/mcp, no authentication. Nothing to install; that host sees only tool arguments, never a résumé (seedocs/PRIVACY.md§3). 豆包(豆包工作) — its custom connectors take an HTTP address, not a command: runohp serve --transport streamable-http --port 8000on your machine, then addhttp://127.0.0.1:8000/mcpas a 自定义连接器 (see the 豆包 entry under Works with). DeepSeek — the DeepSeek app has no MCP slot; use DeepSeek inside an MCP client (Cherry Studio, Chatbox, Cline, WorkBuddy, TRAE) and give that client the config below. VS Code (GitHub Copilot) — Install in VS Code (needsuv), or the.vscode/mcp.jsonunder Works with below.
Prerequisite: uvx ships with uv. Without it the config
below fails with nothing but "server failed to start" in your client — install uv first:
First start downloads a ~30 MB index in the background; searches fill in within a few minutes.
Stuck? Run
ohp doctor. It checks the three things that all look identical from the chat window —uvmissing, no index yet, server configured but not enabled — and reads every client config it can find. It runs in your terminal, which matters: if the client never started our server, nothing we wrote inside it can reach you.Editing the config may not be the last step. Several clients require you to enable or trust a newly added server before its tools load — the config is saved, the server never starts, and the only symptom is that your assistant does not seem to know about the tools. If a search does nothing, open your client's MCP/connectors panel and check that openhire is listed and switched on. (Claude Desktop needs a full quit and reopen; Cursor and Windsurf pick it up on reload; some clients show a per-server toggle.) 改完配置不一定就完事:部分客户端需要你在设置里手动「信任 / 启用」这个 server, 工具才会加载。症状是配置明明在、助手却完全不知道有这些工具。
What uvx costs you, every time. uvx resolves the package on each invocation — measured
at 7–8 s per call even with a warm cache. That is paid on every MCP session start and every
CLI command. It buys you never having to manage an install. If you would rather pay once:
@latest also means your tool surface can change under you without warning. Pin it when that
matters: "args": ["openhire==0.8.63", "serve"].
The server auto-downloads the public job snapshot on first run if the index is empty, so
ohp bootstrap is optional. If you ran pipx install openhire, "command": "ohp" works too.
Claude Desktop — %APPDATA%\Claude\claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/); quit & reopen after editing:
Cursor — ~/.cursor/mcp.json (or a project .cursor/mcp.json):
Windsurf — ~/.codeium/windsurf/mcp_config.json:
VS Code (GitHub Copilot) — .vscode/mcp.json in your project (VS Code's own servers shape),
or the one-click link in the shortcuts above:
Open the file and click Start above the JSON, switch Copilot Chat to Agent mode and tick
openhire in the tools list; then paste the one-block prompt from the Quickstart. A tester who
came in this way found no VS Code instructions here at all (reports/098), hence this entry.
Claude Code — this repo is also a plugin marketplace, so two commands inside Claude Code do
the whole setup (still needs uv on PATH):
Cursor plugin / Agent Plugins — the repo carries .cursor-plugin/plugin.json and the
vendor-neutral plugin.json + mcp.json (agent-plugins.org), so any client that loads Agent
Plugins can install it from the repo URL.
TRAE(字节) — one-click import links (TRAE asks you to confirm the config before adding it):
TraeCode 国内版 ·
TRAE international.
Or paste the uvx config above into TRAE's MCP settings by hand.
腾讯 WorkBuddy — ~/.workbuddy/mcp.json, same mcpServers shape. WorkBuddy reads the
file when it starts, so after editing it quit and reopen the app (or add the server through
the app's 专家·技能·连接器 → 自定义连接器 button instead); a hand-written entry does not appear in
the list until then. No uv on the machine? pip install openhire into any Python ≥ 3.11 environment and
point command at that environment's ohp executable with "args": ["serve"].
豆包 · 豆包工作(字节) — 豆包的「技能·连接器·伙伴 → 新建 → 新建自定义连接器」只收 HTTP 地址, 不收命令行。先在本机起一个 HTTP 端:
然后在豆包里新建自定义连接器:传输类型 HTTP,服务器 URL http://127.0.0.1:8000/mcp,Headers 留空,保存后打开开关。
索引、指纹和匹配都在你自己的电脑上,豆包只是通过本机端口调用它。
(2026-10-12 在豆包工作里实测通过:新建连接器后直接问「用 openhire 搜香港的岗位」就有结果。)
DeepSeek — DeepSeek 自己的 App 没有 MCP 入口。把 DeepSeek 当模型用在任何支持 MCP 的客户端里
(Cherry Studio、Chatbox、Cline、腾讯 WorkBuddy、TRAE 都行),再把上面那段 uvx 配置填进该客户端的
MCP 设置即可;Cherry Studio 里是「设置 → MCP 服务器 → 添加 → 类型 STDIO,命令 uvx,参数 openhire@latest serve」。
First start downloads the ~30 MB public snapshot (jobs/companies only) — give it a moment. To refresh later run
ohp bootstrap --forceorohp ingest. On Windows Claude Desktop from the Microsoft Store, the config is under…\Packages\<Claude package>\LocalCache\Roaming\Claude\.Hosted endpoint, for ChatGPT and any client that only takes a URL (since 0.8.11): the maintainer runs a copy of this server at
https://openhire-vlny.onrender.com/mcp(streamable HTTP, no authentication; a Render web service built fromhosted/viarender.yaml, same package, same public index;/healthztells you it is up and which version. It is a free instance: the first request after a quiet spell can take up to a minute). ChatGPT (paid plans): Settings → Connectors → Advanced → turn on Developer mode → Create: nameOpenHire, URL above, authentication none; then ask in a new chat with the connector enabled. What that host receives is only what the tools take (search filters, a company name or domain, a board URL, the anonymous fingerprint), never a résumé; it keeps no query log; watches live in its index keyed by your fingerprint. The local install above remains the recommended path, because there nothing of a search leaves your machine. Details indocs/PRIVACY.md§3.Self-host the same thing:
OPENHIRE_HOSTED=1 ohp serve --transport streamable-http --host 0.0.0.0 --port 8000exposeshttp://host:8000/mcpplus a landing page,/healthz, a per-address rate limit and a worker that picks up the weekly snapshot (also--transport sse). ADockerfileis included.GitHub unreachable from your network (mainland China without a proxy, some sandboxes)? When the GitHub download of the index snapshot fails, the server and
ohp bootstraptry a copy on CNB, Tencent's code hosting, under the same checks (the copy is refreshed from GitHub every 6 hours by the repository's.cnb.yml; direct link https://cnb.cool/gzchenhao/openhire/-/releases/download/snapshot/openhire-index.db.gz , andOPENHIRE_SNAPSHOT_URLset to it skips GitHub altogether). The package itself comes from PyPI; a PyPI mirror makes the install fast, e.g. add"env": {"UV_DEFAULT_INDEX": "https://pypi.tuna.tsinghua.edu.cn/simple"}to the server's config (older uv:UV_INDEX_URL), orpip install -i https://pypi.tuna.tsinghua.edu.cn/simple openhire. The code is mirrored at https://cnb.cool/gzchenhao/openhire .ohp bootstrap --freshskips the snapshot entirely and crawls the employers' own ATS (20+ minutes, no GitHub). An empty index caused by a failed download says so in the tool result (bootstrap_error) instead of looking like "no matches". 国内网络:GitHub 下载快照失败时,服务器和ohp bootstrap会改试国内镜像 CNB 上的副本(每 6 小时与 GitHub 同步,校验相同)。 镜像直链:https://cnb.cool/gzchenhao/openhire/-/releases/download/snapshot/openhire-index.db.gz ;在 MCP 配置的env里把OPENHIRE_SNAPSHOT_URL设成它,就完全不走 GitHub。 装包慢就用 PyPI 镜像:在 MCP 配置里加"env": {"UV_DEFAULT_INDEX": "https://pypi.tuna.tsinghua.edu.cn/simple"}(旧版 uv 用UV_INDEX_URL),或pip install -i https://pypi.tuna.tsinghua.edu.cn/simple openhire。 代码镜像:https://cnb.cool/gzchenhao/openhire 。
What it does
Optional, entirely local: ohp init --scan <dir> derives a skill fingerprint from your
own repos. You never write a résumé; the code never leaves your machine — only an anonymous
vector does.
For employers: claim your tenant
If your company is in this index, the listings came from your own public ATS — we did not ask, because we did not need to. What we cannot know is your side of it: whether a role is an evergreen talent pool rather than a stale req, or how fast you actually reply.
Claim it (中文表单). Free, verified by corporate identity — a GitHub org membership or a reply from a corporate domain — and never by payment. We answer within 3 business days, claiming leads to no paid follow-up of any kind, and your proof is used to verify and then nothing else.
No GitHub account? Email [email protected] with "Employer claim" and your company name in the subject — sending from your corporate domain is the first step of verification (enough for a claim; a self-reported roster is also checked against the company register, the ICP record and a callback to your public number) — or have anyone file the form on your behalf, since what we verify is the company and not the filer. 没有 GitHub 账号?直接发邮件到 [email protected],用贵公司企业邮箱发出来即完成身份核验。
No ATS, or on Feishu Recruitment? Then your postings are not in the index yet and there is
nothing to claim: the index reads employers' own systems only. Three read-only, free ways in,
none of which touch ranking. The simplest needs no IT staff: fill in
one spreadsheet and email it from
your corporate domain (we then check the company register, the ICP record, the domain's age and call the public number on your own site); each posting is marked employer_self_reported, carries
date_signal: self_reported, and expires after 90 days unless you send the sheet again. The
other two: grant the two read-only Feishu scopes (hire:site:readonly,
hire:site_job_post:readonly), or put machine-readable postings on your own careers page
(schema.org JobPosting JSON-LD or a static JSON file). The claim form has a field for which
of these fits you, and the employer page
walks through all three.
没有招聘系统或用飞书招聘?最简单的一条:填一张岗位表,用企业邮箱发给我们,不需要 IT 人员;另外两条是飞书只读授权和官网机器可读岗位页,都写在认领表单和雇主页里。
A claim gets you:
- Your own note, attributed to you, beside the roles you name. We can see how long a role has been open; we cannot see why. An evergreen talent pool, a genuinely hard-to-fill role, and a neglected one look identical from outside, and only you can tell them apart.
evergreen/hard to fill/closedstatus on specific titles. Aclosedrole stays visible while your ATS still serves it — we do not hide what your own site returns — but it is marked closed on your word so nobody else applies.- A correction if your ATS's date field means "requisition opened", not "went live". That one misreading makes every role you have look years old, and it is not your fault.
response_sla_dayson every one of your postings, including ones you post laterclaimed: trueon your company, with the date
It does not get you rank, and it does not lower your ghost_score. Ordering is a
locked pure function of (match, freshness) and the score is a pure function of (relist
count, posting age); tests freeze both and assert the claim path touches neither. Your note
sits beside the score and explains it. We would rather show a high score with your
explanation than a quiet score somebody paid for.
Asking to be removed entirely is also fine, and we will not argue about it.
Verified claims live in src/openhire/seed/claims.py — in
the repo, not in a private database — so each one is a reviewable diff, and the weekly
rebuild re-applies them instead of quietly dropping them.
Keeping it current
The index refreshes weekly, so a search can be up to seven days behind. When the user is about to act on one employer and wants today's truth:
Over MCP this is the refresh_index tool. Three rules are built in, not advisory:
- One employer per call. A full crawl is 20+ minutes and no client waits that long. An
ambiguous word (
robot→ 11 matches) is refused with the candidate list, never fanned out. - Six-hour throttle per employer, checked before any network call, so a too-soon
request costs the ATS nothing and returns
last_refreshed_atinstead of an error. - Not for speculative or looped calls. Each one hits somebody else's public endpoint.
That last point is the whole design constraint: our own crawl is a weekly batch we control, and handing refresh to callers turns it into our users hitting their endpoint on our behalf. The throttle is what keeps that boundary ours to keep rather than ours to spend.
Reading the three dates
Every row carries three timestamps that answer three different questions. Read together they separate an abandoned requisition from one somebody is still tending:
ghost_score = 1.0 alone is not a verdict. Open 367 days and untouched for 367 days reads as
abandoned; open 327 days but touched 13 days ago reads as a tended evergreen req. Among the rows where the ATS actually reports a last-touched date,
67% of ghost_score >= 0.99 postings were touched by the employer within the last 30 days (measured 2026-09-19; the live figure is pct_ghost_hi_touched_within_30d in docs/numbers.json, which is regenerated every refresh).
ghost_reason spells out which input drove the score ("age only: open 367d, never relisted").
None of these measure intent. A long-open role can equally mean hard-to-fill — treat the numbers as a reason to ask, not a verdict.
Is this employer what it says it is?
check_employer (CLI: ohp check-company 宇树 --domain unitree.com --jd posting.txt) returns a
checklist, each item verified / not_verified / unavailable / not_applicable /
pending_user with its source and time: whether the employer's own system is in the index
and what it shows, the domain's registration date, the earliest Wayback capture, the ICP
备案号 on the homepage, and the recruitment-scam red flags in a posting you paste (or, with
job_id, in the posting as this index holds it, so the assistant can check the role it just
found without copying text). The company register (成立日期, 经营状态, 参保人数) costs money:
set TIANYANCHA_API_KEY on your own machine and the tool fetches it with your 天眼查
account, or follow the free manual links it lists; for a Hong Kong or overseas employer
those are the HK Companies Registry e-Services search, the HK Police Scameter (a phone
number, URL or receiving account against the police scam database) and OpenCorporates.
It also screens the name against the OFAC SDN and consolidated sanctions lists, downloaded
to your machine daily and matched locally, because a live registration proves little on its
own: Prince Group's Hong Kong companies were on the register the day they were sanctioned.
Since 0.8.8 it also reads EarlyTip's public export of organisational and financing events
(executive departures, restructurings, funding rounds, acquisitions, bankruptcies;
earlytip.com/data/org_events.json), downloaded whole to your machine once a day and matched
there by the export's own published company_key, and lists what is on record for the employer
as a type, a date, the amount where recorded, a source domain and a plain link: never the headline, no link at all where
the source site's terms list linking among the prohibited acts, retracted events dropped, and a
note that EarlyTip checked its values against its own published headline and summary, not the
source article.
There is no total and no verdict on purpose: a young domain or a missing ICP record is a
fact to weigh, not a sentence. An unavailable item says whether the source was reached
and had no record, or whether this query never reached it (reason: network), because a
check that could not run says nothing about the employer.
An employer outside the index
read_board (CLI: ohp read-board https://jobs.ashbyhq.com/uipath --location "Hong Kong")
reads one employer's public board live, whether or not the employer is in the index and
whatever its sector, as long as the board sits on a vendor the adapters already read:
Greenhouse, Lever, Ashby, Moka, 北森, Workable, SmartRecruiters, Workday, Rippling or Personio. The assistant pastes the careers URL and gets the
board's own postings with the board's own posting date and days_open where it reports one
(date_signal: not_reported_by_ats otherwise), a direct apply link on the employer's own
tenant and a description excerpt. No skills, no ghost_score, no relist history and no
watches, because those need repeated observation, and nothing is stored. A Feishu Hire
portal, SuccessFactors, LinkedIn or a job site is refused with the reason: those
are access controls or other people's sites, and this index never looks for another way in.
One live read per board per 15 minutes per server process; a repeat is answered from memory.
Your own employers (0.8.13). The public index is a curated list for three sectors; the
employers one person targets are wider than that. add_employer(url) (CLI ohp add-employer)
puts any board the adapters can read into the index on your machine and crawls it at once:
from then on search_jobs(company=...), watch_intent, refresh_index and ghost_score
cover it exactly like a seeded employer, and the weekly refresh keeps it. Such employers are
flagged user_added, never enter the public snapshot (they say where you are looking), and
remove_employer takes them out. The result also carries a prefilled GitHub issue link to
propose the employer for the public index, which sends nothing unless you open it.
A board can also be watched: watch_intent with board_url (plus location / title)
makes check_watches read it live on every pull and report what appeared and what
disappeared since the last pull.
Employers OpenHire cannot read (0.8.14). Some employers in these sectors post where no
adapter can read: Feishu Recruitment (飞书招聘, a signed list API), BOSS直聘 only, or a careers
page without a list. list_portals (CLI ohp portals) lists them with a link to each one's
own careers portal, what a browser shows there without logging in, and the day it was last
checked. The same list is a page you can click through:
gzchenhao.github.io/openhire/portals.html.
An employer the index does read that also posts on such a portal (星动纪元 reads through
Moka and also lists roles on Feishu) carries also_posts_on in get_company_info, because
the roles that are only there are missing from search results.
Asking about one employer
search_jobs(company=...) takes whatever the user actually said — an id (unitree), or any
part of the name in either language (宇树, Unitree, XPeng). A name this index does not
carry comes back as the empty-result object naming the company, never as an unfiltered search.
--distinct (collapse_role_group over MCP) keeps one row per role_group and adds
role_group_size. About 20% of a page is the same role listed once per city; folding is
opt-in because each city row has its own job_id and apply_channel, which matters under a
location or visa constraint.
The five protocol fields
Every listing is valid schema.org/JobPosting, plus:
verified_at— last moment confirmed live on the employer's own sitesource—employer_site | ats_public_api(never a job board)ghost_score— 0–1 listing-activity signal, aged off the real posting date (lower = fresher). A noise filter, not an accusation: long-open listings are often evergreen talent pools or slow pipelines — the score simply lets agents down-rank low-activity noiseresponse_sla_days— the employer's OWN committed reply window. Null on almost every row, and that null is meaningful: it is set only when an employer claims their tenant. We never infer or estimate it, because we cannot observe a reply even in principle — the application deep-links to the employer and never touches this server. Read null as "no employer has claimed this tenant", not as "missing" or "slow". Employers: claim yours — free, verified by corporate identity, never by payment. It buys a verified badge, the ability to correct listing status (an evergreen pool carrying an unfair staleness score, say), and this field. It does not buy rank: ranking is a locked pure function of (match, freshness), and a test freezes that signature.apply_channel— always the employer's own application URL, deep-linked to the specific job
Privacy Policy
Short version: there is no résumé field in the protocol, matching runs on your machine, and the only user-originated value the server ever stores is an anonymous client-generated fingerprint. No analytics, no telemetry, no third-party sharing. Full policy: docs/PRIVACY.md.
Privacy model
First-run data — the snapshot vs. fresh
ohp bootstrap (default) downloads a small public index snapshot (a GitHub Release
asset — companies + jobs only, zero user data) and then runs one incremental crawl to
refresh verified_at / delisting. --fresh skips the snapshot and crawls the public ATS from
scratch with the free offline heuristic extractor. Either way: no account, no PII.
Two things that surprise people:
- The incremental crawl is slow and quiet. On a cold index it can run for 20+ minutes
with no output. It is working, not hung. If you only want the data,
ohp serveskips it entirely — the server downloads the snapshot on first start and is answering in seconds. - The snapshot URL is pinned to the
v0.1.0tag on purpose. It looks stale; it is not. That asset is overwritten in place every Monday by a scheduled workflow, so the URL is a stable address for always-current data. Pinning it to the newest tag would break every client the moment a release is cut.
Three rules this project will never break
- Your résumé stays on your machine — it never transits the server, and we never store it.
- Ranking is not for sale — it is only
f(match_quality, freshness), a locked pure function. - Employers pay only for authorized, delivered outcomes — never for exposure. (v0.1 has no billing at all.)
These are enforced by CI (tests/test_privacy.py, tests/test_ranking.py,
tests/test_snapshot.py).
Development
Set OPENHIRE_DATABASE_URL=postgresql+psycopg://… to run against Postgres instead of the
default local SQLite file (~/.openhire/openhire.db).
Roadmap
- v0.2 – v0.3 (shipped) — CN ATS adapters (北森 Beisen + Moka) · weekly auto-refreshed
public snapshot ·
ghost_scorepublic beta · 216 employers across US / EU / China - next — Employer claim + verified badges — employers can reserve their claim today via a corporate-identity GitHub issue (zero-cost now; badges + listing-status control ship next) · response-SLA enforcement (7-day auto-delist) · redacted proof-of-fit — an anonymous, candidate-authorized match summary that travels with an application (skills overlap only; identity never included, résumés still never transit the server)
- v1.0 — Open, vendor-neutral schema extension for AI-readable job postings
FAQ
Where does the job data come from?
Directly from 216 employers' own public ATS APIs (Greenhouse, Lever, Ashby, 北森 Beisen, Moka, Workable, SmartRecruiters, Workday, Rippling, Personio, Comeet, plus
first-party employer career sites such as Li Auto's), the same endpoints that power their careers
pages. No third-party job boards. source is
always ats_public_api, and verified_at records the last time we confirmed each posting live.
The public index is auto-refreshed weekly, so a fresh ohp bootstrap starts from recent data.
Why should I trust ghost_score?
It's a pure, open, unpurchasable function — min(1, 0.15·relist_count + staleness) aged off the
real ATS posting date, not our crawl date. The formula lives in pipeline/ghost_score.py,
is unit-tested, and takes no money as input (red line #2). Long-open, repeatedly-relisted
postings score higher; you can always re-rank client-side. Read it as signal-to-noise, not
bad faith: plenty of high-scoring listings are legitimate evergreen talent pools. Employers
who want their listing activity represented accurately can claim their tenant (see Roadmap).
Does my résumé actually go through the server — really?
No. There is no résumé anywhere in the protocol. authorize_application has no résumé/file
parameter (it structurally cannot accept one), matching runs on your machine, and the only thing
that ever transits the server is an anonymous fingerprint the client generates, like #a3f9-k2p7-x8q1. This is enforced by
tests/test_privacy.py, and the published snapshot carries zero user data (tests/test_snapshot.py).
Does it support China (中国区)?
Yes — this is what sets OpenHire apart. Employers on 北森 Beisen (<tenant>.zhiye.com) and
Moka (app.mokahr.com) are indexed: 20+ autonomous-driving / robotics / embodied-AI
companies including 宇树 Unitree, 小鹏 XPeng, 优必选 UBTECH, 梅卡曼德 Mech-Mind, 速腾聚创 RoboSense,
元戎启行 DeepRoute, 星海图 Galaxea, 傅利叶 Fourier, 普渡 Pudu. Pay published as 月薪 keeps its real
period (salary_period), so a salary floor no longer silently drops Chinese roles.
飞书招聘 (Feishu Hire) is not supported and won't be: it signs its job-list requests with a
ByteDance _signature and gates them behind a captcha SDK, so its listings are not publicly
readable. We don't break anti-bot measures.
How do I get a company added?
Open a Company inclusion request issue (title it with the company + its ATS URL) — this is
the best way to contribute. If you code, add it to src/openhire/seed/candidates.py (company
slug + ATS vendor/tenant) and open a PR; the seeder validates tenants against the live API.
License
MIT © OpenHire Protocol · PRs welcome.
Built by a deep-tech headhunter who does not write code, pair-programming with Claude Code. Full acceptance reports, including the mistakes, in reports/.
来源:README.md,提交 392e282
工具
0版本历史
38- v0.8.63最新Nov 7, 2026
- v0.8.61Nov 5, 2026
- v0.8.60Nov 5, 2026
- v0.8.59Nov 4, 2026
- v0.8.58Nov 3, 2026
- v0.8.57Nov 3, 2026
- v0.8.51Nov 1, 2026
- v0.8.47Oct 31, 2026
- v0.8.44Oct 31, 2026
- v0.8.42Oct 31, 2026
- v0.8.41Oct 30, 2026
- v0.8.38Oct 30, 2026
- v0.8.34Oct 30, 2026
- v0.8.33Oct 29, 2026
- v0.8.27Oct 22, 2026
- v0.8.26Oct 22, 2026
- v0.8.25Oct 21, 2026
- v0.8.17Oct 21, 2026
- v0.8.15Oct 20, 2026
- v0.8.14Oct 19, 2026
- v0.8.13Oct 15, 2026
- v0.8.11Oct 15, 2026
- v0.8.10Oct 14, 2026
- v0.8.7Oct 12, 2026
- v0.8.5Oct 12, 2026
- v0.8.4Oct 11, 2026
- v0.8.3Oct 11, 2026
- v0.8.2Oct 11, 2026
- v0.8.1Oct 10, 2026
- v0.8.0Oct 8, 2026
- v0.7.0Oct 8, 2026
- v0.6.9Oct 8, 2026
- v0.6.8Sep 28, 2026
- v0.6.7Sep 24, 2026
- v0.6.6Sep 23, 2026
- v0.6.2Sep 23, 2026
- v0.6.1Sep 22, 2026
- v0.6.0Sep 16, 2026
评分
已满足适用检查项的 50%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通25 分
请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。
- 已声明认证方式10 分
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分