项目事实、任务状态、代码偏好这三类信息如果共用同一段上下文,Agent 很容易把「今天先把 A 接口返回改成 202」当成项目长期约定,下次会话继续按 202 处理。MemoraX Code 把三类记忆分开管,核心不是换更大的上下文窗口,而是给每类记忆定不同的写入时机、命名空间和生命周期。可以先用最近会话记录做一次归类,再落地隔离字段。
判断:先分清三类记忆的写入主体和有效期。项目事实由人确认后写入长期分区,任务状态只挂在当前任务 ID 下,代码偏好必须等明确采纳再写。验证方式:跨会话重新提问,看 Agent 是否仍把临时改动当约定;如果混写,按「定位来源—降级为任务级—补写事实或偏好」三步纠正。风险边界:不同工具字段名和分区能力不同,需要以实际配置和日志为准。
把现有会话内容按三类各归一堆
要判断混写发生在哪里,先别改配置,直接从最近的会话记录里抽三条项目事实、三条任务状态、三条代码偏好,并标注原文。下面是一张抽取表,把原文贴进去,标出属于哪一类、当前写入了哪个存储。
| 会话原文 | 归类 | 当前写入位置 |
|---|---|---|
| 「这个仓库的 API 前缀统一用 /v2,不再用 /v1」 | 项目事实 | 项目事实区或长期记忆键 |
| 「先把登录接口的返回改成 202,明天再回滚」 | 任务状态 | 任务状态区或长期记忆键 |
| 「日志里不要打完整手机号,脱敏后再输出」 | 代码偏好 | 代码偏好区或长期记忆键 |
例如从一次模拟会话里可以抽到:
- 项目事实:原文「这个仓库的 API 前缀统一用 /v2,不再用 /v1」——属于长期约定,应写入项目事实区。
- 任务状态:原文「先把登录接口的返回改成 202,明天再回滚」——属于当前任务,应只留在任务状态区,不能进长期库。
- 代码偏好:原文「日志里不要打完整手机号,脱敏后再输出」——如果团队明确采纳,应写入代码偏好区;如果只是某次 review 的临时意见,则留在任务状态区。
混写通常发生在三个位置:任务开始时的自然语言里夹带项目事实;Agent 把「先这样改」当成规则写进长期记忆;代码偏好没有确认人,只凭一次修改就被固化。把三类原文各抽三条后,先看它们是否落在同一个存储键或同一个标签下。如果是,就说明隔离没生效。
给每类记忆定写入时机
写入时机比存储位置更容易出错。建议按下面的界限执行,写入前先问一句「这句话过了当前任务还有效吗」。
| 记忆类型 | 写入时机 | 有效期 | 写入动作 |
|---|---|---|---|
| 项目事实 | 在有人确认或已合并的变更之后写 | 长期,直到被新事实覆盖 | 写入项目事实分区,附来源链接或 commit/工单号 |
| 任务状态 | 任务创建或状态变更时写 | 只留在当前任务,任务关闭后归档或清理 | 挂在 task_id 下,不写长期分区 |
| 代码偏好 | 被明确采纳后写,例如 review 通过、规范文档更新 | 长期,但需要可撤销 | 写入代码偏好分区,标 owner 和生效范围 |
项目事实不要在对话中途随手写。有人问「这个项目是不是用 PostgreSQL」,Agent 回答「是」并不等于事实被确认;应该等到配置、迁移文件或负责人确认后再写入项目事实分区。任务状态只留在当前任务:像「临时把 feature flag 打开」「先跳过这个测试」这类信息,任务结束后不应该影响下一次会话。代码偏好在被明确采纳后写:明确采纳可以是一次 review 通过、一条团队规范更新,或者负责人书面确认;如果只是 Agent 自己推断的偏好,先放在任务状态里,不要直接写长期库。
MemoraX Code 里可以用配置或提示词把这三类写入动作分开。例如一个通用的写入骨架:
# 伪配置,字段名以实际配置为准
memory:
project_fact:
write_when: confirmed
namespace: proj-{repo}-facts
ttl: none
task_state:
write_when: task_update
namespace: task-{task_id}
ttl: on_task_close
code_preference:
write_when: explicitly_accepted
namespace: proj-{repo}-prefs
ttl: review_quarterly
这个骨架只表达写入条件、命名空间和过期策略,不绑定具体实现。替换项是 repo、task_id 和 ttl 的取值,放在项目的记忆配置或 Agent 初始化提示词里执行。验证方式是改完配置后发起一次新任务,看任务状态是否还被写进 proj-{repo}-facts。
给三类记忆加隔离标识
隔离可以从命名、标签、分区三个层面做,不必一次上齐。先用命名前缀把三类记忆的键分开,比如 proj.fact.*、task.state.*、code.pref.*;如果工具支持标签,再给每条记忆打 type=project_fact、type=task_state、type=code_preference。分区是更硬的一层:三类记忆写入不同的存储表、不同的向量集合,或不同的文件目录。字段名和分区名以实际配置为准,别照搬示例。
一个可用的最小隔离做法是:在每次写入前强制带 type 字段,并在检索时按 type 过滤。下面是一个通用的输入校验样例,字段名可以替换成实际配置:
type: project_fact
scope: repo:demo
content: API 前缀统一用 /v2
source: commit:abc123
confirmed_by: owner
校验规则可以简单写成:type 不在 [project_fact, task_state, code_preference] 内则拒写;task_state 必须带 task_id;project_fact 和 code_preference 必须带 source 或 confirmed_by。这样即使 Agent 想写,也会在写入层被拦住。隔离之后,检索时也要按类型走不同路径:问「这个项目的约定」只搜 project_fact,问「当前任务进度」只搜 task_state,问「代码风格」只搜 code_preference。
复现一次临时状态被误当长期约定的情况
要验证隔离是否生效,可以故意做一次误判复现,但不要编造失败率。步骤是:在一个新任务里让 Agent 处理「先把超时改成 30 秒,明天回滚」,并明确说这是临时调整;结束任务后,开一个新会话问「这个项目的超时默认是多少」。如果 Agent 回答 30 秒,说明临时状态被写进了长期记忆,隔离没拦住。
记录误判现象时,至少写清楚:误判发生在哪个问题、Agent 的回答原文、这条状态原本属于哪个 task_id、它被写到了哪个 namespace。纠正动作分三步:先把这条记忆从长期分区移除或标记为无效;再把「超时默认值」这类真正的事实回填到 project_fact,并附上确认来源;最后在写入层补一条校验,任务状态必须带 task_id,且不允许写 proj-{repo}-facts。做完三步后,再开一个新会话复测同一个问题,看回答是否回到项目事实的值。这个过程只验证隔离动作,不承诺一定没有其他混写路径。
整理一份日常维护检查表
三类记忆分开管之后,维护成本主要落在跨会话验证、过期清理和冲突标记上。下面这张检查表可以按周或按迭代执行,检查项不依赖具体工具,能通过日志、检索结果或页面行为看到。
| 检查项 | 做什么 | 看到什么算正常 |
|---|---|---|
| 跨会话验证 | 用新会话重复问一个项目事实和一个任务状态问题 | 项目事实回答稳定;任务状态不回答或提示「未在当前任务中」,而不是把旧任务状态当约定 |
| 过期清理 | 列出 task_state 分区,按任务关闭时间清理;列出 code_preference,按复查周期处理 | 已关闭任务的临时状态不再出现在检索结果里 |
| 冲突标记 | 同一 scope 下 project_fact 出现不同值时,不自动覆盖,先标记冲突 | 冲突记忆带 conflict=true,由人确认后再合并 |
| 写入来源检查 | 抽查 project_fact 和 code_preference 是否带 source 或 confirmed_by | 没有来源的长期记忆被降级或拒写 |
跨会话验证不要只看 Agent 的回答语气,要看检索命中哪条记忆。过期清理可以先从 task_state 开始,因为任务状态的生命周期最短。冲突标记比自动合并更安全:项目事实出现两个版本时,先让人确认哪一条生效。最后,检查表只覆盖可观察行为;如果工具不提供分区或标签能力,就退回到命名前缀和写入校验,至少保证任务状态不写进长期库。