wth2jhl.online

Back

career-ops 在跑的 demo

“Companies use AI to filter candidates. I just gave candidates AI to choose companies.” —— santifer,career-ops ↗ README 第一行


一、先讲个故事#

如果你曾经投过 200 份简历、收到 199 份沉默,你大概会对 santifer 写 README 的第一句话感同身受——

“I spent months applying to jobs the hard way. So I engineered the system I wish I had.”

他最后用这套系统自己拿到了一个 Head of Applied AI 的 offer。但和”上岸”这个浪漫结局比起来,更有意思的是:他把这套系统全部开源了。

于是我花了点时间把 santifer/career-ops ↗ 翻了个底朝天——43MB,124K 行 JavaScript,123 个 .mjs 模块,WIRED 和 Business Insider 都报道过,被 17 种语言翻译过。

读完之后我决定:如果你身边有正在求职的朋友,我要把这个项目甩给他。

不是因为它”AI 功能强”。而是因为它做了一件别的 AI 工具都没在做的事——它把”求职”当成 production system 来运营。


二、它不叫”career-assistant”,叫”career-ops”#

我第一次看这个名字,觉得只是又一个营销词。但读完 ARCHITECTURE.md 之后才意识到这两个字母的份量:

ops = operations。

santifer/career-ops:44 的 manifesto 里有一句话:

“We run our job searches the way engineers run production: with evidence, with discipline, with tools on our side of the table.”

翻译过来就是:把 SRE 那一套搬进求职领域。

证据?整套项目里这些工具名就是 SRE 词汇:

  • pipeline-lock.mjs(文件级 mutex)
  • reconcile-pipeline.mjs(数据对账)
  • check-table-freshness.mjs(表新鲜度校验)
  • update-system.mjs apply / check / rollback(带 rollback 的版本升级)
  • status-log.tsv(append-only ledger,immutable 不可改)
  • 还有那句贯穿全文的话——“files are canonical, indexes are derived”(数据库/DWH 术语)

求职不是一次性行为,而是一个长期可观察、可回滚、可对账的 pipeline。


三、Reverse ATS——把 ATS 武器还回去#

ATS(Applicant Tracking System)是招聘方用的关键词过滤算法,把 200 份简历在毫秒级筛成 3 份,剩下 197 份候选人收到沉默。

santifer 的反击:

“Apply better to fewer. Ten applications you believe in beat two hundred you don’t.”

career-ops 用对手的武器给候选人搭了一套反向 ATS:与其把 200 份简历砸给 ATS 算法,不如先让 AI 帮你筛掉那 180 个不该投的公司。

具体做法藏在 modes/oferta.md(西班牙语里”oferta” = “offer”,招工帖)。每一条 JD 经过 7 个 block 的硬约束评估:

  • Block A Role Summary(含 Geo-mismatch + Work-authorization 检查——sponsorship 缺失不是扣分,是 ⛔ hard blocker)
  • Block B CV Match(按 archetype 自适应匹配——AI 平台/LLMOps/Agentic/Tech PM/Solutions Architect/FDE/Transformation Lead 6 类)
  • Block C Level Strategy(“sell senior without lying” + “if they downlevel me” 双预案)
  • Block D Compensation(按公司类型给工资可信度评级——public big tech / growth-stage / agency / SMB / recruiter / govt / OSS 各自不同可靠性)
  • Block E Personalization Plan(CV 哪些 section 要改、改成什么、为什么)
  • Block F Interview Plan(6-10 个 STAR+R story,每个拆成 Situation/Task/Action/Result/Reflection 5 列)
  • Block G Posting Legitimacy(独立检测 ghost job / 钓鱼 JD)

最狠的是 Block G——和 A-F 解耦。一条 JD 可能在 A-F 都满分,但是 ghost job,整个评估就废掉。

而且 oferta.md:13 写明:评估前必须先过 liveness gate——链接已经 404 了,整套 A-G 评估直接 short-circuit。

求职不是玄学,是工程。


四、最让我想偷的一个设计:agent 之间没有 master#

career-ops 多 agent 架构

我看过几个号称”多 agent”的系统,里面都有一个 orchestrator 在中心调度子 agent。career-ops 不是。

它的 agent 之间根本没有中心调度。

数据流是这样的(ARCHITECTURE.md:79):

scan ──► data/pipeline.md ──► evaluate (oferta + cv) ──► reports/NNN-*.md
                                          │                      │
                                          └──► data/applications.md (tracker)
                                                         │
                                          apply (human reviews + clicks)
plaintext

几个关键设计:

  • data/pipeline.md = 输入队列(待评估 URL)
  • reports/{NNN}-{company}-{date}.md = 各 agent 的产出
  • data/applications.md = canonical tracker
  • data/scan-history.tsv = append-only,靠 SimHash fingerprint 去重
  • data/agent-inbox.md = session 启动时的 request queue(你在 dashboard 上想到”评估这个 URL”,append 一行,下次 session 启动自动 drain)

