Skip to content

5.5 记忆系统(Memory)

本节你会学到

  • 记忆系统的三种数据:持久化记录、会话摘要、召回候选
  • 两个 SQLite 数据库(项目级 + 用户级)以及如何选
  • 怎样干净地清空 / 迁移 / 禁用记忆

记忆系统让 Agent 跨会话记住用户偏好、项目事实、约定。区别于"对话历史"(本轮聊天内容),记忆是跨会话持久化的。

记忆的三种数据

类型存储位置作用
持久化记忆 (MemoryRecord)SQLite memories偏好、事实、约定——软删除,永不物理删除
会话摘要 (SessionSummaryRecord)SQLite session_summaries上下文压缩时由 LLM 生成的会话摘要
召回候选 (RecallCandidate)仅在内存中按当前用户消息打分出来的 top-k 记忆

两个 SQLite 数据库

<working_directory>/.agentao/memory.db  ← 项目级
~/.agentao/memory.db                     ← 用户级(跨项目)

项目级保存"这个项目专属的事实"(项目代号、团队约定、部署路径);用户级保存"用户本人偏好"(喜欢 tabs 还是 spaces、工作时区)。

降级:文件系统不可写怎么办

ACP 子进程、受限容器、只读文件系统里,记忆 DB 可能写不了。fallback 策略下沉到存储层(SQLiteMemoryStore.open_or_memory),由嵌入式工厂负责接进来:

SQLiteMemoryStore.open_or_memory(<cwd>/.agentao/memory.db)
 ├─ 成功 → 正常使用
 └─ 失败(OSError / sqlite3.Error)
     → 返回 SQLiteMemoryStore(":memory:")
     → 记一条 warning
     → Agent 继续启动(不崩溃)

用户作用域走严格构造 SQLiteMemoryStore.open(...):失败时直接禁用该作用域,而不是降级到 :memory:(否则用户作用域写入会被默默路由到项目作用域,混淆两层语义)。记忆永远不会让 Agent 启动失败——最差就是丢失跨会话持久化。

在嵌入里怎么用

大多数情况你不需要直接碰 MemoryManager——Agentao 自己会:

  1. 每轮 chat() 前把相关记忆注入系统提示(<memory-context> 块)
  2. LLM 通过内置 save_memory 工具主动存记忆
  3. clear_history() 时不清记忆(记忆跨会话存在)

自定义 DB 路径

如果你要把多个 Agent 实例的记忆完全隔离(多租户),每个实例传不同的 working_directory 就够了——项目 DB 会落在 <working_directory>/.agentao/memory.db,天然隔离。

禁用用户级记忆

