Files
smartmate/backend/services/memory/docs/legacy/第三步治理与观测落地计划.md
Losita 2a96f4c6f9 Version: 0.9.76.dev.260505
后端:
1.阶段 6 agent / memory 服务化收口
- 新增 cmd/agent 独立进程入口,承载 agent zrpc server、agent outbox relay / consumer 和运行时依赖初始化
- 补齐 services/agent/rpc 的 Chat stream 与 conversation meta/list/timeline、schedule-preview、context-stats、schedule-state unary RPC
- 新增 gateway/client/agent 与 shared/contracts/agent,将 /api/v1/agent chat 和非 chat 门面切到 agent zrpc
- 收缩 gateway 本地 AgentService 装配,双 RPC 开关开启时不再初始化本地 agent 编排、LLM、RAG 和 memory reader fallback
- 将 backend/memory 物理迁入 services/memory,私有实现收入 internal,保留 module/model/observe 作为 memory 服务门面
- 调整 memory outbox、memory reader 和 agent 记忆渲染链路的 import 与服务边界,cmd/memory 独占 memory worker / consumer
- 关闭 gateway 侧 agent outbox worker 所有权,agent relay / consumer 由 cmd/agent 独占,gateway 仅保留 HTTP/SSE 门面与迁移期开关回退
- 更新阶段 6 文档,记录 agent / memory 当前切流点、smoke 结果,以及 backend/client 与 gateway/shared 的目录收口口径
2026-05-05 19:31:39 +08:00

13 KiB
Raw Blame History

Memory 第三步治理与观测落地计划

1. 这份文档解决什么问题

这份文档只回答第三步要做什么,不再重复前两步已经完成的抽取、决策、召回细节。

第三步的目标很简单:

  1. 把 memory 从“能跑”升级成“敢灰度、敢排障、敢清理、敢回滚”。
  2. 把“日志打在哪里、我怎么看、会不会给接口”说清楚。
  3. 把改动范围收敛在治理层,不再继续扩算法和能力边界。

一句人话总结:

前两步解决的是“有没有能力”,第三步解决的是“出了问题怎么查、怎么收、怎么退”。


2. 先说结论

第三步我会分成两块做:

  1. 观测与切流
  2. 用户管理与清理

为什么这么拆:

  1. 现在第二步最小闭环已经通了,最怕的不是“能力不够多”,而是“出了问题不知道卡在哪一层”。
  2. 如果没有统一日志、指标和开关,后面再继续加功能,只会让 memory 变成一个越来越难维护的黑箱。
  3. 历史重复脏数据不先治理,后面读链路和注入链路的数据噪音会越来越重。

第三步不追求“更聪明”,追求“更稳、更可控”。


3. 你最关心的三个问题

3.1 日志会打在哪里

第三步不会把所有信息都塞进一个地方,而是分三层:

A. 运行日志

运行日志打到后端服务本身的标准日志,也就是当前 backend 进程控制台 / 容器 stdout。

这层主要看实时链路,适合排查:

  1. 这次写入为什么是 ADD / UPDATE / DELETE / NONE
  2. 这次召回为什么没命中
  3. 这次注入为什么降级到 flatlegacy
  4. 这次 worker 为什么走了 fallback

这层的形态参考当前 RAG 轻量 Observer 的做法,不单独造一套散装日志方案。

参考文件:

  1. backend/cmd/start.go
  2. backend/infra/rag/core/observer.go

B. 变更留痕

变更留痕继续落库,不只打终端。

当前已经有:

  1. memory_audit_logs
  2. backend/model/memory.go
  3. backend/memory/repo/audit_repo.go

这层主要看“已经发生过的变更事实”,适合研发排查和后端自查:

  1. 哪条记忆被删了
  2. 删之前和删之后内容是什么
  3. 这次 dedup 清理保留了哪条,归档了哪条
  4. 某次 update / delete / restore 是谁触发的,原因是什么

C. 汇总指标

第一版不先上完整 Prometheus / Grafana 平台,而是先把关键指标打稳,再视需要接统一观测平台。

这层主要看趋势和健康度,适合回答:

  1. 最近写入成功率怎么样
  2. hybrid 召回到底有没有提升
  3. 去重到底丢了多少垃圾数据
  4. 是否频繁回滚到 legacy

3.2 我会怎么看

开发和联调阶段,推荐分两种看法:

看实时问题

直接看后端运行日志。

适合看:

  1. 单次请求链路
  2. 单次 worker 执行过程
  3. fallback / 降级 / 回滚是否发生

看历史问题

直接查数据库留痕表和主表。

适合看:

  1. 某条 memory 历史上被怎么改过
  2. 某次清理动作具体处理了哪些记录
  3. 当前 active / archived / deleted 分布

建议排查时优先查这几张表:

  1. memory_jobs
  2. memory_items
  3. memory_audit_logs

第一版就够用了,不强依赖前端页面才能排查。


3.3 会不会提供接口