外加几个非常 SRE 的工具:

  • pipeline-lock.mjs——文件级 mutex,防止并发覆盖
  • reserve-report-num.mjs——原子分配 report number
  • merge-tracker.mjs + dedup-tracker.mjs——CRDT-like 合并,多个 worker 并发写 tracker 时各产 batch/tracker-additions/{ID}.tsv,再原子合并

结果是:任何 CLI agent(Claude Code、Codex、OpenCode、Qwen、Kimi、Grok、Cursor……)进来读这些 markdown,就能”接入”整个集群。Agent 之间通信靠的是 filesystem RPC——本质上是个”LLM-friendly FUSE”。


五、“cv.md 是 single source of truth”——一份事实,N 份文档#

cv.md 派生

career-ops 的 CV 子系统有一个非常优雅的设计:

cv.md 写一次,所有格式都从它派生。

具体链路:

cv.md (markdown 事实清单)
    │
    ├─► build-cv-html.mjs  ──►  templates/modern.html / executive.html / zh-minimal.html
    ├─► build-cv-latex.mjs ──►  Overleaf PDF
    ├─► generate-pdf.mjs   ──►  Playwright HTML→PDF
    ├─► cv-sync-check.mjs  ──►  校验 HTML/LaTeX/PDF 不漂移
    └─► verify-cv-facts.mjs──►  反向校验事实是否一致
plaintext

字体都自托管(Space Grotesk + DM Sans),避免 ATS 抓不到字体显示成 fallback。这是细节里的工程。

而且每次申请一条新 JD,batch-tailor.mjs 会针对那条 JD 单独走”读取 → 评估 → CV 重写” pipeline,产出一份量身定制的 CV——不是改一句话那种,是 section 级别的重组。

README 里那个”100+ personalized CVs”就是这么来的。


六、批处理 worker prompt——写在 markdown 里的人肉容错#

批处理 worker 协议

career-ops 的批量求职靠 batch/batch-prompt.md(约 350 行)驱动。这不是代码,是写在 markdown 里的 worker 协议:

1. 拿 {{URL}} + {{JD_FILE}}
2. 跑 A-G 评估
3. 写 reports/NNN-*.md
4. 生成 PDF
5. 写 batch/tracker-additions/{ID}.tsv
6. 输出 final JSON
plaintext

关键的容错在 batch/batch-prompt.md:144:

“If both fail, this is a hard stop — do ALL of the following… Do NOT invent, estimate, or guess a score”

这段注释背后有个真实事故——以前有过 worker 没读到 JD,但 commit 进 tracker 一条带假分数的记录。所以这条规则被写成硬规则:没有数据就 hard stop,绝不发明分数。

这种”曾经的故障长在 prompt 里”的设计,比任何代码层面的 validation 都更 robust。因为 validation 会被绕过去,但 prompt 注释是被 LLM 直接读进上下文的。


七、MANIFESTO 里那句”never submits”——立场即架构#

santifer/career-ops:14-19
plaintext

“career-ops never sends, submits, or clicks anything for you. Every send button is yours to press.”

这不是技术限制,是立场。把”永远不替你按提交”写成 manifesto 里的不可妥协条款。

对应的工程实现是分层:

  • browser-extract.mjs 注释里明说:“STRICTLY READ-ONLY: it navigates and reads the DOM. No clicks, typing, or form fills — that boundary is exactly what keeps this separate from apply”
  • apply 流程的每一步都要求 human review
  • 17 种语言的 README 都强调这条规则

不是”我们不能做”,是”我们故意不做”。这和 ATS 算法把候选人无声过滤掉形成对称:

ATS 把候选人无声拒掉 → career-ops 把”无人类按下提交”做成 first-class invariant

立场即架构。


八、和前两篇多 agent 框架对比:调度 vs 黑板#

顺便把这四篇博客里见到的多 agent 范式横向比较一下:

维度StrixACScareer-ops
业务目标安全测试(红队)领域知识检索个人求职
Agent 通信Hierarchical orchestratorYAML frontmatter共享 FS 黑板 + markdown RPC
状态持久化DB/内存YAML + JSONTSV + markdown(git-diffable)
编排协议中心调度(树状)静态索引去中心化(任何 CLI 都能挂)
HITL强人工审批无强 human-in-the-loop + 不替你点提交
隐私模型沙箱公开本地优先 + 数据所有权
本质调度智能体调度 metadata调度 markdown

career-ops 是唯一一个拒绝中心调度的——它的”多 agent”不是因为有一个聪明的 orchestrator,而是因为每个 CLI agent 都能 read 自己关心的那部分 markdown。


九、几个让我佩服的小细节#

9.1 评估日志 = 结构化反对”vendor assessment 越来越水”#

assessment-log.mjs + data/assessments.tsv:候选人收到 eSkill / HackerRank / Criteria Corp 的 skills assessment 时 append 一行:

