docs(iit): Add IIT Manager Agent V2.9 development plan with multi-agent architecture

Features:
- Add V2.9 enhancements: Cron Skill, User Profiling, Feedback Loop, Multi-Intent Handling
- Create modular development plan documents (database, engines, services, memory, tasks)
- Add V2.5/V2.6/V2.8/V2.9 design documents for architecture evolution
- Add system design white papers and implementation guides

Architecture:
- Dual-Brain Architecture (SOP + ReAct engines)
- Three-layer memory system (Flow Log, Hot Memory, History Book)
- ProfilerService for personalized responses
- SchedulerService with Cron Skill support

Also includes:
- Frontend nginx config updates
- Backend test scripts for WeChat signature
- Database backup files

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-05 22:33:26 +08:00
parent 4b9b90ffb8
commit 0c590854b5
27 changed files with 7279 additions and 7 deletions

View File

@@ -0,0 +1,161 @@
# **IIT Manager Agent V2.7:基于“双层文本记忆”的极简架构**
**版本:** V2.7 (Clawbot Memory Edition)
**日期:** 2026-02-05
**核心变更:** 废弃复杂的 pgvector 语义检索,采用 Clawbot 式的“流水账 \+ 沉淀物”双层文本记忆。
**优势:** 记忆可读、可改、透明、零黑盒。
## **1\. 记忆架构重构 (Memory Refactoring)**
我们将记忆分为两层,完全模拟人类大脑运作,且摒弃不可解释的向量数据。
graph TD
subgraph "层级 1: 流水账 (Daily Stream)"
Log\[Conversation History\]
Note\[Raw Actions\]
desc1\[特点: Append-Only, 巨量, 易遗忘\]
end
subgraph "层级 2: 沉淀物 (The Sediment)"
ProjectMem\[项目级 MEMORY.md\]
PatientMem\[患者级 MEMORY.md\]
UserMem\[用户级 MEMORY.md\]
desc2\[特点: 精炼, 结构化, 人类可编辑\]
end
Log \--\>|每晚 Cron 归纳 (Gardening)| ProjectMem
Admin\[管理员/医生\] \--\>|手动修正/编辑| ProjectMem
ProjectMem \--\>|注入 System Prompt| LLM
## **2\. 数据库设计变更 (Schema Changes)**
### **2.1 新增 iit\_memory\_files 表**
我们不用磁盘文件,用数据库表来模拟文件,方便 Web 端管理。
model IitMemoryFile {
id String @id @default(uuid())
projectId String
targetType String // PROJECT | PATIENT | USER
targetId String // 对应的 ID (如 projectId, recordId, userId)
// 核心:这就是那个 MEMORY.md 的内容
content String @db.Text
lastUpdatedBy String // 'system\_daily\_job' 或 'user\_001'
updatedAt DateTime @updatedAt
@@unique(\[projectId, targetType, targetId\])
@@map("iit\_memory\_files")
@@schema("iit\_schema")
}
### **2.2 废弃计划**
* **废弃**iit\_conversation\_history 中的 embedding 字段。
* **废弃**Phase 5 中的向量检索服务。
## **3\. 核心服务实现MemoryService (V2.7)**
### **3.1 读取记忆 (Read)**
在 ChatService 启动对话前,简单的把文本读出来拼接到 Prompt 里。
// backend/src/modules/iit-manager/services/MemoryService.ts
export class MemoryService {
async getContext(projectId: string, userId: string, recordId?: string) {
// 1\. 获取项目级记忆 (规则、偏好)
const projectMem \= await this.loadMemory('PROJECT', projectId);
// 2\. 获取用户级记忆 (习惯)
const userMem \= await this.loadMemory('USER', userId);
// 3\. (可选) 获取患者级记忆
const patientMem \= recordId ? await this.loadMemory('PATIENT', recordId) : '';
return \`
\=== 长期记忆 (Long-Term Memory) \===
\[项目背景\]:
${projectMem}
\[用户偏好\]:
${userMem}
\[患者备注\]:
${patientMem}
\================================
\`;
}
private async loadMemory(type: string, id: string) {
const mem \= await prisma.iitMemoryFile.findUnique({
where: { projectId\_targetType\_targetId: { ... } }
});
return mem?.content || '';
}
}
### **3.2 记忆维护 (Gardening \- The Clawbot Way)**
这是最精彩的部分。我们不实时更新长期记忆(太乱),而是每天晚上让 AI 当“园丁”,修剪记忆。
**任务Daily Memory Consolidation**
* **触发**:每天凌晨 2 点。
* **输入**:当天的对话流水 (iit\_conversation\_history) \+ 旧的 MEMORY.md。
* **Prompt**"这是今天的对话流水。请提取其中新的重要事实(如用户偏好、新的关键决策、患者的新状态),合并到旧的记忆文件中。保持 Markdown 格式。如果没有新信息,保持不变。"
* **输出**:新的 Markdown 文本,覆盖数据库。
// SchedulerService.ts
async runDailyMemoryConsolidation() {
const logs \= await this.getDailyLogs();
const oldMem \= await this.memoryService.loadMemory('PROJECT', projectId);
const newMem \= await this.llm.chat(\[
{ role: 'system', content: '你是记忆整理员...' },
{ role: 'user', content: \`旧记忆:\\n${oldMem}\\n\\n今日流水:\\n${logs}\` }
\]);
await this.memoryService.saveMemory('PROJECT', projectId, newMem);
}
## **4\. 调整后的开发计划 (Phase 5 重构)**
我们将原计划 Week 5 的“向量检索”替换为“文本记忆系统”。
### **Week 5Clawbot 式记忆系统**
| 时间 | 任务 | 说明 |
| :---- | :---- | :---- |
| **Day 24** | **记忆表结构与 CRUD** | 创建 iit\_memory\_files 表,实现读写 API。 |
| **Day 25** | **记忆注入 Prompt** | 修改 ChatService在 System Prompt 头部注入 Markdown 内容。 |
| **Day 26** | **记忆整理 Worker** | 实现“每日记忆整理”的 Cron Job (pg-boss)。 |
| **Day 27** | **记忆编辑 UI** | 在 Admin 后台增加一个简单的文本框,允许人工修改 MEMORY.md。 |
## **5\. 为什么这比向量库好?**
1. **完全透明 (White Box)**
* 向量库出了问题(比如 AI 突然变傻),你只能重新 Embed甚至不知道哪条数据坏了。
* 文本记忆出了问题,你打开 Admin 界面,看到一行:“用户不喜欢红烧肉”,你把它删了,问题立刻解决。**这对医疗系统排查问题是无价的。**
2. **Token 可控**
* 向量库如果不加限制,可能召回 5000 字的无关内容。
* 文本记忆由“每日整理”压缩过通常只有几百字的核心干货Token 消耗极低。
3. **技术栈简化**
* 不需要 pgvector 插件(虽然你们装了,但不用也没关系)。
* 不需要 Embedding 模型调用。
* 纯字符串处理Node.js 最擅长。
## **6\. 结论**
这个建议**极具战略价值**。它把一个“高科技难题”(如何做语义检索)变成了一个“管理学问题”(如何整理笔记)。
**执行建议:**
全盘接受这个建议。在 V2.6 架构中,**移除 Embedding 层,替换为 Memory File 层。** 这会让你们的系统在面对 PI 的刁钻问题时,显得更有“记性”,同时让你们的运维工作变得无比轻松。

View File

@@ -0,0 +1,134 @@
# **IIT Manager Agent V2.8:基于“周报卷叠”的终极记忆架构**
**版本:** V2.8 (The Chronicle Edition)
**日期:** 2026-02-05
**核心变更:** 采纳“以周报代替向量检索”的建议。利用 LLM 的长窗口能力,通过周期性压缩数据,实现全生命周期的可读记忆。
**适用场景:** 1-3 年周期的临床研究项目。
## **1\. 核心理念:把“大数据”变成“厚书”**
我们不再试图在海量碎片数据中大海捞针Vector Search而是把数据写成一本\*\*“编年史”\*\*。
* **每日**:忠实记录流水账。
* **每周**LLM 阅读本周流水账,写一页“历史书”(周报)。
* **查询时**LLM 直接阅读整本“历史书”。
### **优势分析**
| 维度 | 向量检索 (V2.6/2.7) | 周报卷叠 (V2.8) |
| :---- | :---- | :---- |
| **准确性** | 模糊匹配,容易漏掉关键细节 | **全量阅读**,拥有上帝视角,极准 |
| **可解释性** | 黑盒向量,不知道 AI 看了啥 | **白盒周报**,医生可以随时翻阅、修正周报 |
| **技术难度** | 高 (Embedding, Vector DB) | **低** (Cron Job \+ LLM Summary) |
| **成本** | 检索便宜,但索引维护贵 | 生成周报耗 Token但查询极其高效 |
## **2\. 数据库设计 (Schema)**
### **2.1 新增 iit\_weekly\_reports 表**
model IitWeeklyReport {
id String @id @default(uuid())
projectId String
weekNumber Int // 例如: 202605 (2026年第5周)
startDate DateTime
endDate DateTime
// 核心LLM 生成的高浓缩总结
// 包含入组进度、发生的AE、关键沟通结论
summary String @db.Text
// 结构化数据 (可选,用于画图)
stats Json? // { "enrolled": 5, "queries": 2 }
createdAt DateTime @default(now())
@@unique(\[projectId, weekNumber\])
@@map("iit\_weekly\_reports")
@@schema("iit\_schema")
}
## **3\. 核心流程实现**
### **3.1 写入端:每周一凌晨的“编史官” (Cron Job)**
**SchedulerService.ts:**
// 每周一凌晨 02:00 执行
async generateWeeklyMemory(projectId: string) {
// 1\. 获取上周所有的原始对话 & 操作日志
const logs \= await this.rawLogs.get({
projectId,
from: lastMonday,
to: thisSunday
});
// 2\. 调用 LLM 进行“有损压缩”
const prompt \= \`
你是一个临床项目经理。请阅读上周的项目流水账,生成一份【周报记忆块】。
要求:
1\. 忽略闲聊和无关信息。
2\. 重点记录:入组人数变化、新增不良事件(AE)、主要方案偏离、PI的关键决策。
3\. 格式为 Markdown字数控制在 500 字以内。
流水账数据:
${JSON.stringify(logs)}
\`;
const summary \= await this.llm.chat(prompt);
// 3\. 存入数据库 (成为历史书的一页)
await prisma.iitWeeklyReport.create({
data: { projectId, summary, ... }
});
}
### **3.2 读取端:查询时的“速读” (Context Injection)**
当意图识别发现用户在问“过去”或“趋势”时,直接把**所有周报**读出来。
**ChatService.ts:**
async buildHistoryContext(projectId: string): Promise\<string\> {
// 1\. 取出该项目所有历史周报 (按时间正序)
// 3年也就 150 条Postgres 毫秒级返回
const reports \= await prisma.iitWeeklyReport.findMany({
where: { projectId },
orderBy: { weekNumber: 'asc' },
select: { weekNumber: true, summary: true }
});
// 2\. 拼接成一本“书”
// 格式:
// \[Week 2026-01\]: 入组 2 人,无异常。
// \[Week 2026-02\]: P003 发生 SAE已上报。
const chronicle \= reports.map(r \=\> \`\[Week ${r.weekNumber}\]: ${r.summary}\`).join('\\n');
return \`
\=== 项目编年史 (Project Chronicle) \===
${chronicle}
\====================================
\`;
}
## **4\. 最终开发计划修正 (Phase 5\)**
我们将 Week 5 的任务彻底简化:
| 时间 | 任务 | 说明 |
| :---- | :---- | :---- |
| **Day 24** | **原始日志表** | 确保 iit\_conversation\_history 记录完整。 |
| **Day 25** | **周报表结构** | 创建 iit\_weekly\_reports 表。 |
| **Day 26** | **编史官 Worker** | 写一个 Cron Job每周把 Log 压缩成 Report。 |
| **Day 27** | **记忆注入** | 在回答 QA\_QUERY 类问题时,将历史周报注入 Context。 |
## **5\. 总结:为什么这是终极方案?**
1. **解决了“上下文遗忘”**150 个周报拼接起来,刚好填满 DeepSeek 的上下文窗口。AI 可以看到**完整**的项目生命周期,这是 RAG 切片做不到的。
2. **解决了“幻觉”**:周报是持久化的,医生可以去后台检查某一周的周报写得对不对。如果 AI 瞎写,医生可以手动修正。**修正后的周报就是新的真理。**
3. **极简运维**:不需要维护向量库索引,不需要调优 Embedding 模型。就是简单的 SQL 查询和文本拼接。
**就用这个方案。这是目前为止最完美、最优雅的解决路径。**

View File

@@ -0,0 +1,83 @@
# **IIT Manager Agent V2.8:记忆信息映射指南**
**核心逻辑:** \> \* **即时/全局信息** ![][image1] 存入 **Hot Memory (Markdown)** ![][image1] 每次对话都带。
* **历史/阶段信息** ![][image1] 存入 **Weekly Reports (Database)** ![][image1] 查历史时全量读取。
## **1\. 信息存储位置映射表**
| 信息类型 | 存储位置 | 存储形式 | 举例 | 谁来维护? |
| :---- | :---- | :---- | :---- | :---- |
| **用户偏好** (User Preferences) | **Hot Memory** (Level 3\) | iit\_project\_memory 中的 Markdown 列表 | "PI 喜欢简报格式" "不要在周末发消息" | **人工编辑** (Admin) 或 **每日 AI 提炼** |
| **经常出现的上下文** (Frequent Context) | **Hot Memory** (Level 3\) | iit\_project\_memory 中的 Markdown 文本 | "当前处于入组冲刺阶段" "P001 是重点关注对象" | **每日 AI 提炼** (Cron Job) |
| **关键决策** (Key Decisions) | **Weekly Reports** (Level 2\) | iit\_weekly\_reports 中的 summary 字段 | "2026-02-01 决定放宽入排标准" "确认 P003 不良事件不相关" | **每周 AI 归档** (Scheduler) |
| **踩过的坑/经验教训** (Lessons Learned) | **双重存储** | **Hot** (如果是永久教训) **Weekly** (如果是历史记录) | **Hot**: "严禁直接调用 API 修改数据" **Weekly**: "W5 因参数错误导致同步失败" | **人工** (定规矩) **AI** (记流水) |
## **2\. 详细存储机制**
### **2.1 用户偏好 & 经常出现的上下文 \-\> Hot Memory (Markdown)**
这部分信息需要\*\*“时刻生效”\*\*,所以必须存成轻量级的 Markdown每次对话都注入 System Prompt。
* **存储表**iit\_project\_memory (config 字段)
* **内容示例**
\# User Preferences
\- \[PI\]: 汇报时只看数据,不要废话。
\- \[CRC\]: 下午 2 点后比较忙,尽量上午推任务。
\# Active Context
\- 当前重点任务:清理 3 月份的 Query。
\- 风险提示P005 患者依从性差,需每日提醒。
* **更新机制**
1. **被动更新**:管理员在后台手动修改。
2. **主动更新**每天凌晨AI 扫描昨日对话,如果发现用户说了“以后别...”,自动追加到这里。
### **2.2 关键决策 & 踩过的坑 \-\> Weekly Reports (编年史)**
这部分信息属于\*\*“项目历史”\*\*,不需要每次对话都挂在嘴边,但当用户问“回顾一下”时,需要能查到。
* **存储表**iit\_weekly\_reports
* **内容示例 (Week 2026-05)**
\[进度\]: 本周入组 3 人,累计 15 人。
\[决策\]: 2月3日 PI 会议决定:暂停筛选 "间质性肺炎" 既往史患者。
\[问题\]: 曾尝试自动录入化验单,但因 OCR 精度不足失败(踩坑记录),已回退为人工复核模式。
* **使用机制**
* 当用户问:“我们之前为什么暂停筛选肺炎患者?”
* 意图识别判断为 QUERY\_HISTORY。
* 系统一次性拉取过去所有周报 (150条记录),拼接成“编年史”,喂给 LLM 阅读并回答。
## **3\. 为什么这样设计?**
1. **偏好 (Preferences)** 必须是 **Hot** 的:
* 如果存到周报里AI 可能聊着聊着就忘了“老板不喜欢废话”这个规矩。只有放在 Hot Memory (System Prompt) 里,才能保证每一句回复都符合老板口味。
2. **决策 (Decisions)** 必须是 **Time-Series** 的:
* 决策往往有时间背景。存到周报里天然带有时间戳Week 5 做的决定)。这样 AI 才能回答“上个月做了什么决定”。
3. **踩坑 (Lessons)** 需要 **人工干预**
* 如果只是 AI 自动总结,可能会漏。
* V2.8 允许你在 Hot Memory 里手动写上一条:“【系统禁令】严禁在未授权情况下删除数据”。这条“人工植入的记忆”比任何 AI 总结都管用。
## **4\. 总结:信息流向图**
graph TD
User\[用户输入\] \--\>|对话流| Raw\[原始日志\]
subgraph "实时影响 (Hot)"
Raw \--\>|每日提炼| Prefs\[用户偏好\]
Raw \--\>|每日提炼| Context\[高频上下文\]
Admin\[人工干预\] \--\>|手动编辑| Prefs
end
subgraph "历史归档 (History)"
Raw \--\>|每周汇总| Decisions\[关键决策\]
Raw \--\>|每周汇总| Lessons\[踩坑记录\]
end
Prefs \--\>|注入| NextPrompt\[下一次对话\]
Context \--\>|注入| NextPrompt
Decisions \-.-\>|按需查询| NextPrompt
[image1]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAXCAYAAADpwXTaAAAAX0lEQVR4XmNgGAWjYCQAeXn50+hiZAOgYU/QxcgGcnJy2kA8HV2cbAB03SwgDkIXB0lIkomnAfF1oBHM1DBsERCfRzGMHCCPy5ukAmgETEAXJwvIUzFpMMpTM9GOcAAAmV0cRTlI2MMAAAAASUVORK5CYII=>

View File

@@ -0,0 +1,143 @@
# **IIT Manager Agent V2.8:记忆检索与路由逻辑详解**
**核心机制:** 意图驱动的按需检索 (Intent-Driven Retrieval)
**目的:** 既保证 AI 懂当下Hot又保证 AI 懂历史History同时节省 Token。
## **1\. 记忆存储全景图 (Storage Map)**
在 V2.8 中,我们有三个核心存储位置,各司其职:
| 记忆类型 | 存储表 | 字段 | 内容特征 | 更新频率 |
| :---- | :---- | :---- | :---- | :---- |
| **1\. 流水账 (Raw Stream)** | iit\_conversation\_history | content (JSON) | 未经加工的原始对话。 | **实时** (每秒) |
| **2\. 热记忆 (Hot Context)** | iit\_project\_memory | config (Markdown) | 用户偏好、当前状态、系统禁令。 | **每日** (AI 提炼) |
| **3\. 历史书 (Weekly Reports)** | iit\_weekly\_reports | summary (Text) | 高度浓缩的周报、关键决策、踩坑记录。 | **每周** (AI 归档) |
## **2\. 调取顺序与逻辑 (Retrieval Logic)**
当 PI 发问时,系统并不是一股脑把所有记忆都塞进去,而是分三步走:
### **第一步:无条件注入 (Always On)**
**无论 PI 问什么,必须先注入“热记忆”。**
* **来源**iit\_project\_memory
* **内容**用户偏好如“PI 喜欢简报”)、当前项目状态。
* **理由**:这是 Agent 的“人设”和“底线”,一刻也不能忘。
### **第二步:意图识别 (Intent Detection)**
系统分析 PI 的问题,判断他想问“现在”还是“过去”。
* **情况 A问现在/执行任务** (如 "查下 P001", "生成本周周报")
* **动作****不调取** 历史书。
* **理由**:解决当下问题不需要翻阅 3 年前的老黄历,避免干扰 AI。
* **Context** \= Hot Memory \+ Current Task Data
* **情况 B问历史/趋势/回顾** (如 "回顾下去年的入组情况", "我们为什么暂停了筛选?")
* **动作****全量调取** 历史书 (iit\_weekly\_reports)。
* **理由**:需要上帝视角。
* **Context** \= Hot Memory \+ All Weekly Reports
### **第三步:上下文组装 (Prompt Assembly)**
最终发给 LLM 的 Prompt 是这样组装的:
\[System Prompt\]
你是一个临床研究助手...
\[Hot Memory\] (来自 iit\_project\_memory)
\- 用户偏好: 简洁、数据驱动
\- 当前状态: 入组阶段
\[History Context\] (仅在情况 B 下注入)
Week 1: 启动项目...
Week 2: 发现 P001 不良事件...
...
Week 50: 决定放宽标准...
\[User Question\]
我们之前为什么暂停筛选?
## **3\. 实战场景演示 (Scenario Walkthrough)**
### **场景 1PI 问 "P001 入组了吗?"**
1. **加载 Hot Memory**:获取偏好(简洁回复)。
2. **意图识别**QUERY\_DATA (查数据)。
3. **决策****不需要** 查历史书。
4. **行动**:调用 read\_clinical\_data 工具去 REDCap 查实时数据。
5. **回答**"已入组,时间是 2026-02-05。"
### **场景 2PI 问 "最近入组太慢了,我们之前有没有讨论过怎么解决?"**
1. **加载 Hot Memory**:获取偏好。
2. **意图识别**QUERY\_HISTORY (查历史决策)。
3. **决策****需要** 查历史书。
4. **行动**
* 从 iit\_weekly\_reports 拉取过去 20 周的 summary。
* 拼接成 3000 字的文本。
* 喂给 LLM。
5. **LLM 思考**:阅读周报,发现 Week 12 记录了“增加受试者交通补贴”的决策Week 15 记录了“在门诊增派 CRC”的决策。
6. **回答**"我们曾在第 12 周尝试增加补贴,第 15 周增派了 CRC当时效果有短暂提升..."
### **场景 3PI 问 "我上次跟你说的那个只要看结果的规矩,你记得吗?"**
1. **加载 Hot Memory**:获取偏好。
2. **AI 自检**:在 Hot Memory 里看到了 \- \[PI\]: 只要看结果 这条记录。
3. **回答**"记得的,教授。您要求汇报时只列数据结论,不要冗长的过程描述。我会严格遵守。"
* *注意:这个问题不需要查周报,也不需要查流水账,直接看 Hot Memory 就行。*
## **4\. 总结:给开发者的伪代码**
// ChatService.ts
async handleMessage(userId, message) {
// 1\. 总是加载热记忆 (Hot)
const hotMem \= await prisma.iitProjectMemory.findUnique(...);
// 2\. 意图识别
const intent \= await this.intentService.detect(message);
let historyContext \= "";
// 3\. 按需加载历史书 (History)
if (intent.type \=== 'QUERY\_HISTORY' || intent.type \=== 'ANALYZE\_TREND') {
const reports \= await prisma.iitWeeklyReport.findMany({
orderBy: { weekNumber: 'asc' }
});
historyContext \= reports.map(r \=\> \`\[W${r.weekNumber}\]: ${r.summary}\`).join('\\n');
}
// 4\. 组装最终 Prompt
const finalPrompt \= \`
${hotMem.content}
${historyContext}
User: ${message}
\`;
// 5\. 调用 LLM
return await this.llm.chat(finalPrompt);
}
**记住这个口诀:**
**“偏好时刻带,历史按需查,流水账只用来生成周报。”**
## **5\. 复杂度控制与防失控指南 (Safety Guardrails)**
为了防止系统变得“复杂不可控”,请严格遵守以下开发军规:
### **5.1 容量限制 (The Cap)**
* **Hot Memory**: 限制在 **2000 Tokens** 以内。如果超标,触发 MemoryPruningJob (记忆修剪任务),让 AI 自动总结或通知管理员手动删除。
* **History Book**: 每次加载不超过 **50 周** 的周报。如果项目运行了 10 年,只加载最近 1 年的周报,或者先让 AI 生成“年度总结”。
### **5.2 隔离原则 (Isolation)**
* **任务隔离**:执行 QC\_TASK (质控) 时,**严禁**加载 History Context。质控必须基于当下的事实不能受历史干扰。
* **数据隔离**LLM 永远没有权限直接读取 iit\_conversation\_history 表。这条物理隔绝保证了 Agent 永远不会被海量噪音淹没。
### **5.3 人工介入 (The Kill Switch)**
* **可读可改**:后台必须提供 Hot Memory 的编辑器。如果 Agent 记住了错误的规则(例如“不需要查年龄”),医生可以直接进去删掉那一行 Markdown。**这是系统失控时的“急停按钮”。**

View File

@@ -0,0 +1,210 @@
# **IIT Manager Agent V2.9 补充设计:主动性与用户画像增强**
**目标:** 响应智能化评估中的 P0 需求,补齐“主动服务”和“个性化”短板,并增强复杂任务处理能力。
**原则:** 坚持 **Postgres-Only**,复用现有的 **Skill (JSON)****ToolsService**
## **1\. 主动提醒机制设计 (The Proactive Engine)**
我们不需要写死一堆 cron 脚本,而是将“主动提醒”抽象为一种特殊的 **Skill**
### **1.1 架构复用逻辑**
* **触发源**pg-boss 的定时任务 (Scheduler)。
* **规则载体**iit\_skills 表(新增 trigger\_type: 'cron')。
* **执行者**QcEngineService (复用现有的逻辑)。
### **1.2 数据库 Schema 扩展**
在 iit\_skills 表中增加调度字段:
model IitSkill {
// ... 原有字段
triggerType String @default("webhook") // 'webhook' | 'cron' | 'event'
cronSchedule String? // 例如: "0 9 \* \* \*" (每天9点)
// 核心配置 (JSON)
config Json
}
### **1.3 Skill 配置示例:访视提醒**
这是一个“每天早上9点检查即将到期访视”的技能配置。
{
"name": "即将到期访视提醒",
"description": "扫描未来3天内需要访视的患者",
// 核心:复用 Engine A 的逻辑来筛选数据
"hard\_rules": \[
// 这里我们稍微扩展一下 hard\_rules 的能力,支持 SQL-like 的筛选
// 或者直接调用工具获取列表
\],
// 核心:复用 Engine B (LLM) 来生成人性化通知
"soft\_instructions": \[
{
"instruction": "请调用 \`get\_upcoming\_visits(days=3)\` 工具。如果有数据,请根据【用户偏好】生成一条提醒消息。如果没有数据,什么都不做。",
"tools": \["get\_upcoming\_visits", "send\_wechat\_msg"\]
}
\]
}
### **1.4 代码实现 (SchedulerService.ts)**
// SchedulerService.ts
async initCronJobs() {
// 1\. 从数据库加载所有 triggerType \= 'cron' 的 Skill
const cronSkills \= await prisma.iitSkill.findMany({
where: { triggerType: 'cron', isActive: true }
});
for (const skill of cronSkills) {
// 2\. 注册到 pg-boss
await this.boss.schedule(
\`run-skill-${skill.id}\`,
skill.cronSchedule,
{ skillId: skill.id }
);
}
}
// Worker 处理逻辑
async handleCronSkill(job) {
const { skillId } \= job.data;
const skill \= await prisma.iitSkill.findUnique({ where: { id: skillId } });
// 3\. 复用现有的引擎!
// 就像处理 Webhook 一样处理 Cron架构极其统一
await this.qcEngine.runSoftAgent(
skill.config.soft\_instructions\[0\].instruction,
{ source: 'cron\_job' } // Context
);
}
**效果:** 你只需要在后台配一个 JSONAgent 就会每天早上 9 点醒来查数据然后发微信“张医生P005 明天该来复查了。”
## **2\. 用户画像增强 (User Profiling)**
我们已经在 V2.8 中设计了 iit\_project\_memory (Hot Memory),现在只需要把“画像”结构化。
### **2.1 存储设计**
在 iit\_user\_preferences 表中,我们将 preferences 字段细分为两类:
1. **显性偏好 (Explicit)**:用户明确说的(“我要简报”)。
2. **隐性画像 (Implicit)**:系统分析出来的(“他是 PI关注宏观数据”
// preferences 字段内容示例
{
"communication\_style": "concise", // 简练 | 详尽
"role\_tag": "PI", // PI | CRC | CRA
"focus\_areas": \["AE", "Enrollment"\], // 关注点
"notification\_time": "08:30", // 接收时间
"feedback\_history": { // 简单的反馈记录
"thumbs\_up": 12,
"thumbs\_down": 1
}
}
### **2.2 自动画像更新 (The Profiler)**
我们在 ChatService 结束一次对话后,异步触发一个微型任务:**“画像侧写”**。
// ProfilerService.ts
async updateProfile(userId: string, lastConversation: any\[\]) {
const currentProfile \= await this.getProfile(userId);
// 让 LLM 当心理分析师
const prompt \= \`
基于刚刚的对话,更新用户画像。
对话:${JSON.stringify(lastConversation)}
旧画像:${JSON.stringify(currentProfile)}
提取规则:
1\. 如果用户抱怨"太长了",将 style 设为 concise。
2\. 如果用户问了"安全性",将 AE 加入 focus\_areas。
返回新的 JSON。
\`;
const newProfile \= await this.llm.chat(prompt);
await this.saveProfile(userId, newProfile);
}
## **3\. 反馈循环 (Feedback Loop \- Lite)**
不要做复杂的强化学习RLHF只做简单的\*\*“点赞/点踩”\*\*。
### **3.1 交互设计**
在 Agent 回复的每条消息下面(如果是小程序/网页),加两个小按钮:👍 / 👎。
### **3.2 逻辑闭环**
1. **用户点踩 (👎)**
2. **前端**:弹窗询问“哪里不好?”(选项:太啰嗦、不准确、没听懂)。
3. **后端**
* 将这条 Negative Feedback 写入 iit\_conversation\_history。
* **关键动作**:触发一次 Hot Memory 更新。
* **Prompt**"用户对刚才的回答点了踩,原因是'太啰嗦'。请在 Hot Memory 中记下一条禁令:\[针对该用户,严禁输出超过 100 字的废话\]。"
**结果**Agent 下次遇到这个用户,真的会改。这就是最真实的“智能感”。
## **4\. 多意图与任务规划增强 (Multi-Intent Handling)**
针对“先做 A再做 B”的复杂指令“查一下 P001 的状态,然后通知张医生”),我们不需要引入重型 Planner而是通过 **ReAct \+ Prompt Engineering** 实现。
### **4.1 核心策略ReAct 自然涌现**
**结论**ReAct 引擎本身就是一个轻量级 Planner。
不需要额外的 Planner 模块,只需要在 **System Prompt** 中显式教导 Agent 如何拆解任务。
### **4.2 Prompt 增强设计**
在 backend/src/modules/iit-manager/engines/ReActEngine.ts 的系统提示词中加入以下**思维链Chain of Thought指令**
const REACT\_SYSTEM\_PROMPT \= \`
你是一个临床研究助手。你可以使用工具来回答问题。
核心原则:
1\. \*\*任务拆解\*\*:如果用户请求包含多个步骤(例如"先...再...""查完...发给..."),请务必分步执行。
\- 不要试图在一个步骤里做完所有事。
\- 比如:先调用查询工具,获得结果后,再调用发送工具。
2\. \*\*依赖管理\*\*:如果步骤 B 依赖步骤 A 的结果,必须先执行 A观察 A 的结果,再执行 B。
3\. \*\*完成检查\*\*:在输出 Final Answer 之前,检查是否完成了用户的所有指令。
思考格式示例:
Thought: 用户想查 P001 并通知张医生。我有两个任务。首先,我需要查 P001 的状态。
Action: 调用 read\_clinical\_data(id='P001')...
Observation: {"status": "Enrolled"}
Thought: 我已经查到了状态。现在我需要执行第二个任务:发消息给张医生。
Action: 调用 send\_wechat\_msg(to='Dr. Zhang', content='P001 status is Enrolled')...
...
\`;
### **4.3 为什么不用 Planner**
对于 2 人团队,显式 Planner先生成计划列表再循环执行过于沉重且难以维护状态。ReAct 的 **“边走边想”** 模式完全能够覆盖 90% 的“线性多步任务”。
## **5\. 总结与建议**
| 需求 | 解决方案 | 复杂度 | 推荐实施阶段 |
| :---- | :---- | :---- | :---- |
| **主动提醒** | **Cron Skill** (复用 pg-boss \+ QC Engine) | 低 | **Phase 4** (与周报一起做) |
| **用户画像** | **Profiler Job** (对话后异步分析) | 中 | **Phase 5** (智能化增强) |
| **反馈循环** | **点赞按钮 \+ 记忆写入** | 低 | **Phase 5** |
| **多意图处理** | **ReAct Prompt 优化** (无需新模块) | 极低 | **Phase 5** (开发 ReActEngine 时同步完成) |
### **对你的建议**
1. **全盘接受 P0 建议**:主动提醒和用户画像是成本低、收益大的功能。
2. **技术上保持克制**
* 别写专门的 Alert Engine用 Cron Skill 复用现有的引擎。
* 别搞复杂的推荐算法,用 JSON 存画像就够了。
* 别搞复杂的 Planner**Prompt** 教会 ReAct 拆解任务就够了。
这样,你的 V2.6+ 架构就真正补齐了“右脑”的情商短板,变成了一个 **“有记忆、会主动、懂人情、能办事”** 的完整 Agent。

View File

@@ -0,0 +1,44 @@
## **1\. 风险预警 (Risk Alert)**
### **🔴 高风险Phase 4 排期过饱和**
* **问题**Week 4 同时安排了"视觉识别"、"周报系统"、"高级伦理规则"开发,对于 2 人团队工作量过载。
* **建议****战略性放弃视觉识别的自动录入功能**,将其降级为 MVP仅识别展示。将资源集中在"周报系统"上,因为周报对 PI 的价值感知更强。
### **🟠 中风险SOP 状态丢失**
* **问题**SopEngine 在内存中运行 while 循环。若 Node.js 服务在长任务中途重启(如发布新版),当前流程会丢失。
* **建议**:在 while 循环的每一步,增加 await updateTaskProgress(),将 currentNode 写入数据库,确保有据可查。
### **🟠 中风险AI 自动写入合规性**
* **问题**Phase 4 提到"高置信度自动录入"。在 GCP 原则下AI 直接修改源数据存在合规隐患。
* **建议**:坚持 **Shadow State (影子状态)** 原则。VisionService 的结果应生成 PROPOSED 状态的记录,必须由 CRC 人工点击确认。
## **2\. 架构优化建议 (Architecture Tuning)**
### **2.1 增加 "Dry Run" 机制**
* **痛点**JSON 配置写在数据库里,出错难排查。
* **方案**:在 ToolsService 中增加 simulate() 方法,允许在不产生副作用的情况下测试 Skill 逻辑。
### **2.2 强化字段映射管理**
* **痛点**iit\_field\_mapping 表如果只能通过 SQL 修改,运营成本极高。
* **方案**:在 Week 3 增加一个简单的 Admin API 或界面,用于管理字段别名。
## **3\. 调整后的推荐路线图 (Refined Roadmap)**
* **Week 1 (基础)**:数据库 \+ HardRuleEngine \+ ToolsService (不变)
* **Week 2 (引擎)**SoftRuleEngine \+ SopEngine (**增加状态持久化**)
* **Week 3 (联调)**:配置第一个 Skill \+ 端到端测试 (**增加 Dry Run 工具**)
* **Week 4 (扩展)**
* **P0**: 定时任务 \+ 周报系统 (Scheduler \+ Report)
* **P1**: 高级质控工具 (Visit Window)
* **P2 (延后)**: 视觉识别 (VisionService) \-\> **建议移至 V2.6**
## **4\. 结论**
架构选型Postgres-Only \+ Service-First非常精准。只需在 Phase 4 做适当减法,并注意状态持久化细节,即可确保项目按时、高质量交付。

View File

@@ -0,0 +1,169 @@
**第二章 微信服务号集成与Error 200002根因深度剖析**
用户提到的“配置消息推送失败: invalid args, 200002”且“后端无日志”是微信开发中极其典型且令人抓狂的现象。这通常不是单一的代码错误而是网络层、网关层与应用层叠加的系统性故障。
### **2.1 现象解码:“无日志”的恐怖**
当微信后台提示配置失败而您的服务器访问日志Access Log空空如也时这意味着**请求根本没有到达您的应用服务器**。
#### **2.1.1 网络层的隐形墙:防火墙与安全组**
微信服务器位于腾讯的公网IP池中。当它发起HTTP/HTTPS请求时如果您的服务器部署在阿里云、腾讯云或AWS等云平台\*\*安全组Security Group\*\*是第一道关卡。
* **诊断**大多数云服务器默认只开放22端口SSH
* **排查**检查云控制台的入站规则。必须允许0.0.0.0/0对TCP 80HTTP和443HTTPS的访问。
* **误区**不要试图将微信的IP加入白名单。微信的出口IP是不定期的海量IP池必须全量开放。
#### **2.1.2 接入层的黑洞Nginx配置错误**
如果您使用了Nginx作为反向代理请求可能在Nginx层被丢弃导致后端Java/Python应用收不到日志。
* **Server Name匹配**如果Nginx配置了server\_name www.example.com而您在微信后台填写的是直接IP地址Nginx可能因为找不到匹配的Host而直接拒绝连接。
* **SSL握手失败**如果配置的是HTTPS URL但服务器的SSL证书不完整如缺少中间证书或协议版本过旧TLS 1.0微信服务器会终止握手请求甚至不会进入Nginx日志。
### **2.2 Error 200002 “invalid args” 的代码级解剖**
如果请求成功到达服务器可以通过Nginx日志确认但微信仍然报200002则问题出在**握手协议的实现**上。这是最考验开发者基本功的环节。
#### **2.2.1 握手协议的三大铁律**
微信在验证服务器有效性时发送signature, timestamp, nonce, echostr四个参数。开发者必须完成以下步骤
1. **字典序排序Lexicographical Sorting**
* **错误高发点**很多开发者将数字类型的timestamp直接参与排序或者使用了错误的排序算法。
* **正确逻辑**:将\[token, timestamp, nonce\]放入一个字符串数组调用标准的字符串排序方法如Python的list.sort()Java的Arrays.sort())。
2. **SHA1哈希Hashing**
* **错误高发点**编码问题。在Python 3中hashlib.sha1()接受的是字节流bytes必须先对拼接后的字符串进行.encode('utf-8')。
3. **返回值的纯净性Response Purity**
* **错误高发点**这是导致200002最隐蔽的原因。微信要求**原样返回**echostr的明文。
* **忌讳**
* 不能返回JSON格式如{"ret": "success", "echo": "..."})。
* 不能包含引号(如"abc...")。
* 不能包含换行符或空格。
* **Content-Type**虽然微信不强制但最佳实践是设置Response Header为text/plain。
#### **2.2.2 深度调试代码示例Python Flask版**
以下是一个通过了生产环境验证的标准校验代码,用于替换可能存在缺陷的逻辑:
Python
import hashlib
from flask import Flask, request, make\_response
app \= Flask(\_\_name\_\_)
@app.route('/wechat', methods=)
def check\_signature():
\# 1\. 获取参数
token \= "YOUR\_TOKEN" \# 必须与微信后台填写的完全一致
signature \= request.args.get('signature')
timestamp \= request.args.get('timestamp')
nonce \= request.args.get('nonce')
echostr \= request.args.get('echostr')
\# 2\. 空值校验(防御性编程)
if not all(\[signature, timestamp, nonce, token\]):
return "Invalid Request", 400
\# 3\. 字典序排序
li \= \[token, timestamp, nonce\]
li.sort()
\# 4\. 拼接与哈希
temp\_str \= "".join(li)
hash\_str \= hashlib.sha1(temp\_str.encode('utf-8')).hexdigest()
\# 5\. 对比与返回
if hash\_str \== signature:
\# 关键使用make\_response确保返回的是纯文本不受框架序列化影响
response \= make\_response(echostr)
response.headers\['content-type'\] \= 'text/plain'
return response
else:
\# 记录错误日志以便排查
app.logger.error(f"Sig check failed. Calc: {hash\_str}\!= Req: {signature}")
return "Signature Failed", 403
if \_\_name\_\_ \== '\_\_main\_\_':
app.run(port=80)
### **2.3 “我很痛苦”的终结方案:抓包与模拟**
如果依然失败,请停止盲目修改代码,采用**证据驱动调试**。
1. **自测脚本**编写一个Python脚本模拟微信的逻辑生成签名并发送GET请求给自己的服务器。如果自测通过但微信不通过问题一定在网络层防火墙/CDN/WAF
2. **网络抓包**在服务器上使用tcpdump \-i eth0 port 80 \-w wechat\_debug.pcap。在微信后台点击提交后停止抓包并用Wireshark分析。
* 如果抓不到包 \-\> 网络不通。
* 如果抓到包但应用没日志 \-\> Nginx配置错误。
* 如果应用返回了200但微信报错 \-\> 检查Response Body是否有隐藏字符BOM头、换行符
## ---
**第三章 全场景调试指南:工具链与最佳实践**
对于“如何调试”、“本地还是远程”的疑问,行业内的最佳范式是\*\*“本地隧道调试”\*\*Local Tunnel Debugging。直接在远程服务器上修改代码并重启服务来调试微信接口效率极低且风险极高。
### **3.1 本地调试的核心逻辑:内网穿透**
微信服务器无法直接访问你笔记本电脑上的localhost:8080。因此需要一个“隧道”将公网请求转发到本地。
#### **3.1.1 工具选型与配置**
针对中国网络环境,推荐以下工具链:
| 工具名称 | 适用场景 | 优点 | 缺点 | 推荐指数 |
| :---- | :---- | :---- | :---- | :---- |
| **ngrok** | 快速验证 | 命令简单,全球通用 | 免费版域名随机变化,国内连接有时不稳定 | ⭐⭐⭐ |
| **frp** | 长期开发 | **国内标准**。需一台公网VPS。稳定域名固定 | 配置稍繁琐,需维护服务端 | ⭐⭐⭐⭐⭐ |
| **cpolar/Natapp** | 无VPS用户 | 专为国内优化,速度快 | 免费版有限制,自定义域名需付费 | ⭐⭐⭐⭐ |
#### **3.1.2 FRP实战配置SOP**
假设您有一台腾讯云/阿里云服务器IP: 1.2.3.4),这是最稳定的方案。
1. **服务端VPS配置 frps.ini**
Ini, TOML
\[common\]
bind\_port \= 7000 \# 用于frp内部通信
vhost\_http\_port \= 8080 \# 微信访问的端口
启动:./frps \-c frps.ini
2. **客户端(本地电脑)配置 frpc.ini**
Ini, TOML
\[common\]
server\_addr \= 1.2.3.4
server\_port \= 7000
\[wechat-debug\]
type \= http
local\_port \= 5000 \# 本地Python/Java服务的端口
custom\_domains \= wechat.your-startup.com \# 解析到1.2.3.4的域名
启动:./frpc \-c frpc.ini
3. **调试闭环**
* 在微信后台配置URL为http://wechat.your-startup.com:8080/callback
* 当微信发送请求时,流量路径为:微信 \-\> VPS(7000) \-\> 隧道 \-\> 本地电脑(5000)。
* **效果**您可以在本地IDEPyCharm/VSCode中打断点实时查看微信发来的XML数据包单步调试每一行代码。这是解决“痛苦”的根本途径。
### **3.2 微信官方调试工具的使用**
除了内网穿透,腾讯提供了**微信开发者工具**(主要用于小程序,但也包含公众号调试)和**在线接口调试工具**。
* **在线接口调试工具** 6
* 地址:[https://developers.weixin.qq.com/apiExplorer](https://developers.weixin.qq.com/apiExplorer)
* 用途当您的Token配置成功但消息推送失败时可以用它模拟微信服务器向您的URL发送POST请求。它会详细显示您的服务器返回了什么HTTP Code, Body帮助快速定位“回包格式错误”。
### **3.3 行业最佳实践范式**
1. **TraceID全链路追踪**在入口处生成一个UUID作为TraceID贯穿Nginx日志、应用日志和数据库日志。当某个课题组反馈机器人不回话时通过TraceID可秒级定位问题环节。
2. **日志分级与脱敏**
* **DEBUG级**打印完整的XML/JSON请求体注意生产环境需对患者姓名、ID进行掩码脱敏
* **INFO级**:记录核心链路节点(收到消息 \-\> 开始推理 \-\> 推送完成)。
* **ERROR级**记录所有的API调用非200状态码及异常堆栈。
3. **容错与重试**微信接口偶尔会出现网络抖动。在调用send接口时务必封装重试逻辑Exponential Backoff遇到超时或5xx错误时自动重试3次。
## ---

View File

@@ -0,0 +1,64 @@
# **潜在的具体风险与问题**
即使架构完美,细节仍可能翻车。以下是代码落地时可能遇到的“暗礁”以及针对性的应对策略。
## **🔴 风险一:意图路由的“延迟感”**
* **位置**IntentService (Day 20-21)
* **问题**:用户发一句话,系统先调一次 LLM (路由),再调一次 SOP/ReAct (执行)。
* **后果**:响应延迟 \= LLM(路由) \+ LLM(执行) \+ 网络开销。可能导致用户发完消息后 **5-8 秒** 才有反应。在微信的即时通讯场景下,这个体感很差,用户容易认为系统卡死。
* **对策**
1. **流式欺骗 (UX Trick)**:收到消息立刻回一个“正在思考...”或“正在查询...”的状态(企业微信 API 支持中间态更新或 typing 状态)。
2. **快速通道 (Fast Path)**:对于极短的、特征明显的指令(如“质控”、“报表”、“帮助”),先用 **正则 (Regex) / 关键词 (Keywords)** 进行拦截。只有正则拦截不住的复杂长句,再走 LLM 意图路由。
* *原则***混合路由 \> 纯 LLM 路由**。
## **🔴 风险二ReAct 的“多嘴”风险**
* **位置**ReActEngine (Day 22-23)
* **问题**ReAct 的核心机制是“思考-行动-观察”循环。模型倾向于在思考过程中输出大量中间步骤,例如:
* *AI Thought*: "用户想查 P001我需要先调用 search 工具,然后..."
* *AI Thought*: "哎呀,没查到数据,可能需要换个参数..."
* **后果**如果将这些中间思考过程Internal Monologue全部实时推送给用户用户会觉得“这个机器人话痨”、“不够干练”甚至“不自信”。
* **对策**
1. **UI 静默处理**:只把 Final Answer 发送给用户。
2. **调试可见**:中间的 Trace (思考轨迹) 只存入后台日志,或者仅在 Admin 管理端的 Debug 模式下显示,用于排查问题。
## **🔴 风险三SOP 状态机的“死锁”**
* **位置**SopEngine (Day 8-9)
* **问题**SOP 流程中经常包含“人工介入”环节。例如,如果 SOP 走到了 review\_required等待人工复核节点流程会暂停。如果 CRC 一直不点击确认,或者忘记处理,这个任务就会一直挂在内存或队列中。
* **后果**pg-boss 可能会认为任务超时Timeout触发反复重试机制最终判定失败Failed导致流程异常终止。
* **对策**
1. **区分状态**:在系统设计中严格区分 **“系统阻塞 (System Blocking)”** 和 **“业务等待 (Business Waiting)”**。
2. **挂起机制**:如果是“等人”,任务状态应设为 SUSPENDED挂起。此时应从活动队列中移除不再占用 Worker 资源。
3. **唤醒机制**:当 CRC 在前端点击“确认”后,触发一个回调,创建一个新的 Job 来唤醒流程,继续执行后续步骤。
## **3\. 给 2 人团队的“瘦身版”执行建议**
基于 V2.6 计划,为了确保在有限人力下 100% 交付核心价值,建议执行以下 **“瘦身”** 策略:
### **✂️ 裁剪清单 (不做或延后)**
1. **\[延后\] Phase 6 全阶段 (视觉能力)**:放到 V3.0 再说。目前的文本交互和结构化数据处理已经足够复杂,且 OCR 准确率调优是个无底洞。
2. **\[裁剪\] Phase 5 的语义记忆 (pgvector)**:暂时只做简单的文本记录(如 JSON 或 Markdown。跨度极大的语义召回在初期并非刚需。
3. **\[简化\] 伦理合规检测 (T11.3)**:伦理规则极度复杂且非结构化。建议先写死几条最核心的规则(如“知情同意日期必须早于入组日期”),暂不做通用的伦理引擎。
### **✨ 聚焦清单 (必须做好)**
1. **P0: ToolsService 的健壮性**:这是整个系统的地基。一定要加上 V2.3 文档中提到的 **“字段模糊映射”**,否则 LLM 根本调不通工具,体验会崩塌。
2. **P0: 每日早报 (Morning Brief)**这是用户PI感知最强的功能。一定要做得漂亮、准时这是体现 Agent “主动性”的关键。
3. **P0: 意图路由的混合模式**:正则 \+ LLM 双保险,这是保证响应速度和降低 Token 成本的关键。
## **4\. 最终评价**
这份 **V2.6 计划** 是一个经过深思熟虑的、高水平的架构设计。
* **它懂业务**SOP 状态机完美解决了医疗合规与流程锁定的痛点。
* **它懂用户**双脑架构ReAct \+ SOP解决了“机器人太死板”的交互体验痛点。
* **它懂工程**极简架构Postgres-Only \+ Service Class解决了小团队维护复杂微服务的痛点。
**结论**:只要你们能忍住不去做“视觉识别”和“复杂语义记忆”这些锦上添花的功能,按部就班地把前 5 个 Phase 做完,这将是一个在医疗垂直领域极具竞争力的产品。
**最后一步建议:**
把这份文档发给团队,然后开一个 **Kick-off Meeting**,明确告诉大家:“我们要造的是**双脑 Agent**,但我们从\*\*左脑SOP\*\*开始造,视觉能力先放进冰箱。”