Repository navigation
Conversation
🤖 Open Code ReviewTarget: PR #796 🔍 OpenCodeReview found 44 issue(s) in this PR.
1.
|
|
|
|
||
| listActive(userId: string, limit = 2000): UserMemoryRecord[] { | ||
| private activeWhere(userId: string, options: UserMemoryRecallOptions = {}) { | ||
| const clauses = ["user_memories.user_id = ?", "user_memories.status = 'active'", "user_memories.deleted_at IS NULL"]; |
There was a problem hiding this comment.
召回开关开启时,历史 User Memory 连原账号也无法召回
问题
升级前已保存的 User Memory(用户偏好、事实等),升级后仍能在记忆面板看到,但正式搜索无法召回;即使用原来写入它的账号查询也一样。补充测试保留历史行的 user_id=account-a,并显式开启 enableUserMemoryRecall,通过 service.search() 查询同一内容:指定 base 返回该记忆,新 head 返回空结果。新增的历史跨账号测试也在此处失败。这个问题发生在 User Memory 召回开关开启时,开关关闭时 Agent 不收到 User Memory 是预期行为;修复此底层查询问题不代表要打开开关或撤销关闭设置,而是保证开关以后开启时能够正常召回。
原因
请求的账号已在 namespace 归一化时替换为固定设备 owner,而这里的 activeWhere() 仍要求 user_memories.user_id 等于传入 owner。历史行按本 PR 的约定保留旧 UID,因此全部被过滤。这个条件同时影响有效数量统计、普通列表、全文检索、关键词检索和向量检索;后续分页补取无法补回已经被查询条件排除的行。记录没有被删除,是读取规则没有完成 UID 解耦。
建议做法
让 User Memory 的所有召回入口统一使用设备共享的读取规则,移除账号 UID 过滤,同时保留有效状态、删除状态、来源范围、排除回合及向量模型匹配等条件。不要通过批量改写历史 UID 修复。补充正式搜索的旧库升级/重启测试,覆盖原账号、另一账号和 API Key 模式,以及全文、关键词、向量三条补取路线;修复后跑通当前失败的历史保留与补取测试。
| }); | ||
| // A read projection only: retain each historical record and its ordering. | ||
| // Never seed/merge the writable device scope with these historical fields. | ||
| const field = (key: keyof typeof records[number]["fields"]) => { |
There was a problem hiding this comment.
修改或清空旧 L3 规则后,Agent 仍会收到旧规则
问题
例如,v9 历史项目规则是 Run tests。升级后把它改成 Use pnpm test instead of npm test,Agent 实际收到的上下文却同时包含旧规则和新规则;再清空该字段,Agent 又会收到旧的 Run tests。补充测试直接读取实际 L3 上下文:更新后预期只有新规则,head 返回新旧两条;清空后预期为空,head 返回旧规则。直接更新、执行排队演化任务、更新后清空这三个场景在指定 base 全部通过。这里不是要求删除历史数据库行;问题是旧行保留后,读取层仍把旧值当成当前有效规则。
原因
读取层把历史账号记录和设备记录的同一字段直接拼接,并过滤掉 null,没有表达“新版已覆盖旧版”或“此字段已明确删除”。同时,演化任务读取当前字段时调用的 fields() 只看设备记录,读不到仍会注入 Agent 的历史内容。于是演化可能把旧的 Run tests 当作空字段并按“新建”处理;写入新值后上下文仍拼出新旧两条;清空设备字段时,因 null 没有作为删除标记保存,历史旧值又成为唯一非空值并重新出现。
建议做法
统一演化输入与 Agent 上下文所使用的有效字段规则:没有设备更新时读取经验证的历史字段;设备明确更新或删除后,以该结果覆盖历史字段,区分“尚未写入”和“明确清空”。可以单独记录覆盖/删除状态,保持历史行原值不变。继续保留旧 batch 原始哈希及项目检查;环境画像还需携带其对应的已应用项目环境扫描编号。回归覆盖修订、清空、重启、跨账号读取及旧排队任务,直接断言 Agent 收到的内容。
| if (projectId === null) return [null]; | ||
| const rows = db.prepare(` | ||
| SELECT DISTINCT peer.project_id AS projectId | ||
| FROM sessions origin JOIN sessions peer |
There was a problem hiding this comment.
历史记录缺少机器标识时,相同路径会被误认成同一项目
问题
PR 前生成的数据确实有缺少机器标识的可能。目标 base v1.2.6 的 hook 在 Memory 服务未报告支持 v2 时会走旧协议兼容分支,只传 workspacePath,不传 workspaceHostId(App/memmy-agent/src/memmy-memory/hook.ts,第 728、742 行);服务端旧版 session 写入使用清洗后的元数据(Memory/src/service/session/session-turn-service.ts,第 896 行),清洗函数会移除 workspace_host_id(同文件第 4608 行)。因此通过该路径创建的 session 不会保存机器标识。
本次复现没有运行真实导出/导入流程。测试构造的是两台机器曾各自生成不同历史项目编号、但 session 中 workspace_host_id 缺失且 workspace URI 相同的情形。测试 fixture 原先有 host 值,随后专门移除它来模拟缺失;这不能说明真实存量中缺失记录的比例,也不能证明标准导入会丢掉该字段。读取其中一个项目的 L3 时,head 会把另一个项目的规则也读进来,base 只返回本项目规则。机器标识仍存在且能区分两台机器时,该测试没有复现问题。
原因
这段 JOIN 只比较 workspace URI,并使用 SQL IS 比较 host;两个缺失值满足 NULL IS NULL,所以两条记录进入同一别名集合。结果是查询时把两个原本不同的项目范围当成别名一起读取,而不是把数据库中的项目记录物理合并。这里没有校验项目编号是否确实由该历史 owner、workspace 和 host 推导,也没有拒绝冲突证据。后面的 host/hash 校验只会追加别名,不会撤销前面已加入的错误项目。L3 读取和工作记忆范围查询都会使用这个集合。
建议做法
所有别名入口统一采用可验证的项目身份规则:本地路径必须有可靠 host 证据,并验证旧 UID 派生编号与设备编号的关系;缺失、矛盾或无法验证时保留原项目隔离,不因路径相同而扩大读取范围。补充同路径不同机器、双方缺 host、伪造或修改 workspace 元数据、冲突映射和无 session 的历史 scope 测试,同时验证正确的跨账号项目仍可共享。
|
三条 review comment 已逐项修复,修复提交为 1. 历史 User Memory 在召回开启时仍无法召回
2. 修改或清空旧 L3 后仍注入旧规则
代码:有效字段与独立状态、演化输入和修订号校验、Agent 上下文。 3. 历史记录缺少 host 时同路径项目串读
代码:统一项目身份验证。 测试结果与验收边界
全量回归的唯一失败是 新增回归集中在 uid-review-regressions.test.ts,并复跑了既有 UID、L3、召回补取、画像与向量维护测试。 上述验证使用独立 SQLite fixture、隔离的 Memory home 和本地 HTTP 服务;未进行 DMG 安装/真实桌面 GUI 验收、真实模型调用或真实历史库导入导出。缺 host 的复现证明该数据形态下存在串读风险,不据此推断真实导入会丢失 host 或存量发生比例。 |
|
| const before = this.memories.get(memoryId); | ||
| if (!before) throw new Error(`corrupt L3 World Model scope memory: ${memoryId}`); | ||
| validateL3WorldModelMemory(before, scope.userId, scope.projectId); | ||
| for (const field of L3_FIELD_NAMES) { |
There was a problem hiding this comment.
删除旧 L3 记录会同时屏蔽其他未删除记录的有效字段
问题
同一项目保留历史 L3 记录,并且升级后已经生成新的设备级 L3 时,删除旧记录会连带清空其他记录的注入内容。例如旧记录只有项目约定,新设备记录保存了独立的领域知识;通过正式 deleteMemory() 删除旧记录后,新记录仍在数据库中,正文和状态都没变,但 Agent 上下文里的领域知识变成了空。相同场景在指定 base 通过、当前 head 失败。
原因
这里不检查被删记录实际包含哪些字段,也不检查哪个记录提供当前有效值,而是把该项目的画像、约定、领域知识全部写成 cleared: true。这些标记按共享项目编号保存,effectiveFields() (line 6081) 看到标记就不再读取任何候选,因此一次按记录 ID 的删除实际变成了整个共享项目的字段清空。
建议做法
让按 ID 删除只影响选中的记录。写清空标记前,逐字段确认当前有效内容是否由该记录提供;来自其他未删除记录的有效值和已有覆盖标记应保留。显式清空字段仍需防止旧内容重新生效,但不能借此覆盖无关字段。补充“旧记录仅有约定、新记录另有知识或画像,删除旧记录后其他字段继续注入”的正式服务测试,并验证重启后的结果一致。
| ) {} | ||
|
|
||
| getScope(userId: string, projectId?: string | null): L3WorldModelScopeRecord | undefined { | ||
| userId = DEVICE_MEMORY_OWNER; |
There was a problem hiding this comment.
升级前排队的项目环境扫描无法正确完成
关联位置:project-environment-service.ts (line 70)、repositories.ts (line 6834)。
问题
升级前已有排队的项目环境扫描任务时,旧任务和旧 scope 仍保存账号 UID。升级后继续执行该任务,会报 project_environment_workspace_uri_missing,尽管旧 scope 中确实保存了 workspace URI。补充测试从 v9 数据库恢复任务,通过正式 processProfileJob() 执行:base 成功,新 head 失败。这里是生成项目环境画像的目录扫描,不是 agent-source 对话扫描。
原因
processProfileJob() 用旧任务的 UID 读取 scope,但 getScope() 把传入 UID 强制替换为设备 owner,因此找不到历史 scope。只补这个读取也不够:下游 applyProfile() 将新画像写成设备 owner,却仍按旧 UID 更新已应用扫描记录;读取画像时又按画像的设备 owner 校验扫描编号。单独执行画像生成阶段的补充测试已确认:状态变成 clean、正文保存成功,但上下文仍返回空。
建议做法
为旧任务保留可验证的历史 scope 读取,并统一任务执行后的设备级画像与扫描完成记录的关联。旧任务的 owner、项目和扫描编号仍需校验,历史行保持原值;不要只放宽读取校验或批量改写历史 UID。覆盖“升级前排队→升级后完成→Agent 读到画像→重启后仍可读”的完整流程,同时验证过期任务不能覆盖较新的扫描结果。
|
已补充修复“旧会话恢复后,新建的设备扫描任务被误拒绝”,本次更新包含以下两条修复提交:
本次新增修复历史 session 保留原账号 UID,新扫描任务使用 这次仅移除该相等要求,保留任务 owner/payload/持久化任务一致性、session 项目、精确 owner 的 scope_key、workspace URI、扫描编号、字段修订号和 worker lease 校验。没有改写历史 session、scope 或 Memory,也没有新增 UID 分支、扫描版本或修改队列排序。 测试及本地 auto-cr
范围与边界本次新增修复只处理 session UID 相等限制。另两项本地 CR(删除较新画像后旧任务重新生效、导入旧任务后 enqueue_seq 顺序判断)未在本轮调整。验证使用隔离 SQLite、临时目录真实扫描及模型 mock;未进行真实模型、生产旧库、DMG/GUI 或远端 CI 验收。 请按以上范围复审。 |
问题定位
记忆 owner 和召回候选池此前仍受账号 UID 影响。历史记忆可能因不同账号重复写入,重复项会占用召回额度,导致去重后的有效记忆数量不足。调整 owner 时还必须保留历史行原值,以及既有 source、project、tenant 等范围约束。
修改内容
具体改动与代码位置
v1.2.6 适配
本次只回移植 PR #795 的 4 个代码提交,没有合入其他分支提交。冲突解决保留了 v1.2.6 的数据库范围过滤,同时接入召回补取条件;会话服务则同时保留异步让出逻辑和设备 owner import。
测试结果
git diff --check upstream/v1.2.6...HEAD通过;未运行自动化测试。npm run lint、npm run typecheck通过;历史行/重启保留、权限范围及请求传输/UID 解耦定向测试曾通过。better-sqlite3原生模块时触发 Node assertion,未能完成;不将其列为通过。