Skip to content

Latest commit

 

History

History
406 lines (292 loc) · 15.1 KB

File metadata and controls

406 lines (292 loc) · 15.1 KB

AutoPatch-J Agent Memory 设计说明

面向 code_explain / general_chat 的项目级普通问答记忆。它不是全局 Agent 记忆,也不是补丁修复记忆。

1. 为什么需要 Memory

AutoPatch-J 的主要工作不是聊天,而是围绕 Java 仓库完成代码解释、代码审计、补丁生成和人工确认。

普通问答仍然需要连续性:用户可能先问项目结构,再追问某个模块, 再切到 Java 语法或工程实践。如果每一轮都完全失忆,体验会很割裂。

但把完整历史直接塞进 Prompt 也会带来问题:

  • 上下文变长,成本和延迟上升。
  • 无关历史会干扰当前问题。
  • 历史解释、旧状态或算法题讨论可能污染补丁修复判断。

因此 Memory 的目标不是保存所有对话,而是在清晰边界内沉淀少量有用上下文,让普通问答更连续,同时保证代码修复链路仍然只相信当前证据。

2. IntentType 边界

AutoPatch-J 的自然语言输入最终会进入五类 IntentType。Memory 的第一条设计原则是按意图划清边界。

IntentType 场景 读 Memory 写 Memory 原因
code_audit 检查代码并生成补丁 必须以当前 scope、finding 和源码证据为准
code_explain 解释项目、目录、文件或代码 需要继承用户对项目的关注点
general_chat Java、算法、调试、架构和工程常识问答 需要继承用户偏好和近期话题
patch_explain 解释当前待确认补丁 只应围绕当前补丁,不被普通聊天污染
patch_revise 重写当前待确认补丁 修订范围必须锁定当前补丁

这个边界比“让 Agent 更懂用户”更重要。审计和修复必须围绕当前代码证据、扫描 finding、补丁队列和用户反馈推进;普通问答 Memory 可以帮助回答更连贯,但不能参与决定补丁是否正确。

3. 设计原则

raw 是材料,不是记忆。
summary 是上下文,不是事实。
长期记忆是沉淀资产,必须经过治理。
LLM 负责理解,程序负责约束。
普通问答有记忆,补丁修复保持隔离。

边界优先

Memory 只服务 code_explaingeneral_chat。补丁相关流程不读取、不写入普通问答 Memory。

这会牺牲一点全局个性化,但换来更关键的工程收益:审计和修复链路不会被历史聊天、旧偏好、算法题讨论或无关项目解释污染。

摘要优先

可注入 Prompt 的内容应该是仓库元信息、摘要、近期话题、用户偏好和项目讨论笔记,而不是完整历史。

完整历史太长、太杂,而且混有示例、推测、临时问题和旧状态。assistant_text 可以作为后续摘要材料保存,但不会直接注入下一轮 Prompt。

程序治理

短 LLM 只负责把近期问答压缩成 Memory Delta。它不能输出完整 memory.json,也不能随意决定最终写入内容。

程序负责 JSON 解析、字段校验、id 校验、来源白名单、长度裁剪、容量裁剪和原子写文件。LLM 的语义能力可以被使用,但最终状态必须由程序约束。

失败降级

Memory 失败不能影响主流程。

摘要失败、Memory Delta 非法、写入失败都应安静降级。 坏 JSON 会备份为 memory.corrupt*.json 并回退为空文档。 /reset 会清理 Memory,并丢弃尚未写回的后台摘要结果。

4. 分层模型

Memory 在概念上分为仓库元信息、工作记忆和长期记忆;落到 JSON 文件中,对应 repo_profileworking_memorylong_term_memory

repo_profile 是程序侧从仓库文件中提取的窄元信息:

  • 构建工具、Java 版本、项目名、模块名。
  • 明确依赖特征,例如 Spring Boot、MyBatis。
  • 信息来源文件,例如 pom.xmlbuild.gradlesettings.gradle