默认工厂会读写 ~/.agentao/memory.db。如果你的产品不希望 Agent 沾染"用户"层的任何状态,自己构造 MemoryManager、显式 user_store=None 传进来即可(#16 之后 MemoryManager 接的是预先构造好的 store):

python
from agentao.memory import MemoryManager, SQLiteMemoryStore

workdir = Path("/tmp/sess")
agent = Agentao(
    working_directory=workdir,
    memory_manager=MemoryManager(
        project_store=SQLiteMemoryStore.open_or_memory(
            workdir / ".agentao" / "memory.db"
        ),
        # user_store=None — 不要跨项目用户作用域
    ),
    llm_client=...,
)

完全禁用记忆

最简单的方法:让项目 store 指向 :memory:(user_store 默认 None,无需显式给):

python
from agentao.memory import MemoryManager, SQLiteMemoryStore

agent = Agentao(
    working_directory=Path("/tmp/sess"),
    memory_manager=MemoryManager(
        project_store=SQLiteMemoryStore(":memory:"),
    ),
    llm_client=...,
)

会话内工作,进程结束即丢。

自定义记忆后端(Redis / Postgres / 远端 API)

MemoryStore 能力协议(agentao.capabilities.MemoryStore)就是这种场景的官方注入点——实现一次它的 15 个方法,把实例作为 project_store= / user_store= 传入即可。详见 docs/guides/embedding.mdagentao/capabilities/memory.py

提示词里的两个记忆块

Agentao 在系统提示中注入两种块(组装逻辑在 agentao/prompts/builder.py::SystemPromptBuilder.build(),由 agent._build_system_prompt() facade 转发):

<memory-stable> — 稳定块

长期、结构化的记忆(类型 profile / constraint / decision)。每轮 chat() 都一样,从而享受 prompt cache(大多数 LLM 厂商把稳定前缀缓存起来、降费降延迟)。

<memory-context> — 动态召回块

根据本轮 user message 做 top-k 召回,每轮不同。从所有已存记忆里按关键词/Jaccard/标签/时间 等打分挑最相关的。

两块的分工

  • 稳定块 = "这个用户一直是这样的"
  • 动态块 = "这次用户问的跟历史哪几条相关"

MemoryGuard:敏感信息防护

MemoryGuard 在记忆写入前做验证。默认配置(agentao/memory/guards.py)拒绝明显的敏感信息:

  • API keys、token、password 字面量
  • 信用卡号、身份证号等 PII 模式

自定义:

python
from agentao.memory.guards import MemoryGuard

class StrictGuard(MemoryGuard):
    def validate(self, content: str, key: str) -> None:
        super().validate(content, key)   # 复用默认检查
        # 再加你的规则
        if "internal-only" in content.lower():
            raise SensitiveMemoryError("Cannot store 'internal-only' content")

agent = Agentao(working_directory=Path("/tmp"))
agent._memory_manager.guard = StrictGuard()

LLM 能做什么、不能做什么

Agentao 只给 LLM 暴露了记忆的工具:

python
# LLM 可以调
save_memory(key="preference", value="user prefers TypeScript strict mode")

LLM 不能

  • 列出所有记忆
  • 删除记忆
  • 清空记忆
  • 搜索记忆(但它看到的 <memory-context> 块已经是召回结果)

这是故意的——避免 LLM 被 prompt injection 攻击后读/写/删用户的整个记忆库。所有读/删/清操作只通过 CLI 命令 /memory search/memory delete/memory clear 提供,或你的 host 直接调 MemoryManager API。

在你的 UI 里做记忆管理

宿主可以直接调 MemoryManager 给用户提供"查看/删除记忆"的 UI:

python
mm = agent._memory_manager

# 列出项目级所有记忆(不传 scope 时返回 project + user 合集)
for record in mm.get_all_entries(scope="project"):
    print(record.title, "—", record.content[:60])

# 搜索
for record in mm.search("typescript"):
    print(record)

# 按 id 软删除(按用户友好的 key 删用 ``delete_by_title``)
mm.delete(record_id)

# 完全清空:把所有作用域的 memory 都软删除 + 删掉会话摘要
mm.clear()                       # 两个作用域;传 scope="project" / "user" 可只清一个
mm.clear_all_session_summaries()

合规价值:给用户"查看 AI 知道关于我的什么"和"一键遗忘"的按钮,是很多 SaaS 场景的硬性要求。

记忆 vs 会话历史 vs AGENTAO.md

存放内容用哪个
"这次对话刚聊的事"会话历史(agent.messagesclear_history() 清)
"用户一直是 Python + tabs 党"记忆(save_memory
"本项目用 Ruff 做 lint、端口 8080"AGENTAO.md(项目级约束,进 git)
"本轮需要的技术方案"Plan 模式(不跨会话)

⚠️ 常见陷阱

上线前先确认这几条

  • 把大文档塞进记忆 —— 召回会把它带进每个 prompt,撑爆上下文
  • 多租户共享记忆 —— 用户作用域 DB 是进程全局,跨租户泄漏只是时间问题
  • 忘记记忆跨 clear_history() 存活 —— "新对话"还记得旧事

下面每一条都附完整修法。

❌ 把大文档塞进记忆

python
save_memory("doc", open("readme.md").read())   # 几十 KB

每轮都会被召回,爆上下文。记忆应该是几百字以内的结构化陈述。大文档放仓库里让 Agent 按需用 read_file 读。

❌ 多租户共享记忆

两个用户的 Agent 如果都落在默认 Path.cwd() 或同一个 working_directory,会读写同一个 DB,互相泄漏信息。多租户必须按用户隔离 working_directory

❌ 忘记记忆跨 clear_history() 存活

用户在 UI 上点"新对话"→ agent.clear_history() 只清会话,不清记忆。如果"新对话"应该忘掉一切,要同时调 MemoryManager.clear() + clear_all_session_summaries()

TL;DR

  • 记忆 ≠ 对话历史。记忆跨会话存在 SQLite;历史活在 agent.messages 上。
  • 两个作用域 / 两个 DB:项目<wd>/.agentao/memory.db)和用户~/.agentao/memory.db)。多租户部署里要么按 tenant_id+user_id 拆分用户作用域,要么直接禁用。
  • 只读 / 受限 FS 自动降级为内存 store 并 warning——Agent 还是能起。
  • clear_history() 清记忆;这是设计如此。要"新对话"彻底忘记时显式同时清两边。

→ 下一节:5.6 系统提示定制