21
1

Agent 上下文管理:从内存 Session 到 MySQL 持久化与自动压缩

2026-09-05

Agent 上下文管理:从内存 Session 到 MySQL 持久化与自动压缩

前言

在开发 Agent 时,很多人一开始会把“上下文”“Memory”“Session”“数据库聊天记录”“上下文压缩”混在一起。

但实际上,这几个概念解决的是完全不同的问题。

以一个 Coding Agent 为例:

用户输入
   ↓
AgentSession
   ↓
模型
   ↓
Tool
   ↓
Tool Result
   ↓
模型继续思考

模型之所以能知道“刚才发生了什么”,并不是因为模型自己拥有持续记忆,而是 Agent Runtime 一直维护着一份当前上下文。

如果会话很长,这份上下文会越来越大,最终超过模型的 Context Window。

于是又需要:

Context Compaction

同时,如果程序关闭之后还希望用户能够重新打开历史会话,就需要数据库持久化。

因此,一个完整的 Agent 上下文系统实际上至少包含三个部分:

Agent Context
+
Persistent History
+
Compaction Checkpoint

一、首先区分三个概念

可以先用一句话概括:

内存负责“模型现在需要知道什么”,数据库负责“这个会话完整发生过什么”,Compaction 负责“上下文太长时如何缩短”。

整体结构:

                    Agent 会话
                       │
        ┌──────────────┼──────────────┐
        ↓              ↓              ↓
   当前 Context    完整历史记录    压缩检查点
        │              │              │
     内存 / Pi        MySQL         MySQL
        │
        ↓
       LLM

这三层不能混为一谈。


二、模型运行时的上下文存在哪里?

以 Pi SDK 为例,如果创建 Session:

const { session } = await createAgentSession({
    sessionManager: SessionManager.inMemory()
});

那么当前会话运行期间,Pi 会在内存里维护 AgentSession。

其中包括:

User Message
Assistant Message
Tool Call
Tool Result
Compaction
当前状态

例如用户说:

帮我看看这个项目是什么项目

Pi 可能执行:

User
↓
LLM
↓
bash("dir")
↓
Tool Result
↓
LLM
↓
read("package.json")
↓
Tool Result
↓
LLM
↓
最终回答

当前有效上下文可能是:

System Prompt

User:
帮我看看这个项目是什么项目

Assistant:
我要查看目录

Tool:
package.json
src
tsconfig.json

Assistant:
我要读取 package.json

Tool:
package.json 内容

Assistant:
这是一个 TypeScript + Pi SDK 项目

这些内容由当前 AgentSession 管理。


三、每一轮模型调用都会重新带上 Context

LLM 本身没有真正的持续记忆。

它之所以知道上一轮发生了什么,是因为 Runtime 每次调用模型时,都会把当前上下文重新发送过去。

概念上:

第一次模型调用:

System
+
User

第二次:

System
+
User
+
Assistant Tool Call
+
Tool Result

第三次:

System
+
User
+
Assistant Tool Call
+
Tool Result
+
Assistant Tool Call
+
Tool Result

因此:

模型的“记忆”本质上是 Context 重放。

这也是 Agent Context Management 的基础。


四、为什么还需要 MySQL?

如果当前 Session 只存在内存:

pnpm dev
↓
AgentSession
↓
聊天

那么程序运行期间没有问题。

但是一旦:

Ctrl + C
关闭程序
服务器重启
进程崩溃

内存中的 Session 就不存在了。

因此需要 MySQL 做持久化。

MySQL 主要负责:

历史会话
完整聊天记录
用户查看
Web 页面展示
审计
恢复
统计

例如:

agent_session

保存:

session_id
user_id
project_id
title
created_at
updated_at

另外:

agent_message

保存完整消息:

id
session_id
role
content
created_at

例如:

1 user       帮我看看这个项目
2 assistant  这是一个 TypeScript 项目
3 user       帮我创建 README
4 assistant  已创建完成

五、运行期间每次聊天都查 MySQL 吗?

不需要。

这是一个很重要的设计。

正常运行:

打开 Session
    ↓
恢复一次上下文
    ↓
AgentSession 常驻内存
    ↓
用户连续聊天

用户每轮:

User
↓
Pi AgentSession
↓
LLM / Tool
↓
新消息写 MySQL

MySQL 只需要:

INSERT

新增本轮消息。

并不需要:

每轮 SELECT 全部历史

否则会非常低效。

所以在线运行时:

AgentSession
负责实时上下文

MySQL
负责增量持久化

六、什么时候需要查询 MySQL?

一般只有这些情况:

程序重新启动

用户打开历史会话

用户切换 Session

服务器重新创建 AgentSession

Session 被释放后重新加载

这时候才需要:

session_id
↓
查询数据库
↓
恢复有效上下文
↓
创建新的 AgentSession

因此 MySQL 更像:

Session 的持久化存储层。

而不是:

每次 LLM 调用都实时依赖的 Context Store。


七、问题来了:Context 会越来越长

如果用户一直聊天:

M1
M2
M3
...
M100
...

Context Token 会不断增长。

例如:

10K
30K
60K
100K
120K

模型都有 Context Window。

一旦接近极限,就必须压缩。

这就是:

Context Compaction

八、Context Compaction 是什么?

最简单理解:

旧消息很多
   ↓
调用 LLM 总结
   ↓
生成 Summary
   ↓
最近消息保持原文

例如原本:

M1
M2
M3
...
M100

Pi 判断:

M1 ~ M70 太老
M71 ~ M100 最近

于是:

M1 ~ M70
↓
LLM
↓
Summary A

最近的:

M71 ~ M100

继续保持原文。

最终给模型的 Context:

System Prompt

+

Summary A

+

M71
M72
...
M100

而不是:

M1 ~ M100

全部发送。


九、为什么一定要保留最近消息原文?

因为 Summary 是有损的。

例如历史:

用户:
帮我修改 UserController 第 87 行

Assistant:
修改完成

Tool:
具体代码...

如果全部总结成:

用户修改过 UserController

很多细节会丢失。

因此合理做法是:

旧内容 → Summary

最近内容 → 原样保留

这样既节省 Token,又保证近期操作细节完整。


十、Compaction 后 MySQL 会删除旧消息吗?

不会。

这是非常重要的原则:

上下文压缩只影响“模型当前看到什么”,不能影响数据库里的完整历史。

假设:

M1 ~ M70

已经被压缩成:

Summary A

MySQL 的 agent_message 仍然保存:

M1
M2
...
M70
M71
...
M100

一条都不删。

数据库负责:

完整事实

Pi Context 负责:

有效上下文

十一、那压缩结果应该怎么保存?

除了:

agent_message

还需要:

agent_compaction

例如:

id
session_id
summary
first_kept_message_id
tokens_before
created_at

假设:

Summary A

代表:

M1 ~ M70

而:

M71

开始继续保留原始消息。

数据库记录:

summary = Summary A

first_kept_message_id = 71

这就是一个:

Compaction Checkpoint


十二、first_kept 到底是什么?

first_kept 不是一条总结。

它只是一个“切割位置”。

例如:

M1
M2
...
M70
M71
...
M100

压缩结果:

Summary A = M1 ~ M70 的总结

first_kept = M71

真正恢复的有效 Context 是:

Summary A
+
M71
+
M72
+
...
+
M100

所以正确理解是:

Context
=
Summary
+
从 first_kept 开始到最新的消息

十三、程序重启以后怎么恢复?

假设程序关闭前:

agent_message:

M1
M2
...
M103

同时:

agent_compaction:

Summary A
first_kept = M71

程序重新启动。

用户选择:

session_id = 1001

第一步:

SELECT *
FROM agent_compaction
WHERE session_id = 1001
ORDER BY id DESC
LIMIT 1;

拿到:

Summary A
first_kept = 71

然后:

SELECT *
FROM agent_message
WHERE session_id = 1001
  AND id >= 71
ORDER BY id;

得到:

M71
M72
...
M103

最终恢复:

System Prompt
+
Summary A
+
M71 ~ M103

然后重新创建 AgentSession。


十四、为什么不能直接加载全部历史?

假设:

M1 ~ M500

已经有:

160K Token

如果程序重启时直接:

SELECT 所有历史
↓
全部塞回模型

可能直接超过模型 Context Window。

而且上次 Pi 已经做过 Compaction:

160K
↓
Summary + Recent
↓
40K

如果重启后又把 160K 全部塞回去,相当于把压缩成果全部浪费。

所以:

保存 Compaction Checkpoint 是为了让 Session 恢复后延续原来的有效 Context 状态。


十五、第二次压缩会发生什么?

第一次:

M1 ~ M70
↓
Summary A

保留:
M71 ~ M100

继续聊天:

M101
...
M180

又快到 Context Window。

第二次:

Summary A
+
M71 ~ M150
↓
LLM
↓
Summary B

然后:

保留:
M151 ~ M180

当前 Context:

Summary B
+
M151 ~ M180

数据库:

agent_message
M1 ~ M180

全部存在。

agent_compaction:

Compaction 1
Summary A
first_kept = M71

Compaction 2
Summary B
first_kept = M151

恢复的时候只需要最新一次:

Summary B
+
M151 ~ M180

即可。


十六、因此会形成三个不同的“数据视图”

1. 完整历史

MySQL:

M1
M2
M3
...
M180

作用:

聊天记录
UI 展示
审计
统计
历史查询

2. Compaction Checkpoint

MySQL:

Summary B
first_kept = M151

作用:

知道有效 Context 如何恢复

3. 当前运行 Context

Pi 内存:

System Prompt
+
Summary B
+
M151 ~ M180

作用:

真正发送给 LLM

十七、完整架构

最终可以画成:

                       用户
                        │
                        ↓
                  Pi AgentSession
                        │
                 当前有效 Context
                        │
              ┌─────────┴─────────┐
              ↓                   ↓
            Model                Tools
              │                   │
              └─────────┬─────────┘
                        ↓
                    Agent Loop
                        │
                        ↓
              Context Token 增长
                        │
                达到压缩阈值?
                  /          \
                No            Yes
                │              │
              继续          Compaction
                               │
                     旧消息 → Summary
                     最近消息 → 保留
                               │
                               ↓
                    Summary + Recent
                               │
                               ↓
                         继续 Agent


与此同时:

                  Agent Events
                        │
                        ↓
                      MySQL
                        │
        ┌───────────────┼───────────────┐
        ↓               ↓               ↓
 agent_session     agent_message   agent_compaction
 会话信息           完整历史         压缩检查点

十八、最重要的几个原则

原则一

LLM 当前 Context
≠
数据库完整历史

模型不需要每次看到数据库所有内容。


原则二

在线运行
↓
AgentSession 管 Context

数据库
↓
只做增量持久化

不要每轮查询全部聊天历史。


原则三

Compaction
≠
删除历史

压缩只影响模型输入。

数据库始终保存完整历史。


原则四

恢复会话时:

最新 Summary
+
first_kept 之后的消息

而不是:

全部历史

重新塞进模型。


十九、Context 和长期 Memory 仍然不是一回事

即使我们已经有:

Session Context
MySQL History
Compaction

它们依然和:

Long-term Memory

不同。

例如:

用户喜欢 Java
用户喜欢详细注释
用户一直使用中文

这种属于:

User Memory

它可能跨越:

Session A
Session B
Session C

而 Session Context 只是:

当前这个会话发生过什么。

所以完整 Memory 架构可能是:

Memory
│
├── Session Context
│      当前会话
│
├── Conversation History
│      MySQL 完整记录
│
├── Compaction
│      当前会话压缩状态
│
└── Long-term Memory
       用户画像 / 项目长期知识

二十、最终理解

现在可以把 Agent 上下文管理总结成一句话:

Agent 在线运行时,由内存中的 Session 管理模型当前真正需要的上下文;MySQL 保存完整历史;当上下文过长时,通过 LLM 生成 Summary,只保留“Summary + 最近消息”继续运行;数据库额外保存 Compaction Checkpoint,程序重启以后根据“最新 Summary + first_kept 之后的消息”恢复有效 Context。

最终关系:

完整历史
    ↓
  MySQL
    │
    ├───────────────┐
    │               │
    ↓               ↓
Messages        Compaction
                    │
                    ↓
              Summary + first_kept
                    │
                    ↓
              恢复 AgentSession
                    │
                    ↓
             当前有效 Context
                    │
                    ↓
                   LLM

这套思路不只适用于 Pi。

实际上以后无论使用:

LangChain
LangGraph
Pi SDK
自己手写 Agent Runtime

都会遇到相同的核心问题:

哪些信息需要永久保存,哪些信息需要进入当前 Context,以及 Context 太长以后如何压缩和恢复。

这就是 Agent Context Management 最核心的工程问题。

Agent 上下文管理:从内存 Session 到 MySQL 持久化与自动压缩
/archives/agent-shang-xia-wen-guan-li-cong-nei-cun-session-dao-mysql-chi-jiu-hua-yu-zi-dong-ya-suo
作者
Administrator
发布于
2026-09-05
许可协议
CC BY-NC-SA 4.0

评论