会,但原则上只补“面向当前用户管理自己记忆”的接口,不补“原始运行日志接口”,也不把 memory 先做成全项目唯一完整的审计后台。

原因很直接:

  1. 原始日志噪音很大,不适合直接给前端看。
  2. 原始日志字段会迭代,直接对外暴露会把内部实现绑死。
  3. 原始日志可能带内部 trace、错误细节不适合直接外露。

所以第三步对外提供的是“用户管理自己记忆”的接口,不是“把 stdout 原样吐给前端”,也不是“先给 memory 单独造一套管理后台接口”。

第三步建议优先补这几类用户接口:

第一组:当前用户查看自己的记忆

  1. GET /api/v1/memory/items
    • 分页查看“我自己的记忆”
  2. GET /api/v1/memory/items/:id
    • 查看“我自己的某条记忆”详情

第二组:当前用户主动维护自己的记忆

  1. POST /api/v1/memory/items
    • 手动新增一条记忆
  2. PATCH /api/v1/memory/items/:id
    • 修改自己的一条记忆
  3. DELETE /api/v1/memory/items/:id
    • 删除自己的一条记忆

第三组:当前用户恢复误删内容

  1. POST /api/v1/memory/items/:id/restore
    • 若底层采用软删或归档,可补恢复动作

这些接口都默认只允许操作“当前登录用户自己的记忆”,不支持跨用户查询和跨用户修改。

原则:

  1. 原始日志看后端 stdout
  2. 内部变更留痕优先给后端查表和排障使用,不急着做成前端正式能力
  3. 对外先开放用户真正会用到的“我的记忆”增删改查

4. 第三步到底要做什么

4.1 观测与切流

这是第三步的第一优先级。

要做的事

  1. 给写入决策链路补统一结构化日志
  2. 给读侧召回链路补统一结构化日志
  3. 给注入渲染链路补统一结构化日志
  4. 给上述三条链路补关键计数指标
  5. 把现有配置字段整理成清晰的切流顺序和回滚手册

为什么先做这个

因为第三步如果先做 dedup 清理,但没有日志和切流能力,一旦清错了,排查成本会很高。


4.2 用户管理与清理

这是第三步的第二优先级。

要做的事

  1. 给“我的记忆”补完整增删改查语义
  2. 给历史重复数据补离线 dedup 工具
  3. 给关键变更动作补最小留痕
  4. 把 dedup 保持在后端内部治理流程,不急着做成前端接口

为什么不一上来绑主 worker

因为第一版 dedup 的目标是“可留痕、可回滚”,不是“全自动”,也不是先给 memory 单独造一个很重的治理后台。

离线或手动触发更安全,出问题也更容易止血。


5. 具体改动计划

5.1 第一轮:先把观测底座补起来

目标

先让系统“可看见”。

预计改动

新增:

  1. backend/memory/observe/log_fields.go

修改:

  1. backend/memory/worker/decision_flow.go
  2. backend/memory/worker/apply_actions.go
  3. backend/memory/service/read_service.go
  4. backend/memory/service/retrieve_merge.go
  5. backend/service/agentsvc/agent_memory.go
  6. backend/service/agentsvc/agent_memory_render.go

这一轮会补什么日志

写入决策日志

至少记录这些字段:

  1. trace_id
  2. user_id
  3. conversation_id
  4. job_id
  5. fact_type
  6. candidate_count
  7. final_action
  8. fallback_mode
  9. success

读侧召回日志

至少记录这些字段:

  1. trace_id
  2. user_id
  3. read_mode
  4. query_len
  5. legacy_hit_count
  6. semantic_hit_count
  7. dedup_drop_count
  8. final_count
  9. degraded

注入渲染日志

至少记录这些字段:

  1. trace_id
  2. user_id
  3. inject_mode
  4. input_count
  5. rendered_count
  6. token_budget
  7. fallback

5.2 第二轮:补指标,不急着开 overview 接口

目标

先让系统“可量化”。

第一版建议补的指标

优先补这 8 个:

  1. memory_job_success_rate
  2. memory_job_retry_rate
  3. memory_decision_distribution
  4. memory_decision_fallback_rate
  5. memory_retrieve_hit_count
  6. memory_retrieve_dedup_drop_count
  7. memory_inject_item_count
  8. memory_rag_fallback_rate

暂不强求第一版就补:

  1. memory_wrong_mention_rate
  2. memory_user_correction_rate

因为这两个更依赖后续“用户纠错入口”。

这一轮先不做什么

这一轮先不单独新增 GET /api/v1/memory/overview

原因不是这个接口没价值,而是现在别的模块还没有统一的观测面板和汇总接口规范。memory 这一轮先把指标打稳,后续如果全项目一起做观测面板,再统一收口更对称。

也就是说,这一轮优先把“数据先有”做出来,不急着把“看板接口先长出来”。


5.3 第三轮:补用户管理动作

目标

先让用户“能管理自己的记忆”。