working_memory 解决近期上下文连续:

  • recent_turns:近期问答材料。
  • active_topics:由多轮问答压缩出的近期话题。

长期记忆保存更稳定的资产:

  • durable_preferences:用户明确表达的长期偏好或协作规则。
  • project_notes:围绕当前仓库持续讨论后沉淀的项目讨论笔记。

这些层次避免把构建元信息、一次性问题、近期话题、稳定偏好和项目讨论笔记混在同一个历史列表里。

recent_turns:近期材料层

recent_turns 保存最近的普通问答材料。它不是长期记忆,而是摘要器的输入和短期兜底上下文。

典型结构:

{
  "id": "turn_20260501_123000_001",
  "intent": "general_chat",
  "user_text": "Optional 怎么用?",
  "assistant_text": "Optional 是 Java 8 引入的容器类型...",
  "summary": "用户关注 Java Optional 的空值建模、常用 API,以及避免直接 get 的安全用法。",
  "summary_status": "ready",
  "scope_paths": [],
  "created_at": "2026-05-01T12:30:00+08:00"
}

约束:

  • intent 只允许 code_explaingeneral_chat
  • user_text 最多 1000 字符。
  • assistant_text 最多 2000 字符,只供摘要,不直接注入 Prompt。
  • summary 最多 300 字符。
  • scope_paths 最多 10 个路径。
  • 总数最多 12 条。

active_topics:短期工作记忆

active_topics 是多个 recent turn summary 合并后的近期话题。它解决几轮内的话题连续,不承诺永久保存。

典型结构:

{
  "id": "topic_20260501_001",
  "label": "Java Optional",
  "summary": "用户近期关注 Optional 的正确用法,以及项目中是否存在 optional-get-without-check 类问题。",
  "related_turn_ids": ["turn_20260501_123000_001"],
  "last_touched_at": "2026-05-01T12:30:00+08:00"
}

active_topics 最多 8 条。旧话题会被新话题淘汰,避免工作记忆变成主题仓库。

durable_preferences:稳定用户偏好

durable_preferences 保存用户明确表达的稳定规则和协作偏好。

典型结构:

{
  "id": "mem_20260501_001",
  "type": "durable_preference",
  "label": "commit message format",
  "summary": "提交信息必须使用 <type>: <lowercase english phrase> 格式。",
  "status": "active",
  "source": "user_explicit",
  "created_at": "2026-05-01T12:30:00+08:00",
  "updated_at": "2026-05-01T12:30:00+08:00"
}

可以进入长期偏好的内容:

  • 用户明确说“以后、每次、必须、不要、我希望、我不喜欢、优先、规则、守则、记住”等。
  • 稳定交互偏好。
  • 稳定输出格式偏好。
  • 稳定协作规则。

不能进入长期偏好的内容:

  • 单次算法题。
  • 临时问题。
  • LLM 推测出的用户意图。
  • 补丁细节。
  • 源码片段。
  • 日志内容。

repo_profile:仓库元信息

repo_profile 由程序侧保守提取,不由 LLM 自由生成。它只记录构建文件或 README 标题能直接支撑的窄信息,不推断项目业务用途。

典型结构:

{
  "build_tool": "maven",
  "java_version": "17",
  "project_name": "demo-service",
  "modules": ["api", "service"],
  "frameworks": ["spring boot"],
  "source_files": ["pom.xml"],
  "updated_at": "2026-05-01T12:31:00+08:00"
}

pom.xmlbuild.gradlesettings.gradle 只能支撑构建工具、Java 版本、模块名和明确依赖特征。README 缺失时,系统不会从构建文件里总结“这个项目是做什么的”。

project_notes:项目讨论笔记

project_notes 保存用户和 Agent 围绕当前仓库持续讨论后沉淀的上下文。它帮助普通问答更连续,但不是修复证据,也不能替代源码读取。

典型结构:

