Files
memind/TKMIND_V1_49_MIGRATION.md
T
john 716ef407fe feat(goose): add local v1.49 canary routing, smoke gates, and migration docs
Wire Portal and TKMind proxy to loopback Goose v1.49 via canary env blocks,
with verification scripts, Phase 2/3 evidence baselines, and rollback runbooks
so local upgrade stays isolated from stable 1.41 and production.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-08 21:10:20 +08:00

180 lines
17 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.
# TKMind Goose v1.49 迁移决策表
## 目标与边界
本次升级的目标是“升级运行时基线 + 选择性吸收 upstream”,不是把 TKMind 变成 vanilla Goose。
- Layer 0PG Session、多实例 affinity、9 实例拓扑——保留。
- Layer 1goosed 运行时、安全修复、agent loop——跟随 upstream v1.49。
- Layer 2:通用能力的 platform extensions——优先改为薄封装,逐项退役重复实现。
- Layer 3`tkmind_compat`、harness、sandbox-fs MCP——保留,确保 Portal/H5 契约与业务语义不变。
- Layer 4Portal、微信、计费、MindSpace、Page Data——保留,属于产品层。
迁移完成的判定不是“upstream 代码已合入”,而是:新旧运行时在受保护业务路径上行为等价,且每个可退役定制都有 canary 证据与可执行回滚。
## Memory 无缝迁移硬闸门
Memory 不能作为普通 extension 随运行时升级一起替换。这里必须分别保护四类状态,不能只比较“能否召回”这一项:
| 状态层 | 当前事实/责任 | v1.49 原则 |
|---|---|---|
| Goose Session | PG 共享 session、`agent_session_id`、SSE/Finish 状态 | 只适配 runtime API;不得让 upstream 本地 SQLite 成为隐式第二份真相。 |
| 业务记忆 | MySQL `h5_conversation_messages``h5_user_memory_items``h5_episodic_memory_items` | 保持原表、原 ID、原 user/session/evidence 关联与原写入语义;upstream 不得接管 owner。 |
| V2 候选/生命周期 | `h5_memory_v2_candidates` 及 promotion/expire/forget | 继续由 Portal policy 控制;`off` 必须零写入,所有操作按 user scope 严格隔离。 |
| 语义索引/项目记忆 | `memory_embeddings`、可选向量后端、harness bootstrap/remember | 只能是可重建派生数据或受控项目记忆;必须保留来源 ID、embedding 版本和回填水位,不能反向覆盖业务记忆。 |
以下条件未全部满足时,Memory owner 必须保持现有 `conversation-memory` / Portal 链路,v1.49 只能旁路 shadow 观察:
- [ ] 同一 `userId` 能在旧运行时、新运行时、Portal 和所有 Memory adapter 中稳定映射;不能用 display name、邮箱或临时 session ID 代替。
- [ ] `agent_session_id``source_session_id``evidence_message_id`、memory `id``memory_hash` 的映射已导出并逐项对账;不能因 session ID 重建造成重复记忆或丢 evidence。
- [ ] 迁移期间只有一个业务记忆写入 owner。禁止旧链路和 upstream 同时写入同一事实,避免重复提取、hash 冲突和不可逆覆盖。
- [ ] 新 runtime 先双读/对照旧结果,再考虑切换 resolve;`write``compact`、promotion、expire、forget 仍走旧 owner,直到对账稳定。
- [ ] 已建立按用户的 count、ID 集合、hash、状态、更新时间、来源 session 和 evidence 外键对账;总数相同不能替代逐用户逐 ID 对账。
- [ ] 已覆盖增量追平:固定 cutover watermark,回填期间捕获 watermark 之后的新写入;不能依赖从 `updated_at=0` 开始的有限批次。
- [ ] 向量索引记录 embedding provider/model/version/dimensions、文本规范化版本、来源 memory ID 和写入时间;维度或模型变化必须新建 namespace/table 或完整重建,禁止混索引。
- [ ] 向量不可用、索引落后、embedding 失败、upstream 超时或结果不一致时,必须 fail-open 回到 legacy exact/recent recall;不得阻塞聊天或删除原记忆。
- [ ] `remember-recent` 使用原始用户消息(优先 `h5_agent_runs.user_message_json`),不得把注入的 `[Memory Context]` 再提取成新记忆;空数组必须表示“无需保存”。
- [ ] 回滚只切回 resolve/write/compact owner 与 feature flag,不删除候选表、向量表或原始记忆;回滚后仍能从原始表重建派生索引。
### Memory 数据不对接上的常见盲点
1. **只迁 goosed session,不迁业务记忆关联**session 恢复成功不代表 `userId → source_session_id → evidence_message_id` 仍然成立。
2. **把向量库当主库**:向量结果是派生索引,不能替代 MySQL 事实、状态和 forget/expire 语义。
3. **双写但没有幂等键**:必须以稳定的业务 memory ID/hash + user namespace 幂等 upsert;不能用每次 embedding 的随机 ID。
4. **只比总数不比内容**:同数但 user 串线、status 丢失、evidence 外键断裂或更新时间截断,都会表现为“数据在但接不上”。
5. **忽略提取输入污染**:新 runtime 注入的 Memory Context 可能被再次保存,造成旧记忆复制和权重膨胀。
6. **把 schema/DDL 当启动副作用**:本地可以显式建空表并验证;生产 schema、备份、回填和 owner 切换必须是独立审批步骤,本次升级禁止触碰 103。
## 本地优先与生产隔离规则
本次迁移只能在本地 loopback、隔离数据库和临时 workspace 开始。未获得新的明确授权前,禁止:
- 连接 103、105 或任何生产数据库、生产 goosed、生产 MindSpace 服务;
- 执行生产发布、runtime/artifact 生成、远端迁移、远端 backfill、远端 schema DDL 或生产重启;
- 使用生产 `DATABASE_URL``GOOSE_SESSION_DB_URL`、Memory provider key 或生产数据做测试;
- 将 upstream Memory backend 接入 Portal 主路径,或修改 production profile 的 Memory 开关。
本地测试必须使用显式 loopback 地址和隔离数据库名,例如 `127.0.0.1` 的 MySQL/PG;测试启动前应打印并检查目标 host、database、runtime profile,命中 `103``105`、公网数据库或 production profile 时直接失败。优先使用脱敏 fixture 或本地数据副本,测试结束删除临时用户/会话/索引,但保留可复核的对账报告。
## 决策规则
| 判断问题 | Replaceupstream 替代 | Keep:保留定制 | Thin wrapper:薄封装 upstream |
|---|---|---|---|
| 是否 TKMind 独有? | 否 | 是 | 部分独有 |
| upstream 是否明显更成熟? | 是 | 否或不对等 | 是,但缺 TKMind 钩子 |
| 替换后 H5/微信契约是否不变? | 是 | 不适用 | 需要 adapter 层 |
| 是否绑定 MindSpace、计费或权限? | 否 | 是 | 部分绑定 |
只有“非独有 + upstream 更强 + 契约可不变”同时成立时,才直接 Replace。任何涉及用户空间、发布、计费、权限、微信 ACK 或 PG Session 的能力,默认先 Keep;若能复用 upstream 内核,采用 Thin wrapper。
## 定制项决策表
状态含义:`Candidate` 表示建议方向,尚未凭 canary 取得退役资格;`Keep` 表示本次不以 upstream 替代;`Wrapper` 表示保留入口与契约,内部逐步换内核。
| 定制项 | 决策 | v1.49 迁移动作 | 退役/切换门槛 | 回滚点 |
|---|---|---|---|---|
| `platform_extensions/web.rs`DuckDuckGo + fetch | Wrapper → Replace | 保留现有 extension 名与 H5 调用入口;内部优先转发 upstream `web-search` / `browser-use` skill。 | 搜索、抓取、超时、fallback、权限与结果格式回归通过;无 TKMind 专属域规则回归。 | 切回旧 extension 实现。 |
| `session-reply-wait` 与 Portal 侧 DeepSeek workaround | Wrapper / Keep(分阶段) | 先在 v1.49 保留等待、Finish、重试与游标适配;对 upstream reasoning 修复做独立 canary。 | 工具调用后的 `reasoning_content`、SSE Finish、断线续播、重复消息与超时回归通过。 | 保留 `session-reply-wait` 与旧兼容链路。 |
| DeepSeek no-think 代理(`:18036` | Wrapper → Pass-through | 先保留独立代理与端口;验证 v1.49 原生兼容后,再将代理改为 pass-through,最后才评估停用。 | 生产同构工具轮次通过,候选失效时可自动回落稳定版本;不得仅凭版本号拆除。 | 恢复旧 `:18036` no-think 处理。 |
| `billing-token-state` 粗估价 | Candidate Replace / Simplify | 优先读取单条消息 usage/cost 与 cache token`accumulated_cost` 稳定时,Portal 仅保留缺失字段时的保守估算。 | Finish 稳定带完整累计成本;账单、margin、补偿与幂等回归通过;不能出现超扣。 | 恢复 Portal 估算逻辑。 |
| 部分 message sanitize 补丁 | Candidate Replace | 对比 v1.49 provider message 处理与本地补丁 diff;只删除已被 upstream 覆盖且契约等价的分支。 | DeepSeek、多模态隔离、工具轮次、历史消息显示回归通过。 | 恢复对应 sanitize 分支。 |
| PG Session 多实例共享 | Keep | 移植到 v1.49 session/runtime 接口;保持 PG、9 实例 affinity 与共享 session 语义。 | 多实例并发、重启恢复、session 一致性与故障切换通过。 | 保留旧 PG adapter / 配置。 |
| `tkmind_compat``/sessions` 等 REST adapter | Keep | 仅适配 upstream v1.49 内部 API;对外 REST/H5 契约不变。 | Portal、H5、微信客户端不需要改请求格式;旧客户端回归通过。 | 切回旧 goosed adapter。 |
| `tkmind_memory`harness bootstrap/remember | Keep / Wrapper | 保留项目记忆与 capability 门控;若 upstream 有 projects/memory 原语,只在内部复用,不改变 TKMind 入口。 | bootstrap、remember、权限边界与项目级记忆回归通过。 | 使用现有 harness 实现。 |
| sandbox-fs MCPMindSpace、Page Data、`generate_image` | Keep | 按 v1.49 extension/MCP 生命周期接口移植;保持路径隔离、页面落盘、缩略图 fallback 与图片策略。 | 发布/改页、Page Data、图片生成、私有路径隔离、Transport closed 重试回归通过。 | 保留现有 sandbox-fs 子进程与 Portal fallback。 |
| aider / openhands platform extensions | Keep | 保持 `coding_router`、微信与 agent-run 策略;只适配运行时加载接口。 | coding 路由、模型名映射、权限与失败回退不变。 | 保留旧 extension。 |
| `custom_tkmind_relay_deepseek` | Keep | 保留计费、margin、Admin 模型中心绑定;若 upstream provider 足够,只替换内部传输实现。 | provider/key/model 目录、计费归属、margin 与 canary 策略回归通过。 | 切回 custom relay。 |
| `capabilities.mjs` + extension policy | Keep / Wrapper | 保留用户权限、canary、微信策略;可将通用 extension 解析委托给 upstream。 | capability 不越权,`sandbox-fs``read_image` 等关键策略不回归。 | 使用现有 policy builder。 |
## 明确不替换的产品契约
以下能力不能因为 upstream 1.49 提供了相似原语就直接删除:
- MindSpace 页面发布、`edit_file` 落盘、Finish 合并与 Page Data 交付。
- `sandbox-fs` 的 workspace 路径隔离、图片生成、缩略图 fallback 与 MCP 失败兜底。
- 微信 5 秒 ACK、消息路由、页面链接验证与定时任务投递。
- 计费、usage/cost、margin、provider/model 权限与 canary 策略。
- PG Session 共享、9 实例 affinity、Portal/H5 `/sessions` 等兼容 REST。
- SSE 断线续播、Portal replay ID 与 Goose `Last-Event-ID` 的映射。
- 私有 MindSpace 页 noindex、confirmed public 才可收录的 SEO/GEO 策略。
## 分阶段迁移清单
### Phase 0:冻结基线
- [x] 记录当前 1.41/现生产候选的 commit、runtime artifact、配置与端口。(见 [docs/goose-v149-local-phase0-manifest.md](docs/goose-v149-local-phase0-manifest.md)
- [x] 拉取 upstream `v1.49.0`;创建 `upgrade/v1.49-tkmind` worktree`/Users/john/Project/tkmind_go-v149`)。
- [x] 本地隔离 PG 库 `goose_sessions_v149_dev`;启动脚本 `scripts/run-goosed-v149-local.sh`(端口 `18049`)。
- [x] 建立旧版与 v1.49 的 canary 开关,以及一键回落路径(`scripts/switch-goose-v149-canary.sh` + [docs/goose-v149-canary-runbook.md](docs/goose-v149-canary-runbook.md))。
- [x] 保存关键业务场景的输入、SSE 事件、Finish 状态、usage/cost 与产物校验结果(`node scripts/capture-goose-v149-baseline.mjs` + `node scripts/check-goosed-v149-all.mjs`)。
- [x] 导出本地基线 Memory manifest:每用户 memory ID/hash/status/source session/evidence、候选状态、向量 namespace/version`node scripts/export-goose-v149-memory-manifest.mjs`)。
- [x] 记录 schema 版本、索引版本、回填 watermark、时区和字符规范化规则(见 manifest `schemaVersion` / `cutoverWatermarkMs` / `textNormalization` 字段)。
### Phase 1:运行时升级
- [x] 将 upstream v1.49 安全修复、agent loop 与 provider 变更移植到 Layer 1(基线已在 v1.49.0 tag)。
- [x] 适配 `tkmind_compat`、PG Session、harness 的加载/生命周期接口(P0 路由 + PG 双后端)。
- [x] 适配 sandbox-fs 的加载/生命周期接口(`extension_overrides` + `/agent/tools` 验证)。
- [x] 自定义 provider 路由(`/config/custom-providers``/config/upsert`+ `custom_tkmind_relay_deepseek` 本地 reply Finish smoke。
- [x] aider / openhands platform extensions + `deploy/coding_router.sh``check-goosed-v149-executors.mjs`)。
- [x] v1.49 初始配置强制 `MEMORY_BACKEND=legacy``MEMORY_VECTOR_ENABLED=0`、lifecycle/promotion/injection 为 off/shadow`run-goosed-v149-local.sh` + `check-goosed-v149-memory-policy.mjs`)。
- [x] 先验证旧 owner 在 v1.49 上完成“写入 → compact → 新 session resolve”的本地闭环(`check-goosed-v149-memory-loop.mjs`)。
- [x] 保持 Layer 34 对外接口不变(Portal canary 联调:`check-goosed-v149-canary-portal.mjs` ✅;主 Portal canary + `check-goosed-v149-portal-smoke.mjs` ✅;`POST /config/read` 已移植并纳入 smoke)。
### Phase 2:逐项 canary
- [x] web/searchTKMind `platform/web` 已移植至 v1.49`check-goosed-v149-web-smoke.mjs`);upstream `web-search` skill 对照仍保留。
- [x] DeepSeek:工具调用 Finish`check-goosed-v149-deepseek-tools.mjs`);reasoning 保留单测(`check-goosed-v149-thinking-preservation.mjs` ✅);`:18036` no-think 代理与 Portal replay 仍保留。
- [x] upstream web-search`load_skill` 路径 smoke`check-goosed-v149-skills-smoke.mjs`);TKMind `platform/web` 仍为主 H5 契约。
- [x] cost`usage`、cache token、`accumulated_cost`、估算回退与账单金额对照(`check-goosed-v149-cost-smoke.mjs``docs/baselines/goose-v149-cost-evidence-*.json`)。
- [x] message sanitizeprovider 层单测 v1.41≈v1.49`compare-goose-v149-message-sanitize.mjs` ✅);Portal 多模态/历史展示路径仍保留对照。
- [x] Memory 对账:逐用户逐 ID/hash/status/source/evidence 对照(`compare-goose-v149-memory-manifest.mjs` ✅)。
- [x] Memory 故障演练:legacy owner 超时 fail-open 不中断(`check-goosed-v149-memory-failopen.mjs`);向量→legacy 自动回退待 pgvector canary 时再验。
- [x] 每项记录“通过 / 保留 / 回滚”,不得一次性批量删除(台账:[docs/goose-v149-phase2-evidence.md](docs/goose-v149-phase2-evidence.md))。
**Phase 2 聚合入口:** `node scripts/run-goosed-v149-phase2.mjs`
**Phase 3 收口入口:** `node scripts/run-goosed-v149-phase3.mjs` · 清单:[docs/goose-v149-phase3-closeout.md](docs/goose-v149-phase3-closeout.md)
### Phase 3:退役与收口
- [ ] 只删除已取得 canary 证据的重复逻辑(当前:**无删除项**;见 Phase 3 清单)。
- [x] 保留薄封装入口、feature flag 与回滚说明(canary runbook + `switch-goose-v149-canary.sh`)。
- [x] 更新 runtime 文档、证据链接与 Phase 3 合并闸门(`docs/goose-v149-phase3-closeout.md``docs/baselines/README.md`)。
- [x] 本地连续验证 + Memory manifest 无 drift`run-goosed-v149-phase3.mjs`);**不授权** 103 操作。
- [ ] 合并前运行影响域 verify;完成测试后再讨论 commit、合并或发布(须用户明确批准)。
## 必须通过的回归守卫
涉及受保护路径时,至少执行仓库约定的:
```bash
npm run verify:mindspace-publish-guards
npm run verify:mindspace-publish-guards:full
npm run verify:mindspace-page-sync-guards
npm run verify:h5-session-patches
npm run verify:seo-geo
npm run verify:seo-discovery
npm run verify:page-data
```
DeepSeek 兼容链路还必须覆盖 `release-gate/deepseek-production-parity.test.mjs` 及对应生产影响域场景。任何守卫失败时,结论只能是“保留定制并修复适配”,不能以“upstream 已支持”为理由继续退役。
Memory 本地最低验证集:
```bash
npm run check:memory-v2-contracts
npm run check:memory-v2-config
npm run check:memory-v2-session
npm run check:memory-v2-stack
node --test conversation-memory.test.mjs conversation-repair.test.mjs \
memory-v2-personal-store.test.mjs memory-v2-lifecycle.test.mjs \
memory-v2-pgvector-backfill.test.mjs memory-v2-runtime.test.mjs
```
以上命令只验证本地代码/fixture;若要使用真实本地 MySQL/PG,必须先确认连接字符串是 loopback 隔离库,并把 count、ID/hash、关联和 watermark 对账结果作为附件保存。任何测试失败、数据 drift、owner 不明确或无法证明回滚时,Memory 迁移保持 blocked,不得进入生产。
## 一句话结论
跟随升级,但不做 upstream 全量替换:通用能力(搜索、浏览器、hooks、cost 追踪、DeepSeek 兼容)按证据逐步 Replace 或 WrapperMindSpace、Page Data、微信、计费、权限、PG 多实例与 H5/Portal 契约必须 Keep。