9393650447
Co-authored-by: Cursor <cursoragent@cursor.com>
180 lines
17 KiB
Markdown
180 lines
17 KiB
Markdown
# TKMind Goose v1.49 迁移决策表
|
||
|
||
## 目标与边界
|
||
|
||
本次升级的目标是“升级运行时基线 + 选择性吸收 upstream”,不是把 TKMind 变成 vanilla Goose。
|
||
|
||
- Layer 0:PG Session、多实例 affinity、9 实例拓扑——保留。
|
||
- Layer 1:goosed 运行时、安全修复、agent loop——跟随 upstream v1.49。
|
||
- Layer 2:通用能力的 platform extensions——优先改为薄封装,逐项退役重复实现。
|
||
- Layer 3:`tkmind_compat`、harness、sandbox-fs MCP——保留,确保 Portal/H5 契约与业务语义不变。
|
||
- Layer 4:Portal、微信、计费、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 或本地数据副本,测试结束删除临时用户/会话/索引,但保留可复核的对账报告。
|
||
|
||
## 决策规则
|
||
|
||
| 判断问题 | Replace:upstream 替代 | 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 MCP(MindSpace、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 3–4 对外接口不变(Portal canary 联调 ✅;`POST /agent/resume`、`GET /sessions/{id}` 带 conversation、restart 返回 `extension_results` 已补齐 smoke)。
|
||
|
||
### Phase 2:逐项 canary
|
||
|
||
- [x] web/search:TKMind `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 sanitize:provider 层单测 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 或 Wrapper;MindSpace、Page Data、微信、计费、权限、PG 多实例与 H5/Portal 契约必须 Keep。
|