Skip to content

记忆系统

OryxOS 为 Agent 提供三层记忆。每一层有不同的作用域、生命周期和存储后端。MemoryService 是统一的门面,ReActLoopPromptBuilder 都通过它来访问记忆,不直接接触各层的实现。

三层记忆

层级存储作用域核心阶段
会话记忆SQLite sessions.messages_json仅限当前对话
长期记忆.oryxos/agents/<name>/MEMORY.md(按 Agent 隔离)跨会话持久化,按 Agent 隔离
情景记忆向量数据库(规划中)跨历史会话的语义检索

会话记忆 是对话历史——当前会话中积累的消息列表。以 JSON 格式序列化存入 SQLite。PromptBuilder 在每次构建 Prompt 时注入最近 max_history_turns 轮。它自动积累,Agent 不需要显式写入。

长期记忆 是一个只追加、带时间戳的 Markdown 文件,Agent 通过 save_memory 工具写入、通过 recall_memory 关键词检索。跨会话持久化,每次构建 Prompt 都会注入。Agent 用它记住用户偏好、项目事实,以及对话结束后值得保留的内容。

情景记忆 规划在扩展阶段实现。它会将历史对话建索引到向量数据库,在构建 Prompt 时检索语义相关的历史片段。

长期记忆按 Agent 隔离

长期记忆遵循「一个目录 = 一个 Agent」原则:每个 Agent 有自己的记忆文件 .oryxos/agents/<name>/MEMORY.md,与该 Agent 的 AGENT.mdskills/ 同目录。无 Agent 上下文(非 Agent 触发的直接调用、单测)时,回退到全局 .oryxos/memory/MEMORY.md。Agent 名会做安全校验,任何非法段一律回退全局文件,防止目录穿越。

当前是哪个 Agent 由执行上下文决定——save_memory / recall_memory 工具路径通过 ToolExecutionContext 置入,读路径(buildContext / 按 Agent 读取)在 load 前后临时置入。

时间戳与两个区块

每条记录都写成一行带时间戳的内容:- [yyyy-MM-dd HH:mm:ss] <内容>。一个 MEMORY.md 文件按两个区块组织:

  • ## 核心记忆core)——始终完整注入,从不截断
  • ## 归档记忆archival)——运行足迹;量大时超限截断

save_memory 写入其中一个区块(corearchival)。recall 和截断只作用于归档区;核心区物理隔离,始终完整保留。

自动触发足迹

每次 Agent 被触发,OryxOS 都会自动向该 Agent 的归档记忆追加一行——把这次的用户消息和回复压成一条紧凑记录。这让每个 Agent 都有一份「自己干过什么」的可审计足迹,Agent 无需自己调用 save_memory

长期记忆的操作

LongTermMemoryStore(默认后端:MarkdownMemoryStore)提供这些操作:

方法行为
append(content, scope)向核心区或归档区追加一条带时间戳的记录
load()返回两个区块;归档区超限则截断,核心区完整返回
recallByKeyword(keyword)返回归档区中包含该关键词的行
截断归档区超过 4000 字符时,从头部截断,保留最近的内容

4000 字符的限制防止归档区占用过多 context window。截断时最旧的归档条目先删,优先保留近期内容。核心区永远不动。

这个接口在设计上为向量检索预留了升级路径。recallByKeyword 可以替换为语义检索实现,不需要改动 MemoryService 或任何调用方。

可插拔后端

长期记忆后端通过 memory.backend 配置项可插拔——换后端只换注入的 LongTermMemoryStoreMemoryService 门面签名与所有调用方保持不变。

memory.backendStore说明
markdown(默认)MarkdownMemoryStore零依赖、人可读、git 可跟踪的 Markdown 文件
sqliteSqliteMemoryStore记忆按条存入 memory_entries 表;记忆量变大后的结构化升级,仍零外部依赖(复用既有 SQLite)
mem0Mem0MemoryStore委托给外部 mem0 记忆服务

save_memory 和 recall_memory 工具

Agent 通过两个内置工具与长期记忆交互,不直接写 MEMORY.md

save_memory — 向当前 Agent 的 MEMORY.md 追加一段文本,写入 corearchival 区块。Agent 自己决定保存什么、写哪个区、何时保存。核心阶段没有自动摘要。

text
工具: save_memory
输入: { "content": "用户偏好简洁的回复,不用列表。", "scope": "archival" }
效果: 向该 Agent 的 MEMORY.md 归档区追加带时间戳的条目

recall_memory — 按关键词检索归档区,返回匹配的行。在保存之前,Agent 可以先检索确认自己是否已经知道某件事,避免重复记录。

text
工具: recall_memory
输入:  { "keyword": "偏好" }
输出: "- [2026-07-22 09:30:00] 用户偏好简洁的回复,不用列表。"

两个工具都经过 ToolExecutor 处理,和其他工具调用一样记录在 tool_invocations 里。

通过 REST 读取记忆

管理台通过以下端点读取某个 Agent 的长期记忆:

text
GET /api/v1/agents/{name}/memory

响应(包裹在统一的 { code, message, data, timestamp } 信封里)携带该 Agent 的 MEMORY.md,管理台把它渲染成两张表:核心记忆(core)和归档记忆(archival)。

记忆如何注入到 Prompt

PromptBuilder 在每次循环迭代时调用 MemoryService.buildContext(session),将结果插入系统提示词的第二段,紧跟在 Agent 身份和 Bootstrap 文件之后:

text
[1] 系统提示词(AGENT.md 正文 + bootstrap)
[2] MEMORY.md  ← 在这里注入:核心区全量 + 归档区截断至 4000 字符
[3] 对话历史(最近 N 轮)
[4] 可用工具(JSON Schema)

记忆块始终存在,即使 MEMORY.md 为空。

MEMORY.md 与 USER.md 的区别

这两个文件看起来相似,但服务于相反的目的:

MEMORY.mdUSER.md
谁来写Agent(通过 save_memory,外加自动触发足迹)用户(手动编写)
OryxOS 的行为读 + 写只读
内容Agent 积累的观察、学到的事实,以及运行历史用户的初始偏好和背景信息
重置从不(只追加;仅归档区过长时截断)从不(由用户自行管理)
位置.oryxos/agents/<name>/MEMORY.md(按 Agent 隔离).oryxos/USER.md

USER.md 属于 Bootstrap 组,在 Prompt 的第 [1] 段注入。MEMORY.md 在第 [2] 段注入。两者对 Agent 都可见,但只有 MEMORY.md 可以被 Agent 修改。

规划中的功能

  • 情景记忆 — 将完整会话记录建索引到向量数据库;构建 Prompt 时检索最相关的 top-k 片段
  • 向量化 recall — 用基于 embedding 的语义检索替换 recallByKeyword,检索范围为 MEMORY.md 内容
  • 记忆摘要 — 截断前自动压缩旧条目,用更少的字符保留更多信息