diff --git a/docs/algorithms/retrieval.md b/docs/algorithms/retrieval.md index 2a85f12..6e0161e 100644 --- a/docs/algorithms/retrieval.md +++ b/docs/algorithms/retrieval.md @@ -28,11 +28,15 @@ 召回输入按优先级解析(`resolveRecallInputController`):override → 待发送意图(send intent)→ 聊天尾部用户楼层 → 已发送用户 → 最新用户楼层。 +控制器里的来源/类型判定保持为小型纯 helper:active input source、no-new-user generation type、可信 user-floor source、持久复用输入构造分别独立测试。它们只做字符串规范化和布尔判定,不调用 `retrieve()`、不写消息、也不触碰生成事务。 + **持久召回复用有两条路径:** 1. **no-new-user 主路径**(`reapplyPersistedRecallBlock`):reroll / swipe / regenerate / continue 由宿主 `type` 判定为 no-new-user 后,`GENERATION_AFTER_COMMANDS` 不计算召回;`GENERATE_BEFORE_COMBINE_PROMPTS` 直接读取父 user 楼层的 `message.extra.bme_recall`,校验绑定文本未过期后确定性重放注入块。命中后不会进入 transaction / `runRecall` / 新检索。 2. **compute fallback 内部复用**(`resolveReusablePersistedRecallRecord`):当主路径没有可用记录(例如无记录或陈旧)而落回 `runRecallController()` 时,如果当前输入匹配某条已持久化的用户楼层召回记录,可在控制器内复用已存注入内容,跳过新检索,返回 `llm.status="persisted"`。 +内部复用命中后,控制器只重写本次 effective recall input 的来源为 `persisted-user-floor`,并保留原 delivery mode / hook / source candidates 等上下文字段;真正注入、generation count bump、metadata save 仍由原路径执行。 + fresh `normal` 发送仍走正常输入选择与召回计算路径;no-new-user 的父楼层绑定来自宿主生成上下文,而不是根据 textarea / send-intent 等输入源猜测(见 [`../architecture/control-plane.md`](../architecture/control-plane.md) 的 reroll 不变量)。 ## 5. 向量预筛 diff --git a/docs/architecture/control-plane.md b/docs/architecture/control-plane.md index 715e05f..92462e9 100644 --- a/docs/architecture/control-plane.md +++ b/docs/architecture/control-plane.md @@ -79,7 +79,8 @@ - `vector/vector-gate.js` — 向量准备/修复前置门禁,决定 skip / repair / blocked / sync。 - `runtime/generation-context.js` — 记录宿主本轮生成的 `type`(`normal` / `swipe` / `regenerate` / `continue` 等),并解析本轮应绑定的父 user 楼层。 -- `runtime/reroll-recall-input.js` — 基于代际上下文构造召回输入;不再用一次性 marker 猜测 reroll。 +- `runtime/reroll-recall-input.js` — 基于代际上下文构造召回输入,并保存 planner recall handoff / plot record handoff;不再用一次性 marker 猜测 reroll。 +- `retrieval/recall-controller.js` — 召回控制器;来源/类型/持久复用输入构造是纯 helper,检索执行和注入副作用仍留在控制器热路径里。 **reroll 不变量:** @@ -96,6 +97,8 @@ no-new-user 的稳定路径分两段: 旧的召回事务机制仍保留为 fresh normal 和 fallback compute 的基础设施;它不再是 reroll 已存召回注入的唯一门闸。 +ENA Planner 另有一条 plot record handoff:它只负责把 planner 产出的剧情推进记录绑定到新 user 楼层的 `message.extra.st_bme_plot`,不参与召回决策。这样剧情历史持久化不依赖 planner recall 是否成功。 + ## 副本一致性模型 Authority 场景下有三处存储,它们**不是平级的版本副本**: diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md index 150c4d1..2d5d0ae 100644 --- a/docs/contributing/testing.md +++ b/docs/contributing/testing.md @@ -9,7 +9,7 @@ ST-BME 的测试是 Node 回归测试(`tests/*.mjs`),`npm run test:stable` | 控制平面 | `identity-resolver` / `persistence-reducer` | 身份解析、持久化状态机不变量 | | 数据格式 | `graph-snapshot-schema` / `graph-snapshot-upgrade` / `snapshot-forward-compat` | 快照契约、宽容解析、向前兼容往返 | | 持久化 | `graph-persistence` / `indexeddb-*` | 图谱持久化、IndexedDB 快照/增量/hydrate | -| 检索/召回 | `p0-regressions` 内相关、`trivial-user-input` | 召回、reroll 复用、注入 | +| 检索/召回 | `p0-regressions` 内相关、`recall-controller-helpers`、`recall-reroll-reuse`、`trivial-user-input` | 召回来源判定、reroll 复用、注入 | | 向量 | `vector-gate` / `vector-connection-probe` / `vector-sync-coalescer` | 向量门禁、连接探测、后台同步合并 | | Native | `native-layout-parity` / `native-rollout-matrix` | native/JS 一致性、灰度门控 | | 防线 | `index-slicing-ratchet` / `runtime-deps-completeness` / `i18n-user-visible-ratchet` | 见下 | @@ -53,6 +53,7 @@ ST-BME 的测试是 Node 回归测试(`tests/*.mjs`),`npm run test:stable` ## 重要测试文件 - **`tests/p0-regressions.mjs`** — 主回归集合,覆盖提取、召回、恢复、UI 关键路径。 +- **`tests/recall-controller-helpers.mjs`** — 召回控制器的纯来源/类型/持久复用输入 helper。 - **`tests/runtime-history.mjs`** — 消息 hash、历史 dirty、恢复状态。 - **`tests/message-render-limit.mjs`** — 聊天区渲染限制和渲染切片历史保护。 - **`tests/graph-persistence.mjs`** — 图谱持久化基础行为。 diff --git a/docs/features/ena-planner.md b/docs/features/ena-planner.md index 086bf3b..9a23fab 100644 --- a/docs/features/ena-planner.md +++ b/docs/features/ena-planner.md @@ -20,7 +20,7 @@ ENA Planner 是一个**可选的、发送前剧情规划**子系统。它独立 ``` 拦截发送(点击发送/回车) → 构建规划消息(buildPlannerMessages) - → 收集上下文:角色卡 + BME 记忆召回 + 近期 AI 对话 + 历史 + 世界书 + 用户输入 + → 收集上下文:角色卡 + BME 记忆召回 + 近期 AI 对话 + 结构化/旧式 plot 历史 + 世界书 + 用户输入 → 渲染模板/宏(EJS、ST 宏) → 组装提示词块(优先用 planner 任务预设,回退遗留块) → 调用规划师 LLM(callPlanner,可流式) @@ -34,10 +34,11 @@ ENA Planner 是一个**可选的、发送前剧情规划**子系统。它独立 ## 与 ST-BME 的集成 -ENA Planner 集成的是**召回**,不是提取: +ENA Planner 集成的是**召回**和**剧情历史记录**,不是提取: - 它调用 BME 召回获取记忆块作为规划上下文(`runPlannerRecallForEna`)。 - 规划输出注入用户文本后,主生成会把规划标签当作用户消息的一部分看到。 +- 规划输出会以结构化记录写入用户楼层 `message.extra.st_bme_plot`;后续规划优先读取这个记录,读不到时再回退扫描历史文本中的 ``。 - 它**不**直接运行提取,也**不**把规划结果写进记忆图谱。后续提取走正常聊天/提取路径。 ### 召回交接(handoff) @@ -48,6 +49,32 @@ ENA Planner 集成的是**召回**,不是提取: 这套机制的实现见 `runtime/planner-recall-controller.js`、`runtime/reroll-recall-input.js`、`runtime/generation-recall-transactions.js`。 +### 结构化剧情历史 + +历史 plot 不再只依赖“从聊天文本里扫描 ``”。Planner 发送时会准备一条独立的 plot record handoff;`MESSAGE_SENT` 绑定到新 user 楼层后写入: + +```js +message.extra.st_bme_plot = { + version: 1, + rawUserInput, + plannerAugmentedMessage, + plotText, + plotBlocks, + inputHash, + createdAt, + recallHandoffId, + taskResults: [] +} +``` + +读取顺序: + +1. 优先读取 `message.extra.st_bme_plot` 中的结构化 `` 块。 +2. 若结构化记录不足 `plotCount`,用历史消息文本里的旧式 `` 补足。 +3. 只把 `` 内容喂回 planner;`` / `` 等标签不会因为结构化记录而混入历史 plot 区。 + +plot record handoff 和 recall handoff 是两条独立通道:即使 planner 召回失败或被禁用,只要 planner 产出了 ``,剧情历史仍可持久化。这避免了“剧情推进记录依赖召回成功”的隐式耦合。 + ## 规划召回 vs 正常召回 | 维度 | 规划召回 | 正常召回 |