docs: docs/32、docs/33 让号至 37、38(主干续占 32–36);同步引用

主干 `qyqy_develop` 已占用 29–36(29 登录接口 / 30 投顾迁移TODO / 31 投顾灰度 /
32 平台侧交接与联调准备 / 33–35 ZSY 底座扩展确认往来 / 36 PR7 合并记录),
本线原用的 32、33 与之重号。按"以架构师为主线"继续让号。

- `docs/32-记忆投影链路实现说明.md`       → `docs/37-记忆投影链路实现说明.md`
- `docs/33-架构对齐-记忆与画像投影链路.md` → `docs/38-架构对齐-记忆与画像投影链路.md`
  (两次 `git mv`,保留文件历史)
- 同步更新 `AGENTS.md` 内 2 处引用、`docs/38` 内 2 处交叉引用
- 文档内部无自引用编号;全仓已无其它 `docs/32`、`docs/33` 引用(全量搜索确认)

验证:`tools/check_authoritative_docs.py` → checked 41 documents, no number collision
This commit is contained in:
2026-09-12 13:00:30 +08:00
parent 57f56bbcbe
commit 3e081addf0
3 changed files with 6 additions and 5 deletions
@@ -0,0 +1,203 @@
# 架构对齐 · 记忆→画像→图 这条链(ZSY_develop vs 主干)
> **目的**:`ZSY_develop` 分支(张胜宇,最后更新 2026-09-11 20:45)包含一整套画像投影实现,
> 而主干 `qyqy_develop` 上也有同类实现。**在合并之前必须先做一次架构对齐**,否则两套会互相覆盖。
> **本文只做核对与建议,不改任何代码**。
>
> **⚠️ 后续(2026-09-12)**:本文的结论**已落地实施**,实施说明与验证证据见
> `docs/37-记忆投影链路实现说明.md`。本文保留作为**决策依据** —— 它记录了
> "为什么选方案 A(复用主干图投影、不引入第二套)"、"为什么由生产端组装 `memory_sources`"
> 这两个决定的原始核对过程与判据,这部分推演在 `docs/37` 里没有重复。
>
> **编号说明**:本文原为 `docs/29`;2026-09-12 让号给架构师线的
> `docs/29-Agent组员登录接口使用说明.md`,先改号 `docs/33`,后又因主干续占 32–36
> 再让号至 `docs/38`(本文的实际编号以文件名与 `AGENTS.md` 为准)。
>
> **核对时间**:2026-09-11 晚 **主干**:`qyqy_develop` @ `bbf623a` **ZSY**:`cbcf7c7`
---
## 一、最重要的发现:主干那条链**已经接好了**(走的是另一条 outbox)
之前(包括架构师的评审意见里)的说法是"**组件写好了、线没接**"。**核对后这个说法不准确** ——
主干上有一条**完整且已在装配层接线**的链路,只是它走的不是 `memory_sync_outbox`:
```
agent.run_completed
↓ (app/worker/runtime.py 的 handler 字典,已注册)
memory.extraction_requested
↓ dispatch_memory_extraction
MemoryExtractionService → 提升成 user_facts
↓
profile.rebuild_requested
↓ dispatch_profile_rebuild(runtime.py L175-197)
ProfileAssemblyService.rebuild() → 重建画像快照
ProfileGraphProjectionService.project_customer() → 投影到 Neo4j
```
**这条链的引入者与时间**:
```
d7f6ef7 09-10 21:52 卿云秋月(架构师) feat: 记忆→画像→图全自动触发
```
⇒ **架构师 09-10 就做完了"全自动触发"**,并且是**直接接在 `runtime.py` 的 handler 字典里**的。
`app/worker/runtime.py` 的 handler 白名单**包含** `"profile.rebuild_requested"` —— 这就是"线接上了"的证据。
### 1.1 与 ZSY 那条链的关系:**两条平行的 outbox**
| | 主干(架构师的链) | ZSY(张胜宇的链) |
|---|---|---|
触发事件 | `profile.rebuild_requested` | `memory_sync_outbox` 表里的行 |
存储 | **`domain_event_outbox`**(领域事件表) | **`memory_sync_outbox`**(记忆同步表) |
消费方式 | `WorkerRuntime` 的 handler 字典(**已注册**) | 独立的 `MemorySyncOutboxWorker` |
消费者装配 | `app/worker/__main__.py` → `WorkerRuntime(...)` | `app/worker/__main__.py` → `MemorySyncOutboxWorker({...})` |
投影目标 | **Neo4j**(`ProfileGraphProjectionService` + `relationships`) | **Milvus + Neo4j**(两个 adapter) |
画像生成 | `ProfileAssemblyService`(从 `user_facts` 组装) | `CustomerProfileCandidateService`(候选 → 复核 → 已批准快照) |
**⇒ 两条链做的是同一件事,但走不同的 outbox、不同的画像生成方式、不同的消费装配。**
### 1.2 现场数据(我这边实测)
| 表 | 行数 | 说明 |
|---|---|---|
`user_facts` | **0** | 事实层为空 ⇒ 主干那条链**从没跑过**(或跑过但被清理) |
`memory_unit` | **0** | 记忆单元为空 |
`profile_snapshots` | **5** | 我的 `seed_profile_demo.py` 种的演示数据 |
`memory_sync_outbox` | **2** | **我的 `profile_generation_service` 写的**,`status='待处理'`,无人消费 |
**⇒ 关键因果链**:`user_facts` 为空 → 主干链没产出过东西;`memory_sync_outbox` 的 2 行是**我的**生产者写的。
### 1.3 `graph_projection_worker.py` 的真相
```
app/worker/graph_projection_worker.py → GraphProjectionWorker 类
git grep "GraphProjectionWorker(" → 零处实例化
```
**它是死代码**(或备用路径)。架构师说的"`GraphProjectionWorker` 没有实例化点"**是对的**;
但由此推论"整条链没接"**不对** —— 接的是 `runtime.py` 的 handler,不是这个类。
---
## 二、ZSY 分支带来的**真正增量**(主干确实没有的)
逐文件核对后,ZSY 分支**独有且主干没有**的只有这些:
| 文件 | 作用 | 主干有没有等价物 |
|---|---|---|
`app/worker/memory_sync_outbox_worker.py` | `memory_sync_outbox` 的消费者(handler 注入 + 重试 + 死信) | **无**(主干消费的是另一个 outbox) |
`app/infrastructure/milvus_profile_projection.py` | 画像投影到 **Milvus** | **无**(主干只投影到 Neo4j) |
`app/infrastructure/neo4j_profile_projection.py` | 画像投影到 Neo4j(另一个实现) | 🟡 主干有 `ProfileGraphProjectionService` |
`app/service/knowledge_publication_service.py` | 知识**发布**状态机(草稿→审核→发布) | 🟡 主干有 `knowledge_ingest_service` + 审核字段 |
`app/service/knowledge_authority.py` / `knowledge_config.py` | 知识权威来源与运行期配置 | **无** |
`app/service/customer_service_session_memory_service.py` | 客服**多轮会话记忆** | 🟡 主干有 Redis 短期记忆 + `docs/24` 说多轮已跑通 |
`app/service/customer_service_handover_*.py` | 转人工上下文与后台 | 🟡 主干有 `handover-requests` 端点 + 工单表 |
`app/service/customer_profile_candidate_service.py` | 画像**候选 → 复核 → 批准**工作流 | **无**(我的 `profile_generation_service` 是直接生成快照) |
`app/worker/customer_profile_candidate_worker.py` | 候选流程的 worker | **无** |
`app/service/agent/customer_service_agent.py` | 他自己的客服 Agent | 🔴 **主干已有**(`implementations/customer_service.py`,老师验收第 4/5/6 条靠它) |
`app/service/agent/customer_service_routing.py` | 他自己的路由 | 🔴 主干已有 `core/customer_service_rules.py` |
`app/service/knowledge_tool_service.py` | 知识工具(另一份) | 🔴 主干已有 `knowledge_tool.py` |
---
## 三、四个必须对齐的架构分歧
### 分歧 1:用哪条 outbox 承载"记忆→画像→图"?
- **主干**:`domain_event_outbox` + `profile.rebuild_requested`(**已在 handler 白名单里**,架构师 09-10 接的)
- **ZSY**:`memory_sync_outbox` + `MemorySyncOutboxWorker`
**我的判断**:`memory_sync_outbox` 是 `docs/00` 基线里定义的表(**存在即有其设计意图**),
而主干那条链并没有消费它 —— 所以**两套有各自的合法位置,不该二选一,而该明确分工**:
| 场景 | 应该走哪条 |
|---|---|
记忆内容变化 → 画像重建 → 图投影 | **主干那条**(`profile.rebuild_requested`,已接线、已验) |
画像快照 → **同步到 Milvus 向量库**(用于语义召回) | **需要一个消费者**,这正是 ZSY 的 `milvus_profile_projection` 补的 |
⇒ **建议**:主干链保留;ZSY 的 **Milvus 投影适配器**接进主干链的尾部(或接成 `memory_sync_outbox` 的消费者,
但那样要明确"这两个 outbox 各自负责什么",否则就是两套并存)。
### 分歧 2:画像生成"直接快照"还是"候选→复核→批准"?
- **我的**:`profile_generation_service.py` —— 直接生成快照(符合 `docs/00` §6.4.6 的事务口径)
- **ZSY 的**:`customer_profile_candidate_service.py` —— 先生成候选、人工/规则复核后才成为正式快照
**我的判断**:这是**业务裁决**,不是技术裁决。候选复核流程更严(金融场景可能更合适),
但**成本更高**(需要复核人、需要审核界面)。**建议由架构师或业务方定**,不要由合并动作决定。
### 分歧 3:两套客服 Agent
- **主干**:`implementations/customer_service.py`(**已进主干**,老师验收第 4/5/6 条靠它)
- **ZSY**:`agent/customer_service_agent.py`
**我的判断**:**这是最危险的一条**。主干那套已通过验收(产品咨询 5/5、政策 3/3、多轮 3 轮),
ZSY 那套是从旧基线写的、**落后主干 144 个提交**。**不能因为合并把它顶掉。**
### 分歧 4:ZSY 落后主干 144 个提交 —— 他的改动里有大量"过期内容"
ZSY 的分叉点是 `c2178a9`(09-11 09:49),而它改过 68 个与主干重叠的文件,其中包含:
```
app/service/tool_executor.py、app/service/agent/base.py、app/service/agent/governance.py、
app/worker/runtime.py、app/service/public_platform_service.py、app/main.py、app/core/contracts.py
```
这些文件在主干上**已经过多轮修改**(风控 P3、投顾线、我的画像线)。**整体合并会把他的旧版本顶回主干。**
---
## 四、建议的收口方案(分三步,风险递增)
### 第 1 步:**只移植两个投影适配器**(低风险、有明确收益)
| 移植什么 | 从哪来 | 落到哪 |
|---|---|---|
Milvus 画像投影适配器 | `app/infrastructure/milvus_profile_projection.py` | 同一路径(主干没有同名文件) |
(可选)Neo4j 投影改进 | `app/infrastructure/neo4j_profile_projection.py` | 与主干 `ProfileGraphProjectionService` 比对后再定 |
测试 | `tests/unit/infrastructure/test_milvus_profile_projection.py` | 同路径 |
**为什么安全**:这两个文件在主干**不存在** ⇒ 零冲突;它们是纯适配器(无业务改动)。
### 第 2 步:**`memory_sync_outbox` 的消费者**(中风险,需要先定分工)
两种做法,**必须选一种**:
| 做法 | 说明 |
|---|---|
**2a** | 把 ZSY 的 `MemorySyncOutboxWorker` 接进 `app/worker/__main__.py`(他那套本来就是这么设计的)—— 直接能用,但要明确它与主干链的分工 |
**2b** | 不引入新 worker,而是把主干链的尾部补上"同时写 Milvus"(改 `dispatch_profile_rebuild`)—— 单一链路更简单,但要改主干的已验代码 |
我倾向 **2a**,理由:`memory_sync_outbox` 是基线表,让它有自己的消费者更符合原本设计;
且不用改主干已验的那条链。
### 第 3 步:**其余部分对齐后再谈**(需要架构师/业务裁决)
- 画像**候选复核流程**要不要采纳(分歧 2)
- ZSY 的**客服 Agent / 知识工具 / 路由**是否全部放弃(分歧 3)—— 我建议放弃
- ZSY 的**知识发布状态机**与主干的知识入库/审核是否合并
---
## 五、给架构师/张胜宇的四个问题
1. **`memory_sync_outbox` 与 `profile.rebuild_requested` 的分工是什么?**
(两个 outbox 都在用,但谁负责什么没写下来。我在 `AGENTS.md` 里已经记了"两个 outbox 不能混",
但现在**两条链各用一个**,需要明确边界。)
2. **画像生成要"直接快照"还是"候选复核"?** 这是业务裁决。
3. **ZSY 的客服 Agent 要不要保留?** 主干那套已通过老师验收第 4/5/6 条,我建议以主干为准。
4. **ZSY 分支怎么收尾?** 它落后主干 144 个提交 —— 建议**不整体合并**,改为"按需移植 2-3 个文件"
(投影适配器 + outbox worker + 对应测试),其余明确标记为"已被主干实现取代"。
---
## 六、我这边确认不做的事
- **不改任何代码**(本文只是对齐)
- **不整体合并 ZSY 分支**(会让 144 个提交的旧改动回退主干)
- **不动主线那条已验链路**(`profile.rebuild_requested` 全自动触发)
---
*核对依据:`git ls-tree` 逐文件比对 + `git grep` 引用追踪 + 现库 `information_schema` 与行数实测。*