Files
memind/docs/architecture/chat-task-intent-layer-review-20260828.md
T
john 3df2c62c3c docs(architecture): add chat-to-task intent layer review and low-impact plan.
Document R1–R3 root causes in the H5 direct/agent routing path, reject the Task Runtime approach, and define a phased rollout with Memory V2 compatibility constraints.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 17:34:59 +08:00

565 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 聊天 ↔ 任务执行意图层评估与改造方案
日期: 2026-08-28
状态: 设计评估(只读核查完成,未改动任何代码)
适用范围:
- H5 聊天主链路的路由层:`chat-intent-router``agent-run-gateway``direct-chat-service`
- direct chat 与 Goose agent 之间的会话身份、上下文传递与升级路径。
- 「用户正常聊天 → 系统识别意图 → 触发任务执行」这一交互闭环。
非目标:
- 不重写 Goose,不改 goosed 协议。
- 不引入新的任务存储模型或新框架。
- 不改变现有发布闸门与回归守卫规则。
- 不重复 [H5 Session 架构方案](../h5-session-architecture-20260706.md) 中已落地的 session broker / SSE replay 能力。
## 0. 结论
被评估的原方案(引入 Task Resolver + Task State Machine + Working Memory 的「Task Runtime」)**方向正确但不是最优解**:它假设「意图飘忽」源于缺少任务控制层,而代码核查显示,真实根因是路由层的三个具体缺陷。
核查确认的三个根因:
| 编号 | 问题 | 性质 |
|---|---|---|
| R1 | direct chat 的历史不进入 Goose 执行输入 | 上下文单向丢失 |
| R2 | 纯聊天会话中 LLM 意图识别**永不启动** | 吸收态死区 |
| R3 | 兜底安全网对 router 判定的 direct_chat 主动放行,且 prompt 指示模型谎报「已交由后台」 | 误判被放大并对用户不可见 |
其中 **R2 是决定性的**`MEMIND_CHAT_LLM_ROUTER_ENABLED=1` 在 103 生产已开启,但在纯聊天会话里那个 LLM 根本不会被调用。语义意图识别目前实际由正则承担。
**Task Runtime 无法解决 R1R3 中的任何一个。** 因此本文提出替代路线:先修复路由层缺陷,让意图识别真正生效;Task 抽象留待有数据支撑后再增量引入。
### 0.1 与相关文档的关系
| 文档 | 关系 |
|---|---|
| [H5 Session 架构方案](../h5-session-architecture-20260706.md) | 本文在其 control plane / execution runtime 划分之上,处理 chat↔agent 的路由与上下文缺口 |
| [Intent Transaction Layer 设计](../intent-transaction-layer-design.md) | ITL 管**有副作用的创建动作**(提醒/定时)的 Draft→Confirm→Commit;本文管**多轮对话中的任务识别与执行移交**,两者并列,不应合并进同一分类器 |
| [SSE 断线续播守卫](../regression-guards/h5-session-stream-replay.md) | 本文 P2 触及 `agent-run-gateway` session 路径,改动须重跑该守卫 |
| [Orchestrator 边界](./memind-orchestrator-boundary.md) | 本文不改变 Portal = control plane、Goose/Orchestrator = execution 的划分 |
---
## 1. 被评估的原方案
原方案主张把架构从 `H5 → Goose → LLM → Tools` 升级为在 H5 与 Goose 之间插入 Intent Gateway / Task Controller,核心为三件事:
1. **Task Resolver** — 每轮先判断本句与当前任务的关系(SAME / MODIFY / NEW / CANCEL / AMBIGUOUS),用小模型输出严格 JSON。
2. **Task State Machine**`NEW → UNDERSTANDING → PLANNING → EXECUTING → WAITING_USER → COMPLETED`,状态由程序控制,LLM 无权变更。
3. **Working Memory** — 结构化的 goal / constraints / decisions / pending_questions,作为权威状态;conversation history 降级为「证据」。
配套还包括 Intent Lock(相似度阈值 0.8 / 0.5)与 Goal → Task → Step 树。
## 2. 对原方案的评估
### 2.1 未量化根因就动架构
整个方案建立在「意图飘忽」的主观描述上。没有漂移基线指标,就无法判断根因,也无法证明改造有效。这是最需要先补的一环。
### 2.2 Task Resolver 修不了已确认的根因
即便 relation 判定 100% 准确、task state 完美维护:
- R1 仍在:Goose 依旧收不到 direct 轮次的原文。
- R2 仍在:Resolver 本身要在路由之后才有意义,而路由在聊天会话里已提前锁死。
Resolver 输出的结构化摘要反而会成为唯一信息源,正是 2.5 所述的权威倒置。
### 2.3 每轮新增阻塞 LLM 调用
Resolver 在关键路径上增加一次调用,带来延迟与新失败模式。配合 Intent Lock 后,分类错误会被**放大**:原本错一轮即自愈,改为锁定 N 轮,用户需显式挣脱。「偶发漂移」被换成「确定性卡死」,后者体感更差。
### 2.4 硬 FSM 过度设定
`UNDERSTANDING``PLANNING` 是不可观测的内部状态,外部无法可靠判定进入/退出。真实对话不服从该链(随时插话、回退、并行两件事),最终会退化为大量转移补丁。GPT 系产品均未采用显式 task FSM。
### 2.5 Working Memory 权威化是危险倒置
一旦 WM 权威且不再发送完整历史,WM 漏写某个约束时模型**无法从原文恢复**。GPT 的做法相反:完整上下文是权威,结构化记忆只是 hint,抽取错了仍可被原文纠正。
### 2.6 Intent Lock 阈值是魔数陷阱
`>0.8 / 0.5~0.8 / <0.5` 的阈值不可跨 domain 迁移。原方案自身已给出反例(「换个话题,帮我看看 Mac」embedding 仍高相似)。叠加 rules 与小模型兜底后,embedding 层已无净贡献,只剩调参负担。
### 2.7 会成为第四套任务模型
现状已有 `h5_agent_runs``h5_goal_checkpoints``h5_unified_tasks`、orchestrator run 四套并存。`AGENTS.md` 已点名 gateway 边界偏厚。Goal→Task→Step 树依赖高质量任务分解,属于过早抽象。
## 3. GPT 的实际做法(对照)
| 维度 | GPT 的做法 |
|---|---|
| 任务状态 | 不维护外部 FSM,整个 thread 即状态 |
| 轮次延续 | 无 relation classifier,模型看到完整线程自然延续 |
| 记忆 | 扁平事实注入,不是状态机 |
| 工具选择 | **模型自选**,靠 tool description 质量,不靠外部 router |
| 显式任务对象 | 只给 Deep Research / Tasks / Agent mode,且**用户显式创建**,不靠推断 |
| 路由 | 路由的是**算力**fast vs thinking),不是**任务身份** |
| 上下文压缩 | 滚动摘要,不是手写 schema |
**核心推论:GPT 的稳定性来自「不切断上下文」+「不每轮重判」,而非「更强的控制层」。** 原方案的两个主要动作恰好反向:切断上下文(WM 替代历史)+ 加强重判(每轮 Resolver)。
---
## 4. 代码核查结论
以下均为 `main``416bf7a2`)只读核查,附 file:line 证据。
### 4.1 R1:上下文单向丢失
割裂是**不对称**的:
| 翻转方向 | Goose / LLM 能否看到上一轮 | 性质 |
|---|---|---|
| agent → direct | 通常能(经 snapshot 回写) | 有竞态窗口,且仅 12 条 |
| direct → agent | **确定看不到** | 真实 gap |
`h5direct_` 会话升级时,旧 transcript 只写入 DB
- `agent-run-gateway.mjs:2071-2089``persistSessionTranscriptMessages``h5_conversation_messages`
- `agent-run-gateway.mjs:2094-2105` — 同 Goose session 上的 direct 轮次同理
`h5_conversation_messages` 仅用于 GET 展示修复(`conversation-repair.mjs:243-251`),**不进入 Goose 推理输入**。Portal 每轮只 submit 当前一条消息(`tkmind-proxy.mjs:2095-2098`),Goose 依赖自己服务端的会话历史——但 direct 轮次从未写入 Goose。
**关键发现:修复所需机制已存在且久经验证。** `buildFreshSessionContext``agent-run-gateway.mjs:202-224`16 条 / 12000 字符)与 `appendFreshSessionContext` 已在三处使用:
| 调用点 | 场景 |
|---|---|
| `agent-run-gateway.mjs:2117` | session compaction 后 |
| `agent-run-gateway.mjs:2157` | session rotation 后 |
| `agent-run-gateway.mjs:2268` | poisoned session 恢复 |
**唯独没有接到 direct → agent 升级路径。** 这不是缺架构,是一条已验证能力没接上入口。
### 4.2 R1 衍生:四条路径的历史窗口口径不一
| 路径 | 窗口 | 位置 |
|---|---|---|
| direct chat LLM | 12 条 | `direct-chat-service.mjs:145` |
| router 分类 | 8 轮 / 4000 字符,且**跳过 `h5direct_*`** | `chat-intent-router.mjs:724` |
| session rotation | 16 条 / 12000 字符 | `agent-run-gateway.mjs:202` |
| Goose agent | 服务端全量 | — |
router 明确跳过 `h5direct_*` 快照,意味着**上一轮若是 direct chatrouter 做路由判断时看不到它**。这直接解释短消息漂移。
### 4.3 R2:意图识别吸收态(决定性问题)
路由为「规则优先,规则返回 null 才进 LLM」,而规则兜底硬判 direct_chat
- `chat-intent-router.mjs:1366-1370` — 兜底 `direct_chat`confidence `0.72`
唯一能把控制权交给 LLM router 的开关是 `shouldDeferActiveTaskRoutingToLlm`,其第一行即要求存在活跃 agent 任务:
- `chat-intent-router.mjs:365``if (!activeTaskContext?.hasRecentAgentTask) return false;`
`activeTaskContext` 在聊天会话中恒为 null,两道门都关着:
- `agent-run-gateway.mjs:646-649``isDirectChatSessionId(sessionId)` 直接返回 null
- `agent-run-gateway.mjs:675-679` — 上一轮非 agent route 则返回 null
形成闭环:
```text
新对话
↓ 无活跃 agent 任务
规则兜底 → direct_chat
创建 h5direct_ 会话
↓ isDirectChatSessionId → activeTaskContext = null
shouldDeferActiveTaskRoutingToLlm = false
规则兜底 → direct_chat
(永远循环)
```
**这是一个吸收态。** 103 生产虽已设置 `MEMIND_CHAT_LLM_ROUTER_ENABLED=1` / `SHADOW=0`,但在纯聊天会话中该 LLM 不会被调用。**语义意图识别实际由正则承担**;唯一逃逸口是命中 `OBVIOUS_AGENT_PATTERNS` 等硬模式,或用户手动开启深度推理。
### 4.4 R3:安全网失效与谎报
`direct-chat-service` 的兜底检查对 router 判定的 direct_chat 主动放行:
- `direct-chat-service.mjs:199-201``routingDecision !== 'direct_chat' && requiresTaskExecution(...)`
即:误判概率最高的来源(0.72 兜底)享受最高信任级别。
同时 system prompt 指示模型谎报:
- `direct-chat-service.mjs:141` — 「简要说明**已交由后台任务处理**或请用户确认具体任务」
direct chat 无任何工具、无任何升级路径,实际什么都没排队。用户以为任务在跑,等待无果后回来追问,而追问那一轮因 4.3 的吸收态大概率仍是 direct_chat。**这条链路能完整解释「意图飘忽」的用户体感。**
### 4.5 交互质量评分
| 维度 | 评分 | 说明 |
|---|---|---|
| 用户可以正常聊天 | 良好 | 规则 fast-path,延迟低,有记忆 |
| 聊天流畅度 | 中等 | direct chat 非流式(`llm-providers.mjs:1670` `stream:false`),前端 600ms 轮询 |
| 聊天中识别意图 | **差** | 语义识别不启动,只剩正则 |
| 识别后触发执行 | **差** | 同轮无法升级;提示语谎报 |
| 误判后恢复 | **差** | 「不对,你去执行」在 `h5direct_` 会话不触发延续 |
| Agent 执行体验 | 良好 | 真 SSE、工具卡片、loading tips 齐全 |
---
## 5. 改造方案:Context Stability First
按 ROI 排序。P0–P1 不增加任何每轮 LLM 调用。
> **实施路径见 [§11 最低影响实施方案](#11-最低影响实施方案推荐落地路径)。** 本章描述目标形态,§11 描述推荐的落地顺序与降风险取舍;两者冲突时以 §11 为准。P2 的实现方式在 §11.4 被修订。
### P0 · 观测先行(零生产风险)
先把「漂移」变成可测数字。**数据源已存在,无需新埋点**:
| 指标 | 数据源 |
|---|---|
| 0.72 兜底占比 | `intent_routed` 事件的 `reason` / `confidence` |
| direct→agent 升级频率 | `direct_session_escalated_to_deep_reasoning` |
| 丢上下文的 agent 回答量 | `portal_direct_transcript_persisted` |
| direct chat 放行的任务类请求 | `direct_chat_skipped` / `direct_chat_completed` |
| route flip / skill flip 率 | 同 session 内 `intent_routed` 序列 |
| 短消息路由分布 | ≤6 字消息的 `intent_routed` |
外加人工标注 100 条真实漂移 case 做归因。**没有 P0,后续每层改动都无法证伪。**
### P1 · 打通 direct → agent 上下文
复用已验证能力,不引入新抽象:
| 步骤 | 内容 | 风险 |
|---|---|---|
| P1.1 | 升级路径接入 `appendFreshSessionContext` | 低,复用 rotation 同款函数 |
| P1.2 | router transcript 不再跳过 `h5direct_*` 快照 | 低 |
| P1.3 | 统一 12 / 8 / 16 三个窗口口径 | 低,需回归 |
### P2 · 打破吸收态:让模型自己声明要执行
**这是解 R2 + R3 的核心动作,也是与 GPT 做法一致的方向。**
现状是「回合开始前用外部分类器猜」。改为「让回答问题的那个模型自己决定调工具」:给 direct chat **一个**工具 `execute_task`
- direct chat 保留纯文本快速回答能力,闲聊体验不变。
- 当它判断需要执行时,不再输出「已交由后台处理」这句谎话,而是发出一次结构化调用。
- gateway 收到该调用 → 走升级路径 → 真正交给 Goose(并经 P1 带上完整聊天历史)。
收益:
1. 意图识别由**掌握完整上下文**的那个模型做,信息量远超前置分类器。
2. **不新增每轮 LLM 调用** —— 复用本来就要发的那次 completion。
3. **不依赖 `activeTaskContext`** —— 彻底绕开吸收态。
4. prompt 不再需要撒谎,「已交由后台」变成真的。
5. 与「工具由模型选,不靠外部 router」的判断一致。
同时移除 `direct-chat-service.mjs:199``direct_chat` 的无条件放行,让 `requiresTaskExecution` 在所有路由下都生效,作为第二道网。
### P3 · 非对称延续策略
**注意:不可照搬对称的「默认延续」,那会让 R2 更严重**(聊天会话里默认延续 = 永远聊下去)。正确形态是非对称的:
| 方向 | 策略 | 理由 |
|---|---|---|
| agent → agent | 强延续 | 防止任务执行中途掉回聊天 |
| chat → chat | **不延续** | 必须每轮保持对升级信号敏感 |
| chat → agent | 低门槛 | 当前是死区,需主动打开 |
| agent → chat | 需明确信号 | 防止任务被闲聊打断 |
同时把 `resolveActiveTaskContext``h5direct_` 的硬返回 null 放宽,使「不对,你去执行」类纠正在聊天会话中也能生效。
### P4 · Conversation State Card(轻量、非权威、异步)
Finish 后**异步**抽取,下轮注入 prompt
```text
【当前上下文】
目标:选购 25L 车载冰箱
已知约束:MPV / 宽度≤60cm / 压缩机制冷
候选:英得尔 KD25
```
与原方案 Working Memory 的四个关键差异:
- **非权威**:历史仍照常发送,Card 丢失不影响正确性。
- **异步**:不在请求关键路径,不增加延迟。
- **扁平**:仅 facts + constraints 两个列表,无 FSM、无 step 树。
- **可降级**:抽取失败则不注入,静默退化。
形态上可与既有 `goal-run-context.mjs:10``buildGoalContextEnvelope` 并列注入。
### P5 · 显式长任务对象(复用 Goal Run)
仅**用户显式发起**的长任务才创建 task 对象,且用户可见可控。不推断、不自动建、不新建表——直接复用 `goal-run-service` + `h5_goal_checkpoints`
### P6 · 聊天流式输出(体验项,可并行)
direct chat 当前 `stream:false` + 前端 600ms 轮询,长回答等待感明显差于 agent 路径。改为 token 流式可显著提升闲聊体验,与路由问题正交,可独立排期。
---
## 6. 明确不做
- 不做每轮阻塞式 Task Resolver LLM 调用。
- 不做 `UNDERSTANDING` / `PLANNING` 等不可观测状态。
- 不做 embedding 相似度阈值锁。
- 不把 conversation 降级为「证据」。
- 不新建第四套 task 存储模型。
## 7. 两案对比
| 维度 | 原方案(Task Runtime | 本方案(Context Stability First |
|---|---|---|
| 每轮额外 LLM 调用 | 1 次(阻塞) | 0 次 |
| 新增存储 | Task Runtime 表 + WM | 无(P4 可复用 snapshot 扩展字段) |
| 能否解 R1 | 否 | 是(P1 |
| 能否解 R2 | 否 | 是(P2 |
| 能否解 R3 | 否 | 是(P2 |
| 错误自愈 | 差(lock 放大错误) | 好(下轮即恢复) |
| 上下文权威源 | Working Memory | 完整会话历史 |
| 与 GPT 做法 | 相反 | 一致 |
| 可证伪 | 无基线指标 | P0 先建基线 |
| 首个收益出现 | 三模块全做完之后 | P1 完成即可见 |
## 8. 落地顺序与验证
| 阶段 | 内容 | 验证 |
|---|---|---|
| P0 | 观测基线 | 只读,无需回归 |
| P1 | 上下文打通 | `npm run verify:h5-session-patches` |
| P2 | `execute_task` 升级机制 | 新增 direct→agent 升级用例;`verify:h5-session-patches` |
| P3 | 非对称延续 | `chat-intent-router.test.mjs` 扩充;短消息路由用例 |
| P4 | State Card | 异步路径,失败降级用例 |
| P5 | Goal Run 归并 | 灰度开关内验证 |
| P6 | 流式输出 | 前端 SSE 用例 |
每阶段独立灰度、独立回归,符合 `AGENTS.md` 的单分支闭环与发布闸门要求。原方案「三模块一起上」在该治理下难以安全交付。
## 9. 演进留口
若 P0 归因数据显示漂移主因**确实**是「多步任务状态丢失」而非上述 R1–R3,再引入原方案的 Task State 抽象。届时它将是**有证据支撑的增量**,而非先建大厦再找用途。
原方案的 Goal → Task → Step 抽象本身没有错,错的是它的**引入时机与权威地位**。
## 10. 证据索引
| 结论 | 位置 |
|---|---|
| 规则兜底硬判 direct_chat | `chat-intent-router.mjs:1366-1370` |
| LLM router 唯一入口需活跃任务 | `chat-intent-router.mjs:359-366` |
| `h5direct_` 会话无 activeTaskContext | `agent-run-gateway.mjs:646-649` |
| 上一轮非 agent 则无 activeTaskContext | `agent-run-gateway.mjs:675-679` |
| 升级仅持久化到 DB | `agent-run-gateway.mjs:2071-2089``2094-2105` |
| DB 仅用于展示修复 | `conversation-repair.mjs:243-251` |
| Goose 只收当前消息 | `tkmind-proxy.mjs:2095-2098` |
| 历史注入能力已存在 | `agent-run-gateway.mjs:202-224`、调用点 `2117` / `2157` / `2268` |
| 安全网对 direct_chat 放行 | `direct-chat-service.mjs:199-201` |
| prompt 指示谎报已交后台 | `direct-chat-service.mjs:141` |
| direct chat 非流式 | `llm-providers.mjs:1670` |
| direct chat 历史窗口 12 条 | `direct-chat-service.mjs:145` |
| router 跳过 h5direct 快照 | `agent-run-gateway.mjs:705-706` |
| 规则命中即短路,LLM 拿不到控制权 | `chat-intent-router.mjs:1737` |
| shadow 模式可零行为变更观测 | `chat-intent-router.mjs:1786-1809` |
| LLM 覆盖需过 minConfidence | `chat-intent-router.mjs:1812-1824` |
| router 对非召回问题不调 Memory V2 | `memory-intervention.mjs:20-22` + `chat-intent-router.mjs:1522-1528` |
---
## 11. 最低影响实施方案(推荐落地路径)
§5 描述目标形态,本章描述**怎么落地才能把回归风险压到最低**。核心思路:不一次做完 P0–P6,只修已证实的缺口,每步单独灰度、单独回滚、不碰 Memory V2 写链。
### 11.1 六条硬约束(全程遵守)
| # | 约束 | 目的 |
|---|---|---|
| C1 | Memory V2 只读,不改 `write` / `compact` / 候选 / lifecycle | 避免候选提取污染 |
| C2 | 记忆召回问题永远走 direct_chat(保留 `chat-intent-router.mjs:1237` 硬规则) | 不碰已验证的 recall 链路 |
| C3 | 不收紧 `direct-chat-service.mjs:199``direct_chat` 的豁免 | 避免闲聊被大批踢进 agent |
| C4 | State Card 不进 orchestration、不进 `remember-recent` | 避免 `[Memory Context]` 自我复制 |
| C5 | 每步独立 feature flag + 可观测事件 | 可灰度、可回滚 |
| C6 | 升级轮次的 memory resolve 使用**旧** sessionId | 避免 episodic 线索断档 |
### 11.2 阶段 A · 观测(零改动)
对应 §5 的 P0。只读统计,不改任何路由逻辑。
关键指标:0.72 兜底占比、`direct_session_escalated_to_deep_reasoning` 频率、升级后 `agent_memory_resolved.injectionEnabled=false` 占比、memory recall 是否 100% direct_chat。
**没有阶段 A,后续所有改动都无法证伪。**
### 11.3 阶段 B · 只补上下文
对应 §5 的 P1,但**只做 P1.1**,暂不做 P1.2 / P1.3。
只改一件事:direct→agent 升级时,把 snapshot 历史经 `appendFreshSessionContext` 注入 Goose 输入。
刻意不做:不改 router 兜底、不改 `canHandle`、不改 `preferDirectChat`、不碰 Memory V2 resolve、不引入 `execute_task`
Memory V2 兼容(C6):
```text
升级后 agent 首轮 resolve 应使用 escalatedDirectSessionId
而非新建的 Goose sessionId,直到确认 episodic 为 user-scoped。
```
Flag`MEMIND_DIRECT_ESCALATION_CONTEXT_ENABLED`(默认 0+ canary 名单。
降级:flag 关或注入失败 → 完全退回现有行为。
预期影响:闲聊无变化;Memory V2 无变化;升级后 agent 不再「失忆」;仅升级轮次略增 token。
### 11.4 阶段 C · 打开语义识别(对 §5 P2 的修订)
**这是本章对原方案改动最大的一节。**
#### 11.4.1 为什么不用「更多升级短语 + 深度推理开关」
一个直觉上更安全的做法是:不动路由,只扩充 `OBVIOUS_AGENT_PATTERNS`,并引导用户手动开深度推理。
**否决理由:** §4.3 论证的 R2 是「语义识别不启动,只剩正则」,而该做法是**再加正则**,并把判断责任推给用户。风险确实最低,但它不修复 R2,只是把死区包装得更礼貌。产品诉求「聊天中自动识别意图并执行」仍然落空。
保留价值:作为**逃逸通道**而非主机制。
#### 11.4.2 为什么暂缓 `execute_task`
§5 P2 提出给 direct chat 一个 `execute_task` 工具。方向正确,但当前 `llm-providers.mjs``createChatCompletion` **不支持 tools / tool_calls**,落地需同时改:direct chat 的 function calling、同轮切 agent 的状态机、run 事件与 UI 时序、sessionId 切换、计费与 Finish 对齐。
风险集中且互相耦合,不适合作为第一刀。**暂缓,不否决。**
#### 11.4.3 采用:让已有 LLM router 在聊天会话可达
代码核查显示,语义识别失效的原因是**一行短路**:
```text
chat-intent-router.mjs:1737
if (ruleResult) return finalizeWithCoercion(ruleResult);
```
规则一旦返回结果,LLM 分支永远拿不到控制权;而规则兜底恒返回 `direct_chat``:1366-1370`)。
因此最小改动是:**让 0.72 兜底在「歧义闸门」通过时返回 `null`**,把控制权交给已经部署好的 LLM router。
歧义闸门复用既有纯函数,不新增判断逻辑:
```text
通过闸门 = 非 memory recall
且 非 FAQ / 寒暄 / 纯文字创作(isExplicitDirectChatOnlyText 已覆盖)
且 文本长度达阈值
```
**这样做的优势:**
1. **零新基础设施** —— shadow 模式、canary 名单、`minConfidence` 覆盖闸门均已就位。
2. **可零行为变更观测** —— `policy.shadowMode` 下只记录 `wouldChangeRoute`,路由完全不变(`:1786-1809`)。这同时补齐了阶段 A 缺失的「LLM 会怎么判」这一维数据。
3. **覆盖有闸门** —— LLM 置信度低于 `minConfidence`(生产 0.65)时自动退回 baseline`:1812-1824`)。
4. **不增加 Memory V2 负载** —— router 对非召回问题 `resolveMemoryInterventionMode` 返回 `SKIP``limit<=0` 时直接返回空 context,不调用 `memoryV2.resolve`
#### 11.4.4 三步灰度
| 步骤 | 配置 | 行为 | 判据 |
|---|---|---|---|
| C-1 | 闸门开 + `MEMIND_CHAT_LLM_ROUTER_SHADOW=1` | **路由不变**,仅记录 | 统计 `wouldChangeRoute` 比例 |
| C-2 | `SHADOW=0` + canary 名单 | 小范围生效 | agent 触发率增幅、误判反馈 |
| C-3 | 移除 canary | 全量 | 同上 + 成本曲线 |
任一步指标超阈值即回退上一步。新增 flag:`MEMIND_CHAT_ROUTER_CHAT_SESSION_DEFER_ENABLED`(默认 0)。
#### 11.4.5 成本与延迟
这是本阶段**唯一真实代价**:通过闸门的聊天轮次会多一次小模型 router 调用(生产为 `deepseek-chat`),在 direct chat 应答前引入约数百毫秒。
缓解:歧义闸门收窄触发面;沿用既有 `policy.timeoutMs` 超时降级;C-1 阶段先用 shadow 数据估算实际触发比例再决定是否推进。
#### 11.4.6 同步做:去掉谎报
独立于路由改动,属纯文案修改,零回归面:
```text
direct-chat-service.mjs:141
「简要说明已交由后台任务处理」
→ 明确说明当前只能文字讨论,并给出可用的升级方式
```
阶段 C 生效后,该文案应再次收敛——升级由系统完成时,不应继续提示用户手动操作。
### 11.5 阶段 D · 可选纠偏
仅当阶段 A/C 数据显示「短句纠偏失败」占比超过阈值时才做。
范围收窄为:session 为 `h5direct_` **且** 上一轮 `direct_chat_completed` **且** 命中 `AGENT_TASK_FOLLOWUP_PATTERNS` → 强制 agent,并复用阶段 B 的上下文注入。
不做:全面放宽 `resolveActiveTaskContext``h5direct_` 的 null 返回;不做 chat→chat 默认延续。
需一并评估 `preferDirectChat` 的 sticky 行为(`agent-run-gateway.mjs:1871-1874`):只要 session 仍是 `h5direct_*`,即使 router 判 agent,仍可能优先尝试 direct chat。纠偏链路若不处理这一点会不完整。
### 11.6 明确后置
| 项 | 后置理由 |
|---|---|
| `execute_task` tool calling | 需改 LLM 层 + 同轮状态机 + UI 时序,耦合最重(§11.4.2 |
| 收紧 `requiresTaskExecution` 豁免 | 会大幅增加 agent 流量,且可能使 Memory 注入倒退(§12.2 |
| P1.2 router 读 `h5direct_` transcript | 与阶段 C 效果重叠,应在 C 之后单独评估 |
| P1.3 统一 12/8/16 窗口 | 独立回归面,收益不确定 |
| P4 State Card | 阶段 B 已补上下文,边际收益下降;且有 Memory V2 污染风险 |
| P6 流式输出 | 体验收益明确但动前后端 Finish 时序,应独立排期 |
### 11.7 阶段对照与验收
| 阶段 | 对应 §5 | 风险 | 验收标准 |
|---|---|---|---|
| A 观测 | P0 | 无 | 拿到 0.72 兜底占比与升级频率基线 |
| B 上下文 | P1.1 | 低 | 升级后 Goose 输入含 prior contextmemory recall 仍 100% direct`verify:h5-session-patches` 通过 |
| C 语义识别 | P2(修订) | 中,可灰度 | shadow `wouldChangeRoute` 在预期区间;canary 期 agent 触发率增幅低于阈值;`RESOLVED_INJECTED` 比例不下降 |
| D 纠偏 | P3(收窄) | 中 | 「不对你去执行」在 `h5direct_` 可进 agent;误触发率低于阈值 |
回归范围(除 `verify:h5-session-patches` 外):`chat-intent-router.test.mjs``direct-chat-service.test.mjs``agent-run-gateway.test.mjs`,以及 `scripts/verify-world-cup-flow-e2e.mjs`(该脚本显式断言不应出现 `direct_chat_completed`,对阶段 C 敏感)。
---
## 12. Memory V2 兼容性
### 12.1 现状:三条独立注入路径
| 路径 | 位置 | 触发条件 | 开关 |
|---|---|---|---|
| Router | `chat-intent-router.mjs:1537-1548` | 仅记忆召回问题(非召回为 SKIP) | `MEMIND_CHAT_ROUTER_MEMORY_ENABLED`,默认开 |
| Direct chat | `direct-chat-service.mjs:302-312` | **每轮**limit=5 | 不依赖 agent 开关 |
| Agent | `agent-run-gateway.mjs:1790-1818` | 注入进 `[Memory Context]` | `MEMORY_AGENT_RESOLVE_ENABLED`**默认关** |
关键不对称:**direct chat 每轮必 resolveagent 默认不注入。**
### 12.2 各阶段影响
| 阶段 | 对 Memory V2 的影响 |
|---|---|
| A 观测 | 无 |
| B 上下文 | 低。仅拼接历史文本,不走 resolve/write。风险点是 sessionId 变更导致 episodic 断档 → 由 C6 约束覆盖 |
| C 语义识别 | **低于直觉**。router 对非召回问题不调 `memoryV2.resolve`,故不增加负载。真实风险是**流量从 direct(必注入)转向 agent(默认不注入)**,可能造成记忆注入率下降 |
| D 纠偏 | 同 C,量级更小 |
| 后置项 | State Card 若与 `[Memory Context]` 混写,可能触发候选提取污染(见 [Memory V2 候选与生命周期守卫](../regression-guards/memory-v2-candidate-and-lifecycle.md) 记录的旧记忆自我复制问题) |
### 12.3 不会被破坏的行为
候选表与 lifecycle、pgvector、admin 配置、`remember-recent` / `sync` API、fail-open 语义、goosed 零改动承诺——本方案均不触及。
记忆召回硬规则(`chat-intent-router.mjs:1237`)保证「你记得我上次说的吗」类问题不会被阶段 C 误判进 agent;`isExplicitDirectChatOnlyText` 亦已将 memory recall 纳入排除集(`:337`)。
### 12.4 强制要求
1. 阶段 B/C 上线前记录 baseline`agent_memory_resolved``RESOLVED_INJECTED``RECALL_HIT` 在两条路径上的分布。
2. 阶段 C 推进到 C-2 之前,确认目标 canary 用户的 `MEMORY_AGENT_RESOLVE_ENABLED` 覆盖情况;否则「任务能执行了,但记忆注入变少」。
3. 阶段 B 升级路径的 memory resolve 使用旧 sessionIdC6)。
4. 任何 State Card 类注入严禁进入 write 链(C1 / C4)。
---
## 13. 修订记录
| 日期 | 变更 |
|---|---|
| 2026-08-28 | 初版:R1R3 根因核查、原 Task Runtime 方案评估、P0P6 目标形态 |
| 2026-08-28 | 增补 §11 最低影响实施方案:将 P2 的实现方式从 `execute_task` 修订为「让已有 LLM router 在聊天会话可达」,暂缓 tool callingP1 收窄为仅 P1.1P3 收窄为仅 `h5direct_` 纠偏 |
| 2026-08-28 | 增补 §12 Memory V2 兼容性:三条注入路径现状、各阶段影响、四条强制要求 |