# Personal Job Hunter — DSH Plugin Agent
> **定位:一个运行在 DeepSeek Harness(dsh)上的「个人求职 Agent」,不是传统 SaaS / 求职网站。**
>
> 核心能力一句话:**了解我,然后替我持续寻找真正值得投递的工作。**
本文件是项目的 Spec of Record(对开发者与 AI Agent 都有效)。改需求先改这里,再改代码。
---
## 1. 产品定义
用户只需告诉 Agent 求职目标,例如:
> “我想找美国 Remote 的 AI Engineer,5 年经验,期望 180K 以上。我有 Python、LLM、AWS 经验。”
Agent 自主完成:
```text
理解我的求职目标 → 建立/更新我的 Job Profile → 搜索 Jobs
→ 读取 JD → 判断匹配程度 → 筛掉垃圾岗位 → 告诉我最值得投的 Top N(附原因)
```
### 1.1 不是(MVP 明确不做)
| 能力 | 是否进 MVP |
| --- | --- |
| Job Search / JD 分析 / Profile / Match / Ranking / Why-Apply / Why-Not / 对话 | ✅ |
| 保存岗位(`search_jobs` 自动落盘到 Agent 传入的 `save_dir`) | ✅ 工具自动(见 §4.3) |
| 简历上传 | 🟡 第二阶段 |
| 简历修改 / Cover Letter / 自动投递 / 面试准备 / Application Tracking / 多 Agent | ❌ |
### 1.2 Agent 工作方式(Recruiter 工作流,实现于 `skills/job-hunter/SKILL.md`)
```text
Personal Job Hunter
├── Model (LLM:理解 JD、提取要求、解释原因)
└── Harness 插件 (dsh-find-jobs bundle)
├── Job Search Tool search_jobs(source, query, roles, remote, location, salary_min, date_posted, limit, save_dir)
│ (一次调用只搜一个数据源的一整页;save_dir 必填,搜索成功即把完整 JD 存档到
/.json;返回轻量摘要,多源由 Agent 并行调用)
├── Profile Memory get_job_profile / update_job_profile (文件持久化,~/.dsh/profiles//)
└── 技能编排 job-hunter(总工作流)
├── job-search (搜索:search_jobs 参数识别 + 自动存档)
└── job-match (评估:5 步业务判断,输出统一结果卡)
```
- **第一版单 Agent 即可,不需要 Multi-Agent。**
- 匹配评估:按 `job-match` 技能(`skills/job-match/SKILL.md`)做**业务判断**——Agent 理解 JD、逐项对照画像后,对每个岗位输出统一结果卡(`match_level` 档位 / `matched` / `gaps` / `concerns` / `recommendation` / `reason`);不依赖数值打分工具。
### 1.3 匹配档位与推荐(全项目唯一权威词表)
| match_level(匹配强度档) | 区间 | recommendation(是否值得投) |
| --- | --- | --- |
| `strong_match` | > 85 | `apply` |
| `good_match` | 70 – 85 | `apply` |
| `stretch` | 55 – 70 | `apply` / `skip`(看缺口性质与用户意愿) |
| `low_match` | < 55 | `skip` |
> 致命硬冲突(强制 onsite 而用户只要 Remote、薪资远低于底线、卡应届/年龄、命中 `avoid` 名单)→ 直接 `skip`,即使分数/档位偏高。
> `recommendation` 表示「是否值得投」;每岗评估统一输出结果卡:`match_score / match_level / matched / gaps / concerns / recommendation / reason`。
### 1.4 匹配评估(job-match 技能 5 步策略;原规则引擎已移除)
评估完全移入 `skills/job-match/SKILL.md`,由 Agent 做**业务判断**(不调用数值打分工具),5 步:
**① 分析 JD 硬性要求 → ② 与画像逐项对照 → ③ 判断 Match(`match_level`)→ ④ 判断是否值得投(`recommendation`)→ ⑤ 解释 Why Apply / Why Not(`reason`)。**
- 每个被评估岗位输出统一结果卡(见 §2 `MatchResult`);`match_score`(0–100)只是量化信号,分档/结论以业务事实为准(薪资须先统一币种与周期)。
- `score_job` 工具与 `src/match/` 目录已移除;原确定性权重表(技能 / 年限 / 薪资 / 远程 / 地点)仅作历史参考,不再执行。
---
## 2. 领域模型(MVP 数据形状)
```ts
// JobProfile —— 用户的求职画像(文件持久化)
{
target_roles: string[] // ["AI Engineer", "ML Engineer"]
skills: string[] // ["Python", "AWS", "LLM", "RAG"]
experience_years: number // 5
locations: string[] // ["United States"]
remote: boolean // true
salary_min: number // 180000(单位见 salary_unit)
salary_unit: string // "USD/年" / "人民币/月";默认 '' = 自动识别,不预设币种
preferences: string[] // ["AI startup", "small-medium company"]
avoid: string[] // ["traditional finance"]
}
// Job —— mock/真实岗位记录
{
id, title, company, location, remote, salary_min, salary_max,
posted_days_ago, url, summary,
requirements: JobRequirements, // 归一化硬性要求(供 job-match 技能评估参考)
description: string // 完整 JD 正文(喂给 LLM 阅读理解)
}
// JobRequirements —— Agent 阅读 JD 后整理出的硬性要求(供 job-match 评估)
{
skills: string[], years_min?: number, salary_min?: number,
remote_required?: boolean, locations?: string[]
}
// SearchedJob —— 岗位「完整记录」(get_job_detail 已并入 search_jobs)
// search_jobs 把完整记录存档为 /.json;其返回的是去掉 description/requirements 的轻量摘要
{
...JobSummary, description: string, requirements?: JobRequirements
}
// MatchResult —— job-match 技能每岗评估输出的统一结果卡(5 步策略后产出)
{
match_score: number, // 0–100 业务判断信号(不是结论)
match_level: 'strong_match' | 'good_match' | 'stretch' | 'low_match',
matched: string[], // 命中的必需项
gaps: string[], // 缺失的必需项
concerns: string[], // 顾虑(薪资/onsite/JD 存疑等)
recommendation: 'apply' | 'skip', // 是否值得投
reason: string // Why Apply / Why Not(引用具体依据)
}
```
---
## 3. 实现阶段
| Phase | 内容 | 状态 |
| --- | --- | --- |
| **1(当前)** | DSH + Recruiter Skill + **Mock Job Data**(不联网):Agent 能「给我一批岗位 → 帮我挑」 | ✅ 已完成 |
| 2 | 接真实 Job Search:`src/provider/` 用 **bsk 实时浏览** Boss直聘/小红书 → 统一 `Job` 形状;`Config.sources` 按喜好勾选 | ✅ 两个源已实站校准(2026-09);Boss 详情改为**直连 `job_detail` URL**(曾需列表点卡,复测点卡会触发风控关闭标签页);薪资为字体混淆不产出文本 |
| 3 | 持续 Job Search + 个人 Memory(基于 Profile Store 演进:会话记录 / 已看岗位去重 / 定时跑) | ⬜ |
| 4 | 自建 Web UI 包装成 "Your Personal AI Job Hunter"(先吃透 dsh web 再决定) | ⬜ |
> Phase 门控:`Config.mock = true`(默认,离线演示)。置 `false` 后按 `Config.sources` 勾选的来源,
> `search_jobs` 会驱动 **bsk**(本机真实 Chromium + 用户登录态)实时浏览抓取——**列表与详情在同一次浏览会话内抓齐**,
> 不在调用之间二次导航(反直链/风控友好)。实时抓取依赖 `bsk` CLI + 浏览器扩展在线;站点解析器按当前 DOM 编写、属实验性,解析失败会带页面片段报错以便校准。
---
## 4. DSH 插件工程约定(照抄 dsh-ecom-agent 已验证的模式)
1. **Bundle 结构**:npm 包声明 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`,`cordis.patch.yml` 顶层是 YAML 数组 `- insert:`。
2. **插件本质**:`src/index.ts` 导出 `name` / `inject` / `Config`(Schemastery Schema) / `apply(ctx, config)`。
3. **注册工具**:`ctx.tools.register(defineTool({...}))`;依赖 `tools` 服务时 `export const inject = ['tools']`。
4. **工具内不能调 LLM**(ds tools execute 无 ctx)。LLM 理解动作发生在 Agent loop 里,工具只做确定性事情;配置/数据通过 `apply` 阶段闭包注入工具。
5. **构建**:tsdown → `lib/`(ESM + dts),`main: ./lib/index.js`。脚本:`build` / `typecheck` / `test`。
6. **版本锚点**:peer/dev deps 统一 `0.1.0-rc.6`(dsh 全局 0.1.0-rc.6);`@deepseek-ai/cordis` 4.0.1;`@deepseek-ai/schemastery` 3.18.1。升级需全局核对。
7. **持久化位置**:`$DSH_HOME/profiles//.json`,用 `@deepseek-ai/dsh-home-paths` 的 `dshHomePath()`;profile 名取自 `DSH_PROFILE` 环境变量,缺省 `web`。
8. **Skill 不硬编码在代码里**:repo 内 `skills//SKILL.md`,复制到 `~/.dsh/skills//SKILL.md` 生效(DSH 本地 skill 发现目录)。
9. **实时数据源(Phase 2)**:`src/provider/site.ts` 定义 `SiteAdapter`(`buildSearchUrl` / `parseListPage` / `parseDetailText` 全为纯函数、可单测);`ADAPTERS` 即来源注册表。**新增站点 = 实现适配器 + 在注册表登记 + 加入 `Config.sources`**。
10. **bsk 会话策略**:`Config.bsk_session` 或环境变量 `BSK_SESSION` 填 `bsk session start` 打印的 id 可复用;留空则懒建一个常驻自动会话(单窗口、退出时 stop);**若命中失效会话(daemon 重启 / 会话过期 / 断连 / 配置的 session 已停),自动重建新会话并整体重试一次**,实现见 `src/provider/bskRunner.ts` 的 `runWithSessionRecovery`。
### 4.1 本地开发命令
```bash
# 本项目内
pnpm install
pnpm typecheck
pnpm test # 离线单测(parse/bossZhipin/xiaohongshu/site/searchJobs,均不联网)
pnpm test:boss # 只看 Boss 解析单测(test/bossZhipin.test.ts)
pnpm test:xhs # 只看小红书解析单测(test/xiaohongshu.test.ts)
pnpm live:boss # 实跑 Boss(需 bsk 在线+登录态;JQ/JLOC/JLIMIT 可覆盖,如 JQ='AI' pnpm live:boss)
pnpm live:xhs # 实跑小红书(同上)
pnpm live:parallel # 并行实跑 Boss+小红书(进程内 bsk 命令级串行,互不撞车)
pnpm build # typecheck + tsdown → lib/
# 注册进 profile(= “CLI 初始化”),dsh 无 init 脚手架,add 即初始化
dsh plugin --profile web add "$(pwd)"
dsh web --dump-config | grep -i find-jobs # 验证层已加载
dsh web # 起 Web UI,跟 Agent 对话验证
# 卸载/重装
dsh plugin --profile web remove dsh-find-jobs
```
### 4.3 岗位清单(search_jobs 自动落盘)
**岗位清单由 `search_jobs` 自动落盘**(索引 + 每岗一文件):`save_dir` = **当前会话工作目录** `/jobs/`(以 `pwd` 为准)为**必填**。搜索成功后工具**立即**为返回的每个岗位写 `jobs/.json` 基础档案(完整字段 + 完整 JD;覆盖时保留旧 `match`/`note`/`requirements`)并去重合并更新 `jobs/index.json`(轻量摘要 + `file` 指针,按 job_id 去重、刷新 last_seen_at)。**返回的 jobs 是轻量摘要(不含 JD),完整 JD 只存档在 `.json`**——Agent 精读某岗先读该文件;返回里带 `saved` 汇总(新增/刷新/总数)。**先存档后评估**:落盘不依赖后续 job-match 是否执行。`index.json` 条目带 **`match` 轻量标识**(`match_level` / `recommendation`,由 `.json` 顶层已有 match 派生)——Agent 判断「已评 / 未评、结论 apply/skip」**只读 index.json 即可**,不必逐个开 `.json`。评估产出的 `match` 由 Agent 读改写回对应 `jobs/.json` 顶层,并**同步 index.json 该条目**(规则见 `skills/job-hunter/SKILL.md`「分流与评估」及 `.json` 模块)。**`match` 即缓存**:画像未变则已评岗复用(`skip` 岗连详情文件都不读);只有首次评估 / 画像变化(`update_job_profile` 改关键字段)才重评覆盖并刷新 index。
插件按 Agent 传入的目录写——保存路径仍由会话 cwd 决定,不引入插件侧路径配置。
### 4.2 验证脚本(MVP 验收对话)
1. `帮我找适合我的工作。` → Agent 应询问画像要素并形成 Profile。
2. `最近有什么值得投的吗?` → Search → Analyze → Rank → Recommend(只报值得投的 Top N)。
3. `为什么这个岗位适合我?` → 引用 requirements + profile 给出原因与担忧。
4. 抽查:匹配评估应遵循 `job-match` 技能的 5 步策略与统一结果卡(`match_level` / `recommendation` 口径;业务建议,非数值工具产物)。
---
## 5. 文件布局
```text
dsh-find-jobs/
├── AGENTS.md # 本文档:Spec + 工程约定
├── README.md # 人类快速上手指南
├── package.json # dsh.bundle 声明
├── tsconfig.json / tsdown.config.ts
├── cordis.patch.yml # 注册 dsh-find-jobs 插件实例(含 Config 默认值)
├── skills/
│ ├── job-hunter/SKILL.md # 总工作流技能(画像→编排→分流→Top N)
│ ├── job-search/SKILL.md # 搜索技能(search_jobs 参数识别 + 自动存档)
│ └── job-match/SKILL.md # 匹配评估技能(5 步业务判断)
├── src/
│ ├── index.ts # 插件入口(name/inject/Config/apply)
│ ├── config.ts # JobHunterConfig + Schemastery Schema
│ ├── types.ts # 领域类型(见 §2)
│ ├── data/mockJobs.ts # Phase 1 mock 岗位库(保持 Job 形状)
│ ├── lib/
│ │ ├── profileStore.ts # JobProfile 内存 + $DSH_HOME 文件持久化
│ │ ├── search.ts # 纯函数搜索/过滤 mock 岗位
│ │ └── jobsArchive.ts # search_jobs 自动落盘(.json + index.json 去重合并)
│ ├── provider/ # bskRunner(会话+抓取) · 站点适配器(Boss直聘/小红书) · HTML/薪资解析
│ └── tools/ # searchJobs(save_dir 必填自动落盘,返回轻量摘要)/ jobProfile
└── test/ # 纯解析 / 落盘可离线单测
```
---
## 6. 注意事项 / 红线
- 匹配词表(`match_level` / `recommendation`)与分档阈值**唯一定义在 AGENTS.md §1.3**,由 `job-match` 技能落地;别在工具或其他文案里另写一套。(原确定性规则引擎与 `src/match/` 目录已移除。)
- 不要给 LLM 一个"黑盒自动投递"工具;MVP 绝不包含任何自动投递/对外发送动作。
- 涉及真实求职数据与外部 API 的 Phase 2 前,先与用户确认数据源与配额。
- Skill 名称保持 kebab-case(`^[a-z0-9]+(?:-[a-z0-9]+)*$`)。