# 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]+)*$`)。