{
  "id": "mem_20260501_002",
  "type": "project_note",
  "label": "review module",
  "summary": "用户关注 review 模块如何管理 finding 队列和补丁确认。",
  "status": "active",
  "source": "conversation_summary",
  "created_at": "2026-05-01T12:31:00+08:00",
  "updated_at": "2026-05-01T12:31:00+08:00"
}

短 LLM 只能通过 conversation_summary 创建或更新 project_note。它不能把 repo_profile 里的构建信息改写成业务事实,也不能把项目讨论笔记注入补丁修复链路。

5. 阈值设计

V1 的阈值不是理论最优值,而是一组保守、可解释、可测试的工程默认值。它们服务三个目标:

  • 控制短 LLM 调用成本。
  • 减少无关历史对 Prompt 的污染。
  • 避免过早把临时问题沉淀为长期记忆。

摘要触发

pending_turns >= 2 触发摘要。

每轮都摘要会增加短 LLM 成本;完全不摘要又会让下一轮缺少稳定上下文。2 条 pending turn 把短期失忆窗口控制在 1 到 2 轮内,是成本和连续性的折中。

recent_turns >= 6 触发摘要。

一两轮对话往往只是临时问题,不足以判断稳定话题。6 轮左右通常已经能看出用户是否围绕同一主题持续追问。

项目级 code_explain 触发摘要。

项目级解释往往包含模块职责、目录结构和用户关注点。它值得尽早尝试摘要,但只能写入 project_notes;仓库构建元信息由程序侧刷新到 repo_profile

LONG_TERM_SIGNAL 触发摘要。

当用户输入中出现明显长期偏好信号时,系统会尝试尽快摘要,避免“以后都这样”这类规则只停留在短期上下文里。

保存容量

阈值 设计理由
recent_turns 12 覆盖一段自然 CLI 对话,超过后旧 turn 更适合压缩
active_topics 8 覆盖几个并行关注点,避免变成主题仓库
long_term_memory 50 保持长期偏好和项目讨论笔记可读、可审查、可人工 diff
scope_paths 10 保留代码解释范围线索,避免路径列表膨胀
user_text 1000 保留问题主体,不把长日志完整写入 Memory
assistant_text 2000 供摘要器理解回答,不直接注入 Prompt
label 60 保持列表可读
summary 300 控制 Prompt 注入噪声

Prompt 注入

进入 code_explain / general_chat 前,系统会构造 Memory Context,最多注入:

  • 相关 durable_preferences:5 条。
  • 当前 repo_profile:有值时注入。
  • 相关 project_notes:5 条。
  • 相关 active_topics:3 条。
  • ready 的 recent turn summaries:3 条。
  • pending recent turn 的 user_text:2 条。

注入顺序体现优先级:稳定偏好和仓库元信息优先,项目讨论笔记、近期话题和问答摘要次之,未摘要用户原文只作为兜底线索。

严格禁止注入:

  • assistant_text
  • 源码全文
  • 补丁 diff
  • 工具 observation
  • reasoning

文件大小

24KB 是软限制,超过后触发整理。32KB 是硬限制,超过后必须裁剪 working_memory 或拒绝本轮写入。

优先保留 long_term_memory,因为它是治理后的沉淀资产;优先裁剪 working_memory,因为它本来就是近期材料。

6. 运行链路

普通问答 Memory 的主链路如下:

code_explain/general_chat 完成
-> 写入 recent_turn,summary_status=pending
-> 判断是否触发摘要
-> 单线程后台调度 MemorySummarizer
-> RepoProfileCollector 刷新 repo_profile 仓库元信息
-> 短 LLM 生成 Memory Delta
-> MemoryDeltaParser 解析 JSON
-> MemoryDeltaApplier 做程序侧硬校验
-> MemoryStore 原子写回 `memory.json`
-> 下一轮普通问答构造 Prompt 时只注入相关摘要

摘要是被动触发的。AutoPatch-J 启动时不会自动后台总结,也不会监听文件变化。