{date, company, report#, platform, subject, threshold%, score%, stale_note}
plaintext

关键是 stale_note——候选人自己标注”考题内容过时吗”。比如 “test content references Adobe Acrobat 9, a 2008-era version”。

这种”把对 vendor 的抱怨结构化”的工程能力,比任何情绪宣泄都更有力量。

9.2 oferta.md:50:“5 次 WebSearch 封顶”#

deep research 模式有 bound:单次评估最多 5 次 WebSearch。把 research 失控做成硬约束,而不是”尽量别太多”。

9.3 reply-matcher.mjs 把”雇主的回复”做成信号#

候选人和雇主对话时,把回复 paste 进来自动分类(offer / rejection / interview request / ghosted),然后 analyze-patterns.mjs 长期看哪些渠道 / 哪些措辞让你转化率高。

把”求职的运气”变成”求职的数据”。

9.4 local-parser-cookbook.md#

README 文档之外,专门有一份教”怎么本地解析 JD”。这是把”模型很强 = 不需要解析”这种幻觉剥掉——模型再强,原始 HTML 也得清洗。


十、可借鉴 & 局限#

10.1 值得抄到任何个人生产力 AI 系统#

  1. manifesto-first 设计:把”我做什么 / 我不做什么”写成不可妥协条款,不是写到 README 末尾,是写到代码注释里;
  2. markdown 当 schema:modes/*.md 是给 LLM 的 schema,比 JSON Schema 更易改、更易读;
  3. filesystem 当通信总线:agent 之间靠 git-diffable 文件通信,比 message queue 简单 10 倍,比 in-memory state 可靠 10 倍;
  4. append-only ledger:求职每次状态变迁都被记录,能回放、能分析;
  5. “single source of truth + 多格式派生”:cv.md → HTML / LaTeX / PDF / DOCX 派生,跟 IaC(Terraform → 多云 provider)同构;
  6. liveness gate:评估前先验证输入活着。404 就别浪费一次 A-G;
  7. hard-stop on missing data:"Do NOT invent, estimate, or guess a score"——LLM 时代的最重要 invariant。

10.2 局限#

  1. 作者个人流程的产品化:虽然 60K stars,但 README 写得坦白——这是”santifer 用了几个月最后拿到 offer 的系统”,不是”经过 N 个用户验证的通用系统”;
  2. 124K 行 JS + 17 语言 README + 60+ 模式 + 90+ provider 适配器:维护成本极高。update-system.mjs 的存在本身就是证据——这是一个会变的活物;
  3. provider registry 覆盖:90+ ATS 适配器看起来很全,但 LinkedIn / Indeed 这种”算法黑盒 + 反爬激进的”渠道不一定稳;
  4. cv.md 是 single source of truth 的代价:所有 CV 变更必须走这一个文件,团队场景下不容易 fork;
  5. 隐私优先与生态化的张力:本地优先意味着没有 SaaS、没有数据飞轮、没有”基于你的数据训练的模型”。这在 AI 时代可能是双刃剑。

十一、读完这个项目之后我想问的一个问题#

我读完 santifer/career-ops 之后一直在想一个问题:

当一个 AI 工具不是替你做事,而是帮你”避免做错事”——它的产品价值怎么量化?

career-ops 不替你投简历、不替你发邮件、不替你点提交。它只帮你判断哪些事不该做、哪些事该做、怎么做。

“避免投错公司”这件事,对用户的回报是多少?“避免准备错的面试”呢?这种”被保护的价值”很难量化,但用户能感受到。

也许这就是为什么 santifer 选择开源——这种”反生产力”工具的商业化逻辑很难讲清楚,但它的代码可以被人读、被人 fork、被人改。


附录:关键源码定位速查#

模块文件
README manifestoREADME.md:1-50
架构原则ARCHITECTURE.md:33、MANIFESTO.md:14-19
单 agent 模式modes/oferta.md:13-180、modes/auto-pipeline.md、modes/apply.md
多 agent 黑板data/pipeline.md、data/applications.md、data/agent-inbox.md
锁与对账pipeline-lock.mjs、reconcile-pipeline.mjs、merge-tracker.mjs
CV 生成build-cv-html.mjs、build-cv-latex.mjs、generate-pdf.mjs
评估流程oferta.md、analyze-patterns.mjs、funnel-velocity.mjs
批处理协议batch/batch-prompt.md:144、batch/batch-runner.sh
公司研究company-history.mjs、company-funded.mjs
ATS 适配器providers/_registry.mjs + 90+ providers/*.mjs
评估日志assessment-log.mjs、data/assessments.tsv
隐私立场MANIFESTO.md:14-19、browser-extract.mjs 顶部注释
系统更新update-system.mjs apply / check / rollback
多语言 READMEREADME.{es,de,fr,pt-BR,ko,ja,cn,zh-TW,ua,ru,pl,da,ta,ar,hi,tr}.md

📘 项目信息

被 740 个岗位暴打之后,这位工程师把"找工作"拆成了 60 个 AI agent
https://wth2jhl.online/blog/career-ops-job-search-os/
Author 小吴同学
Published at August 18, 2026