设计会话 · 2026-08-03 · Alfred V1 重新梳理¶
元信息 | 状态:已归档(决策已落 ADR-0009~0017)| 最后更新:2026-08-03 | 范围:V1 重新设计完整讨论存档 | 关联:VISION.md、DATA_MODEL_V2.md
这份文档的用途:本轮讨论的完整存档 + 决策台账。 关掉对话之后,任何人(包括未来的我)只读这一份就能接上全部上下文。 状态标记:
🔴 待决/🟡 倾向/🟢 已决(附 ADR 编号)相关文档:
VISION.md·DATA_MODEL_V2.md·GLOSSARY.md·adr/
0. 产品愿景(本轮新增的最上位约束)¶
Alfred 从「辅助求职的工具」逐步演变为「懂我的个人管家」。
这句话不是口号,它是一条架构约束,会直接否决一些看起来更省事的设计:
| 愿景推论 | 对 V1 的硬约束 |
|---|---|
| 未来要管健康、记账、社交,不只是求职 | 内核(对话 / 记忆 / 实体 / 事件 / 提醒)必须领域无关;求职只是第一个领域包 |
| 「懂我」= 长期累积,不是单次问答 | 记忆与对话是一等公民,不是日志;原话永久留存,提炼可重算 |
| 「管家」= 会主动找我,不是我问它答 | 提醒引擎(nudge)不是 V3 的锦上添花,是 V1 就要有骨架 |
| 一个人用很久 | 数据可导出、可回滚、可纠错;错一次记忆会污染很久 |
分期(详见 VISION.md):
- V1 求职(当前):投递 / 经历 / Coffee Chat / 简历 / 提醒
- V2 社交 CRM:人脉网络、关系维护、follow-up
- V3 个人管家:健康、记账、日程
- V4 主动代理:跨域联动(「你下周三有面试,但那天你约了体检」)
1. 你本轮的原始输入(原样归档,不做压缩)¶
一、前端设计¶
产品形态类似微信聊天界面,支持发送语音、文字、图片、文件等。AI 也会向用户反馈文件、图片或链接。
- 第一阶段:小程序。
- 第二阶段:个人网页。
- 基础功能:包含必要的权限申请(如日历、相册等)及交互提醒。
- 疑问:小程序中是否能实现定闹钟等系统级操作?如果不行,网页版是否可行,或有无其他替代方案?
二、后端设计(核心)¶
重点构建针对求职场景的 V1,能记录岗位投递、心得、Coffee Chat 等信息,并保证后续扩展性。
- 工作流:(a) 用户输入多模态信息;(b) AI 解析并转化为结构化数据,用户确认后入库;(c) 生成回答时结合数据库历史上下文与聊天记录。
- 技术选型:(a) 参考 LangGraph 或 OpenClaw;(b) 调研 OpenAI Agent SDK;(c) 搜索资料完善方案。
- 上下文管理:(a) 聊天连续,需定期更新与压缩;(b) 手动刷新 / AI 自动判断主题拆分 / OpenAI Session 模式?
三、数据库核心概念(你的清单)¶
- Conversation — 每一条对话,含发送时间、内容、回复内容
- User — 使用系统的用户,数据相互独立
- Person — 联系人(面试官、Coffee Chat 对象),需唯一标识符
- Organization — 公司、社团、组织
- Location — 地点(北京、香港、深圳)
- Jobs / Positions — Jobs = 具体招聘实例(公司+岗位+地点);Positions = 职位大类(后端工程师、产品经理、心理咨询师)
- Fact / Skills — Fact = 事实陈述(「我会 Excel」「我和某人是好朋友」);Skills = 具体技能
- Event — 核心枢纽,连接 Fact / Person / Organization / Jobs;类型含 Application、Coffee Chat、Interview;待定:Event 与 Application 是否拆分
- Resume / Resume Selection — Resume 多版本各有 UUID;Resume Selection 关联特定岗位的简历版本,记录所用的 Facts 或 Experience
四、后续扩展¶
社交 CRM 系统 → 个人管家(健康管理、记账等)
2. 调研结论(你要求的资料检索,均已核实到一手来源)¶
2.1 小程序能不能「定闹钟」——能,但要换个说法¶
| 能力 | 小程序 | 结论 |
|---|---|---|
| 调起系统闹钟 App / 写入闹钟 | ❌ 无此 API | 不可行,别再找了 |
| 写入手机系统日历 + 到点提醒 | ✅ wx.addPhoneCalendar(单次)/ wx.addPhoneRepeatCalendar(重复)基础库 ≥ 2.15.0,授权 scope.addPhoneCalendar参数: startTime(必填, unix 秒)、alarm(默认 true)、alarmOffset(提前秒数)、allDay、repeatInterval(day/week/month/year) |
这就是「闹钟」的正解。写进系统日历后,即使用户不打开小程序、甚至卸载小程序,手机照样响 |
| 主动推送消息 | ⚠️ 订阅消息。一次性订阅:用户每授权一次 = 可下发一条,可累积;长期订阅:仅对政务民生 / 医疗 / 交通 / 金融 / 教育行业开放 | 个人求职助理拿不到长期订阅。必须靠一次性订阅额度经营 |
| 相册 / 拍照 / 录音 / 选文件 | ✅ wx.chooseMedia / wx.getRecorderManager / wx.chooseMessageFile |
满足多模态输入 |
| 后台定时任务 | ❌ 小程序退到后台即冻结 | 提醒必须由服务端发起,不能靠客户端定时器 |
网页版(H5 / PWA)反而更弱:iOS Safari 的 Web Push 必须先「添加到主屏幕」,且后台唤醒不可靠;无法写入系统日历。 → 结论:提醒能力上小程序 > 网页版,网页版的价值在大屏编辑(改简历、看看板),不在提醒。
推荐三层提醒架构(详见 §4.1): 系统日历(离线可靠,硬时间点)+ 一次性订阅消息(在线触达,软提醒)+ 应用内提醒中心(真相来源,永不丢)。
2.2 LangGraph vs OpenAI Agents SDK vs 现状自研¶
| 维度 | OpenAI Agents SDK | LangGraph | Alfred 现状 |
|---|---|---|---|
| 思维模型 | Agent + 托管循环 | 显式状态图(节点+边) | 声明式 Skill(YAML manifest + Action 原语) |
| 状态持久化 | Sessions(SQLite/Redis),无崩溃恢复 | Checkpointer(Postgres),原生持久可恢复 | ingest_request + ingest_run 表,天然持久 |
| Human-in-the-loop | 需自行实现 | 原生(图中断 → 审批 → 续跑) | dry_run → IngestPreview → commit_ingest 已实现 |
| 模型无关 | 偏 OpenAI | 是 | 是(LLMProvider Protocol + Fake 实现) |
关键判断:你的工作流 (a)(b)(c) 是确定性管道 + 一次人工确认,不是自主 Agent 循环。
LangGraph 的核心卖点(durable checkpoint + HITL)在本项目已由 ingest_request / dry_run / commit 等价实现,且更贴合领域语义。
引入任一框架都要重做三条现有铁律:Provider 抽象、Fake-provider 可测、Action 作为唯一落库入口。
→ 详见 §5 与待写的 ADR-00X。
2.3 OpenClaw 的记忆做法¶
OpenClaw 的记忆是 agent workspace 里的纯 Markdown 文件,文件即真相来源。 优点:可读、可手改、可 git diff。缺点:无法 JOIN、无法按结构化条件查询、多用户下无法隔离。
Alfred 的取舍(上一轮已决):记忆落 Postgres(memory 表 + pgvector + tsvector 混合召回 + 时间衰减),
但保留「可导出为 Markdown」这一开源精神——把可读性做成导出能力,而不是存储格式。
3. 🔴 冲突清单:你的概念 vs 已落地的表¶
这一节是本轮最重要的内容。以下 4 条是真冲突,不是措辞差异。
C1 · 「Jobs / Positions」与代码里的 position / occupation 语义互为反义¶
| 概念 | 你的说法 | 代码现状 |
|---|---|---|
| 具体招聘实例(公司+岗位+地点) | Jobs |
position 表(fingerprint = sha256(company_id + normalized_title + location)) |
| 职位大类(后端工程师 / 产品经理) | Positions |
occupation 表 |
position 这个词在你我口中指的是相反的东西。这会一路污染 API 路径、前端字段、prompt 措辞。
现在改成本 = 一次迁移;上线后改成本 = 迁移 + 前端 + 历史数据 + 所有 prompt。
→ 见 Q1
C2 · 「Fact」是新概念,但已有 4 张表都能装下「我会 Excel」¶
现有可承载该语句的表:memory(kind=fact/preference/intent/context)、user_skill、experience、note。
再加一个 fact 就是 5 个地方都可能存同一件事 —— 查询时哪儿都不全,这是数据模型里最难修的一类病。
必须先定一条唯一归属不变量(invariant),再决定要不要建表。
→ 见 Q2
C3 · 「User」= 多租户,但当前是单用户,且唯一约束会全部失效¶
现状:user_skill.user_id 是一个裸字符串,没有 user 表,没有认证,没有隔离。
一旦真的两个人用:
company.slug唯一 → 必须变UNIQUE(owner_id, slug)position.fingerprint唯一 → 同上skill.slug唯一 → 但技能词表到底该不该跨用户共享?(这是个真问题,不是顺手改)- 每一个查询都要加租户过滤,漏一个就是串号级事故
事后补租户列,是数据库改造里最贵的一类。 现在加一列几乎零成本。
→ 见 Q3
C4 · 「Event 是核心枢纽」与现有 application / interview / interaction 分表冲突¶
现状:event 是通用时间轴表;application(12 态状态机 + 外键到简历版本 + 投递凭证)、
interview(轮次 / 面试官多对多)、interaction(Coffee Chat 泛化后的接触记录)各自是强类型表。
你的方案把 Event 当成存储主体。若照做,application 的状态机、外键、约束全部要塞进 JSONB —— 不能约束、不能索引、不能 JOIN。
但你要的「给我看 7 月发生的所有事」确实需要统一时间轴。
→ 见 Q4
C5 · 「Conversation」= 已实现的 chat_thread + chat_message(术语统一即可,非冲突)¶
上一轮已落地:chat_thread(分组/归档)+ chat_message(双方全量原话 + sequence + 软丢弃)+ attachment.chat_message_id。
待决的是上下文管理机制(手动 / 自动分主题 / Session)。→ 见 Q5
C6 · 「Location」当前只是 position.location 字符串(非冲突,是欠债)¶
「HK」「香港」「Hong Kong」现在是三个不同的值,筛选必然漏。→ 见 Q7
C7 · 「Organization」vs 现有 company(非冲突,是收窄)¶
company 装不下学校、社团、NGO、政府机构 —— 而 Coffee Chat 的对象、经历的所属组织经常不是公司。
V2 社交 CRM 更需要泛化。倾向:改名 organization + kind 枚举,成本低(一张表 + 引用它的 5 个外键)。
C8 · 「Resume Selection」≈ 已有 document_version_experience + document_version_bullet¶
结构基本对上。但你多说了一句关键的:「记录其所使用的 Facts 或 Experience」—— 这暗含一个强要求:简历里每一句话都要能溯源。→ 见 Q6
4. 🔴 遗漏清单:你没提但会真出事的环节¶
你问「还有哪些环节遗漏」。以下按「会不会真的坑到你」排序。
M1 · 时区(会真的让你错过截止日期)¶
「8/30 截止」是哪个时区的 23:59?你在香港,投美国岗位,公司在加州。
→ 全库 timestamptz;user.timezone;deadline 必须带来源时区且在提醒时按用户当地时间换算。
M2 · 推送通道账本(小程序订阅额度是稀缺资源)¶
一次性订阅 = 授权一次发一条。这意味着额度是要「攒」的资产,且发了要记账,否则会出现「提醒发不出去且你不知道」。
→ 需要 notification_channel(用户开通了哪些通道)+ notification_delivery(每条 nudge 走了哪个通道、成功与否、额度扣减)。
当前 nudge 表只有生成,没有投递记录。这是 §2.1 三层架构的落库前提。
🟢 本轮决策(见 §8.2):nudge_rule / nudge(提醒规则与生成)本轮建;notification_channel / notification_delivery 账本本轮 defer,但 nudge 预留 channel 字段(email/digest/miniprogram)保证可扩展。
推送主通道 = 后端邮件 + 每日聚合一条(绕开小程序订阅额度稀缺问题),不强制在小程序内做推送。
M3 · 撤销与纠错(记忆一旦记错会污染很久)¶
现在只有单条软删除。真实场景是:「刚才那条你全记错了,撤销。」
→ ingest_request 需要整体回滚能力:一次 ingest 产生的所有实体带同一个 origin_request_id,可一键 revert。
M4 · 幂等(弱网重发)¶
小程序在地铁里发语音,超时重发 → 两条投递记录。
→ 客户端生成 idempotency_key,服务端 UNIQUE。
M5 · 成本护栏¶
每条语音 = STT + LLM + embedding。ingest_run 记了 token,但没有闸门。
→ per-user 月度预算 + 超限降级(关掉 embedding / 换小模型 / 排队)。
M6 · 隐私与 PII(多用户上线即触发)¶
你存的是别人的信息:面试官姓名、Coffee Chat 对象的联系方式。小程序上架要隐私协议,且要能导出/删除。
→ person 需要 pii_level;需要 export / purge 端点;GLOSSARY 里要定义什么算 PII。
M7 · AI 产出物的反向归属¶
你说「AI 也会向用户反馈文件、图片或链接」。当前 attachment.chat_message_id 只表达「用户发来的」,
没有「AI 生成的 PDF 简历属于哪条回复」。
→ attachment 需要区分 direction: inbound | outbound,或 produced_by_message_id。
M8 · 附件生命周期¶
语音和截图会以每天几十条的速度堆积。 → 冷存储 / 转写后原音频降采样 / 过期归档策略。
M9 · 抽取质量的回归基准(Evals)¶
改一次 prompt,抽取质量是升是降,现在没有任何客观信号。 → 建 golden set(20~50 条真实输入 + 期望结构化输出),CI 跑准确率。
M10 · 领域无关内核 vs 求职专用表(愿景 §0 的直接推论)¶
person / event / fact 现在都带着求职语境。V3 要管健康记账时,是「同一个库加表」还是「同一内核 + 领域包」?
这个决定影响现在要不要把 event / fact 做成领域无关的。 现在选错,V3 要重来。
M11 · 「用户确认」的疲劳度¶
工作流 (b) 说「用户确认后入库」。如果每条语音都弹确认,用完三天就烦了。
→ 需要置信度分级:高置信度直接入库(事后可撤),低置信度才拦下确认。这需要 ingest_run.confidence 与阈值配置。
5. 🔴 待决问题台账¶
| # | 问题 | 状态 | ADR |
|---|---|---|---|
| Q1 | 命名体系:job_posting(实例) + occupation(大类),position 退役 |
🟢 已决 | ADR-001 |
| Q2 | Fact 归属:强类型/状态表为真相,memory 残余;fact 为通用状态真相 |
🟢 已决 | ADR-002 |
| Q3 | 多用户:私有表带 user_id,共享实体/词表表不带 |
🟢 已决 | ADR-003 |
| Q4 | Event(行为真相) 与 Fact(状态真相) 并列互补,非派生;Application 独立表 | 🟢 已决 | ADR-002 |
| Q5 | 上下文:常驻单 Session + AI 自动分段 + 人工干预,混合上下文组装 | 🟢 已决 | ADR-005 |
| Q6 | 简历溯源:软引用 + 渲染强制 footnote + 校验阻断无源(折中) | 🟢 已决 | ADR-006 |
| Q7 | Location:建 location 实体 + location_alias,结构化地理字段 |
🟢 已决 | ADR-007 |
| Q8 | Organization 泛化 company → organization(kind 枚举) 本轮做 |
🟢 已决 | ADR-009 |
| Q9 | 编排框架:LangGraph(Python) 编排 + Action 执行,不用 OpenAI SDK | 🟢 已决(重要转向) | ADR-004 |
| Q10 | 领域无关内核的边界划在哪(M10) | 🟢 已决(内核 = event/fact/person/org/location/skill;领域包 = job_posting/application/interview/experience) | ADR-002/008 |
| Q11 | job_posting 是否归入共享表(多人可投同一 posting) |
🟢 已决(是,共享;其 application 私有,见 §6.4) | ADR-003 |
6. 已决模型 · 第一轮拷问结论(Q1–Q4)¶
2026-08-03 用户拍板。这是 V1 数据模型的四根支柱,后续一切表结构都建立在这之上。
6.1 Q1 · 命名体系(🟢 已决 · ADR-001)¶
occupation= 职位大类(长期稳定、跨公司可比):software engineer / data scientist / psychologist。job_posting= 具体招聘实例(公司 + occupation + 地点 + 唯一 ID / fingerprint)。- 现有
position表重命名为job_posting;occupation表保留。 - 反义歧义彻底消除:
position一词在本项目退役,不再用于任何 API / 前端 / prompt。 - 含义固化:
occupation回答「这是哪一行」,job_posting回答「这家公司这个地点这个岗位的这一次招聘」。
6.2 Q4 · Event 与 Fact 的并列真相模型(🟢 已决 · ADR-002,最核心)¶
这一条推翻了我之前「Event = 派生时间轴投影」的假设,是本轮最重要的定论。
Event = 行为的真相(Source of Truth for Behavior)
- 不可变行为日志(append-only, immutable)。记录「发生了什么」,不记录「现在是什么状态」。
- 用户说了什么、做了什么、AI 识别了什么语义事件,对「发生了什么」而言 Event 就是真相。
- Events are the immutable source of truth for behavior — what happened, not state.
Fact = 状态的真相(Source of Truth for State)
- 从 Event + 业务规则物化(materialize)出来的当前状态快照。
- 对「现在是什么样 / 某时间段是什么样」而言 Fact 就是真相。
- 带
valid_from→valid_to,支持纠错(某条 Fact 被改正时,旧值封口、新值开区间)。 - Facts are the materialized source of truth for states — what is true now or in a certain period, derived from events + business rules, correctable via valid_from→valid_to.
二者并列互补,不是「谁派生谁」:
- Event 不是 Fact 的索引,Fact 也不是 Event 的投影。两套独立真相,通过
fact.source_event_id双向可追。 Application/Interview/Interaction各自是强类型表,不是 Event。- Event 只记录「Application Submitted 这个行为」;该投递的实际数据(岗位、简历版本、凭证、状态机)仍在
application表。 - 即:Event 回答「什么时候发生了投递行为」,
application回答「这次投递的细节」。
落到表结构(领域无关,呼应愿景 §0):
event (append-only 行为日志,领域无关)
id, type ENUM, actor_user_id, occurred_at,
source_type (conversation|import|api), source_ref_id,
payload JSONB -- 指向本次行为创建的实体: {fact_id?, person_id?, application_id?, ...}
fact (物化状态快照,领域无关)
id, kind ENUM (skill_declaration|relationship|attribute|...),
valid_from, valid_to,
subject_ref, object_ref, -- 指向 person/skill/organization 等
proficiency (nullable, 技能类 Fact 用),
source_event_id, source_conversation_id,
owner_user_id -- 私有
6.3 Q2 · Fact 的归属(🟢 已决 · ADR-002)¶
强类型/状态表是真相,memory 是残余。结合 6.2,细化「我会 Excel」的完整链路:
- 原话:「我会 Excel」存于
chat_message(永久留存,呼应愿景「原话永久留存」)。 - 行为:AI 识别为「用户声明了一个事实」→ 写一条
event(type=FACT_DECLARED, source=conversation, conversation_id, fact_id)。 - 状态:物化出一条
fact(kind=skill_declaration, subject=我, object=Excel skill, proficiency=high, valid_from=now)。 proficiency的量表定义在 fact 里(每个 skill 的 proficiency scale 可不同)。- 强类型投影:为支持 JOIN 式技能匹配,同步 upsert
user_skill(user, excel, proficiency=high)。user_skill是fact中「技能声明」类目的查询优化投影,二者通过约定保持同步。 - 残余:无法归入强类型的自由内容 →
memory(kind=fact/preference/intent/context)。
一致性不变量(invariant):凡落在受控词表(has_skill / worked_at / knows_person)的声明,同时物化到
fact与对应强类型表;强类型表是查询真相,fact是带时间轴与溯源的通用状态真相,memory仅收容残余。
6.4 Q3 · 多用户边界(🟢 已决 · ADR-003)¶
规则:私有表每张带 user_id / owner_id;共享表不带(它们是跨用户的真实世界实体 / 词表)。
| 类别 | 表 | 是否带 user_id |
|---|---|---|
| 共享(跨用户真实实体 / 词表) | user(账户)、person、organization、occupation、skill、skill_alias、location |
❌ 不带 |
| 私有(「我」的活动与状态) | conversation、chat_message、event、fact、application、interview、experience、experience_source、user_skill、document_version、document_version_experience、resume_asset、note、memory |
✅ 带 user_id |
待确认(Q11):job_posting 是真实招聘实体,是否归入共享表(多人可投同一 posting)?倾向:是,但其 application 仍私有。等待用户确认后写入 ADR-003。
6.5 对现有 plan 的修正¶
- 推翻 plan 中「Event = 派生时间轴投影」的表述 → 改为「Event = 不可变行为真相,与 Fact 并列」。
- 新增
fact为领域无关的状态真相表(带 valid_from/valid_to、source_event_id、proficiency)。 user_skill定位从「真相」调整为「fact中技能声明的查询投影」。- 现有
position表重命名为job_posting(见 §3 C1)。
7. 已决模型 · 第二轮拷问结论(Q5–Q11)¶
7.1 Q5 · 上下文管理(🟢 已决 · ADR-005)¶
管家类 App 的最佳形态:常驻单 Session + AI 自动分段(segment)+ 人工干预。
- thread 永不变:每用户一个常驻
chat_thread,不做严格 Session 隔离(太割裂)。 - thread 内切成语义 segments:AI 检测主题漂移自动开新 segment;用户可手动重命名 / 合并 / 归档。
- 上下文组装 = 混合模式:每次请求时,拼装
(相关 segment 的摘要) + (当前 segment 最近 N 轮) + (检索到的相关 fact/event)。 不纯手动(太累),也不无限常驻(token 爆炸)。这是「带分段的时间线」。 - 落到表:
chat_thread:每用户一个常驻,无频繁切换。chat_segment(新增):id, thread_id, title, started_at, ended_at(nullable), status(active|archived|merged), summary_md, user_id。chat_message.thread_id/segment_id外键化(现状已有 thread_id)。- context-assembly 服务:输入一条新消息 → 召回相关 segment 摘要 + 当前 segment 近 N 轮 + 向量/BM25 召回 fact/event → 组装成 LLM context。压缩/摘要按 segment 粒度进行,不丢历史原话。
7.2 Q6 · 简历溯源(🟢 已决 · ADR-006)¶
折中方案(非每句硬绑、非完全不溯源):
- 结构化
fact是唯一真相源。 - 简历 section 软引用 每个 fact(多对多 link 表,不强制每句死绑一个源)。
- 渲染时强制 footnote:给用户返回的简历,每处内容附来源标注。
- 校验时阻断无源内容:validator 拦截没有任何 source 的 bullet,不通过。
- 允许 polishing:按目标岗位要求做润色,但必须基于 fact(不凭空生成能力)。
落到表:
- document_version_fact(新增 link):id, document_version_id, source_type(fact|experience), source_id, section, bullet_text。
- 渲染层追加 footnote([src: fact:xxx]);校验层设 require_source=True 闸门。
- 呼应愿景「每个结论要能回答凭什么这么说」。
7.3 Q7 · Location(🟢 已决 · ADR-007)¶
- 建
location实体:id, slug, name, country, city, district, admin_code, ...(结构化字段,可对接地图检索)。 - 建
location_alias:canonical_location_id, alias_slug, source(agent|manual)。 - 香港 / 香港特区 / Hong Kong / HK → 同一
location行,别名入location_alias。 job_posting.location_id改为 FK 指向location(替代原字符串字段)。- 与 Q3 一致:
location/location_alias为共享表(不带 user_id)。
7.4 Q9 · 编排框架(🟢 已决 · ADR-004,重要转向)¶
采用 LangGraph(Python)作为主编排骨架,自研一层极薄的 Agent Runtime 封装作为接口;不采用 OpenAI Agent SDK。
用户理由:需要的是有状态、可中断、可审计、多 Workflow 共存、可绑 PostgreSQL 的「个人 OS」,不是全自动工具调用 Agent。LangGraph 原生提供 Postgres checkpointer(持久可恢复)+ interrupt/resume(解析后等用户确认再落库),契合工作流 (a)(b)(c)。
关键约束(架构师补充,必须写进 ADR-004):
- LangGraph 只做编排层:图定义 workflow、拥有 checkpoint 与 HITL 中断;每个 graph 节点在需落库时调用现有 ActionExecutor。
- 执行层复用现有 Action 原语:保持「Action 是唯一落库入口」「dry_run/commit 语义」「Fake-provider 可测」「模型无关」四条铁律不被破坏。即「LangGraph 管状态与中断,Action 管写库」。
- 测试兼容:Agent Runtime 封装须抽象 graph,使测试可用 LangGraph in-memory checkpointer + 现有 SQLite 内存库跑通,不依赖真实 PG。
- 对现有 plan 的修正(必须重做实现计划):推翻 plan「沿用声明式 Skill+Action 作为唯一编排」的表述 → 改为「LangGraph 编排 + Action 执行」双层。tech-stack 与目录结构需调整:新增 alfred/runtime/(LangGraph graph 定义 + checkpointer 绑定 + 薄 Runtime 接口);skills/ 的 YAML manifest 退化为 node/Action 配置或保留为 Action 定义。现有 ingest_request / dry_run / commit_ingest 机制保留并下沉为 LangGraph 节点的执行单元,而非被替换。
7.5 Q10 · 领域无关内核边界(🟢 已决 · ADR-002/008)¶
结合 Q4 的并列真相模型,明确内核与领域包的边界:
- 领域无关内核(通用,V3 管家直接复用):
event(行为) /fact(状态) /person/organization/occupation/skill/skill_alias/location/chat_thread/chat_segment/chat_message/memory/reminder/nudge/nudge_rule。 - 求职领域包(V1 专属,未来可平行加健康包/记账包):
job_posting/application/interview/experience/experience_source/user_skill/document_version/document_version_experience/document_version_fact/resume_asset。 - 原则:内核表不带求职专有字段;新增领域时不改内核,只加领域包表并通过
fact.kind/event.type的枚举扩展接入。这保证了 V3「同一内核 + 领域包」的演进路径。
7.6 Q11 · job_posting 共享性(🟢 已决 · ADR-003)¶
job_posting 是真实招聘实体,归入共享表(多人可投同一 posting,不重复建行);但其 application(我的投递)仍私有、带 user_id。organization / occupation / location 同理为共享。
8. 已决模型 · 第三轮拷问结论(Q8 / M2)¶
8.1 Q8 · Organization 泛化(🟢 已决 · ADR-009)¶
现有 company 装不下学校/社团/NGO/政府/基金/开源办公室等。本轮泛化:
company表重命名为organization,新增kind枚举:company/school/club/ngo/government/fund/open_source_office/other。- 覆盖:港大/中大(school)、红十字会/入境处(ngo/government)、社团(club)等。
- 引用它的 5 个外键(position→job_posting、experience、person.employer 等)同步改指向
organization。 - 与 Q3 一致:
organization为共享表(不带 user_id)。
8.2 M2 · 提醒投递账本(🟢 已决 · 部分本轮)¶
- 本轮建:
nudge_rule(规则定义)+nudge(生成的提醒实例)+ 定时扫描器。与用户手设的reminder分表不混。 - 本轮 defer:
notification_channel/notification_delivery账本不建,但nudge预留channel字段(枚举:email/digest/miniprogram)保证未来可扩展。 - 推送主通道:后端邮件 + 每日聚合一条,绕开小程序一次性订阅额度稀缺问题;不强制在小程序内做推送。
- 理由:提醒规则本身(何时投递、何时 follow-up)是 V1 核心价值,必须本轮有;而额度记账是「接小程序推送时」才需要的工程细节,先留接口即可。