后台调度器使用单线程执行,避免多个摘要任务并发写同一个 Memory 文件。

如果上一轮摘要任务尚未完成,本轮不会重复提交。 /reset 会提升 generation,旧任务即使完成也会被丢弃,避免状态清空后又写回旧结果。

7. 短 LLM 与 Memory Delta

短 LLM 不输出完整 Memory 文件,只输出 Memory Delta。

示例:

{
  "turn_summaries": [
    {
      "turn_id": "turn_20260501_123000_001",
      "summary": "用户关注 Java Optional 的空值建模和安全用法。"
    }
  ],
  "topic_operations": [
    {
      "operation": "create_new",
      "label": "Java Optional",
      "summary": "用户近期关注 Optional 的正确用法。",
      "related_turn_ids": ["turn_20260501_123000_001"]
    }
  ],
  "long_term_operations": [
    {
      "operation": "create_new",
      "type": "durable_preference",
      "label": "answer style",
      "summary": "用户偏好中文、工程化、直接的回答。",
      "source": "user_explicit"
    },
    {
      "operation": "create_new",
      "type": "project_note",
      "label": "review module",
      "summary": "用户关注 review 模块如何管理 finding 队列和补丁确认。",
      "source": "conversation_summary"
    }
  ]
}

更新已有长期记忆时,LLM 必须使用程序提供的 target_id

{
  "long_term_operations": [
    {
      "operation": "update_existing",
      "target_id": "mem_20260501_001",
      "summary": "提交信息必须使用 <type>: <lowercase english phrase> 格式。"
    }
  ]
}

如果 LLM 输出不存在的 id、非法类型、非法来源或过长字段,程序会拒绝对应 operation。这套机制的价值是:

  • LLM 不能随意改坏整个 Memory 文件。
  • 长期记忆不依赖 LLM 自由生成 key。
  • 非法 Memory Delta 可以丢弃,不影响已有记忆。
  • 构建元信息由程序提取,LLM 只能沉淀项目讨论笔记,不能把构建文件脑补成业务事实。

8. 存储与恢复

Memory 使用项目级 JSON 文件存储,位置在当前仓库的 .autopatch-j/memory.json

选择 JSON 的原因:

  • 数据量小,没必要一开始引入数据库。
  • 文件可读、可 diff,方便人工审查。
  • 写坏时可以备份和回退。
  • /reset 可以把它作为可重建状态一起清理。

如果读取失败或 JSON 损坏,系统会尝试把坏文件移动为 memory.corrupt.json 或带时间戳的 memory.corrupt.*.json,然后使用空 Memory 继续运行。

如果 JSON 版本号和当前 MEMORY_VERSION 不一致,系统不做迁移兼容,直接按空 Memory 处理。项目尚未正式上线,优先保证当前结构清晰,不保留旧结构读取逻辑。

9. 当前明确不做

V1 明确不做:

  • 跨项目记忆。
  • 补丁记忆。
  • 源码全文记忆。
  • 工具输出记忆。
  • reasoning 记忆。
  • 数据库、embedding、RAG。
  • 用户手动编辑 Memory 的复杂 UI。
  • 自动把 LLM 猜测沉淀为长期记忆。
  • 从构建文件推断业务用途。

这些限制不是能力缺失,而是架构边界。Memory 先服务普通问答连续性,同时保持代码修复链路稳定可控。

10. 后续演进

如果真实使用中出现 Memory 条目增长、简单相关性检索不够准确、多项目偏好需要共享、仓库元信息需要更强解析能力等问题,可以逐步演进:

  • 将存储从 JSON 迁移到 SQLite。
  • 给长期记忆增加 embedding 检索。
  • 增加 /memory 命令族。
  • repo_profile 和代码索引、README、构建文件建立更明确的证据链接。

这些增强不应改变当前最重要的边界:普通问答 Memory 不进入 code_audit / patch_explain / patch_revise 修复链路。