预计改动

修改:

  1. backend/memory/service/manage_service.go
  2. backend/memory/repo/item_repo.go
  3. backend/memory/utils/audit.go
  4. backend/memory/module.go

新增:

  1. backend/api/memory.go
  2. 路由注册文件中的 memory 接线

要补的动作

  1. list
  2. detail
  3. create
  4. update
  5. delete
  6. 若底层保留软删语义,再补 restore

接口建议

新增:

  1. GET /api/v1/memory/items
  2. GET /api/v1/memory/items/:id
  3. POST /api/v1/memory/items
  4. PATCH /api/v1/memory/items/:id
  5. DELETE /api/v1/memory/items/:id
  6. POST /api/v1/memory/items/:id/restore
    • 仅在底层采用软删或归档方案时开放

设计要求

  1. 所有接口默认只作用于“当前登录用户自己的记忆”
  2. 后端仍保留最小变更留痕,但不把它包装成用户侧“审计接口”
  3. 接口返回给前端的是“人能看懂的记忆内容和操作结果”,不是底层日志

5.4 第四轮:做离线 dedup 治理

目标

先让系统“可清理”。

预计新增

  1. backend/memory/cleanup/dedup_runner.go
  2. backend/memory/cleanup/dedup_policy.go

第一版治理规则

按以下维度扫描重复组:

  1. user_id
  2. memory_type
  3. content_hash
  4. status = active

每组处理规则:

  1. 选一条主记录保留
  2. 优先保留最近更新的
  3. 若最近更新时间接近,则优先保留置信度更高的
  4. 其余记录改为 archived
  5. 每次治理动作都写最小变更留痕

接口建议

这一轮不对外新增 dedup 接口。

dedup 先保留为后端内部治理能力,必要时通过离线任务、后台命令或内部 job 触发,避免 memory 先演化成一个比其他模块更重的专用治理后台。

明确限制

第一版不做:

  1. 直接危险 SQL 清表
  2. 自动定时常驻清理
  3. 无留痕的批量删除

6. 日志、留痕、接口分别给谁看

这个地方一定要分清,不然第三步会越做越乱。

运行日志

给研发和排障看。

特点:

  1. 实时
  2. 噪音大
  3. 字段多
  4. 不直接给前端

变更留痕

先给研发和后端排障使用。

特点:

  1. 是持久化结果
  2. 适合看历史
  3. 这一轮不急着做成正式用户接口

用户接口

给用户和前端页面看。

特点:

  1. 只暴露“我的记忆”内容和操作结果
  2. 不暴露内部 raw log
  3. 不承载平台级观测职责

7. 切流顺序

第三步不允许一刀切。

建议严格按下面顺序灰度:

  1. 阶段 A决策层 shadow
    • 真正写库仍然走 legacy
    • 新决策层只记日志,不生效
  2. 阶段 B决策层仅对显式记忆生效
  3. 阶段 C决策层对全部写入生效
  4. 阶段 D读侧切到 hybrid
  5. 阶段 E注入切到 typed_v2
  6. 阶段 F历史清理跑完再考虑关闭 legacy 默认路径

这里的配置基础已经存在,关键是把切流顺序写清、用清、能回退。

参考文件:

  1. backend/memory/model/config.go
  2. backend/memory/service/config_loader.go

8. 回滚方案

第三步的回滚不应影响前两步代码保留,只回切开关。

最小回滚动作

  1. 写侧回到 legacy
  2. 读侧回到 legacy
  3. 注入回到 flat
  4. 停掉 dedup 清理任务

回滚原则

  1. 先停治理动作,再回切主路径
  2. 不做破坏性 schema 回滚
  3. 不依赖人工热修逻辑判断

9. 第三步明确不做什么

为了防止范围失控,这一轮明确不做:

  1. 不做图记忆
  2. 不做多 Provider 工厂化
  3. 不拆独立 memory 服务
  4. 不在这一轮给 memory 先单独做完整审计后台
  5. 不把 WebSearch 和 Memory 强行合并成一轮上线
  6. 不再扩新的召回算法分支

10. 完成标准

满足以下条件,算第三步完成:

  1. 能从日志看清某条记忆为什么被判成 ADD / UPDATE / DELETE / NONE
  2. 能从指标看清召回命中、去重、降级、回滚情况
  3. 用户能通过接口管理自己的记忆
  4. 能对历史重复数据做可留痕清理
  5. 出异常时能通过开关在分钟级切回 legacy
  6. 文档和代码现状一致,不再靠口头传递

11. 如果只看一页,请看这个执行顺序

第三步不要散着做,建议按这个顺序推进:

  1. 先补统一日志字段和结构化日志
  2. 再补指标,把观测数据打稳
  3. 再补“我的记忆”增删改查能力
  4. 最后做离线 dedup 和内部清理能力

一句人话总结:

先让系统“看得见”,再让系统“能管理”,最后再让系统“敢清理”。