题目 1:为什么 Session 不能只保存 Conversation History?
请至少从 Working Memory、Tool History、Run History 和恢复能力四个方面说明。如果只保存 messages,用户下午回来“继续刚才任务”时可能出现什么问题?
Conversation History 只能告诉系统过去说过什么,却不能稳定表达当前任务认知、已经执行过哪些 Tool、包含多少次 Run,以及会话当前是否还允许继续。只恢复 messages,模型往往还需要重新从长历史提炼 Goal、Completed、Errors 和 Next Action,也容易重复 Tool Call。Session 应该作为更完整状态容器,至少组织 messages、working_memory、tool_history 和 runs,才能真正支持恢复后继续执行。
题目 2:判断题——一个用户应该只有一个 session_id,这样最方便管理,对吗?
请举一个用户同时使用 Coding Agent 和 Data Analysis Agent 的例子说明。
不对。一个用户可以同时拥有多个独立会话。例如 Session A 用 Coding Agent 修 Flask 项目,Session B 用 Data Analysis Agent 分析收入下降。如果都用 user_id 作为唯一 session_id,两类消息、Working Memory 和 Tool History 会混在一起。更合理是 user_id 表示“谁在使用系统”,session_id 表示“一段独立会话”,因此是一对多关系。
题目 3:代码阅读——为什么下面做法会让持久化以后很难维护?
session = sessions[session_id]
session.messages.append(message)
session.status = "CLOSED"
请从统一更新时间、数据库同步、权限和生命周期控制角度解释。
业务代码直接修改 Session 对象,会绕过统一管理逻辑。未来持久化以后,内存对象虽然改了,数据库可能没有同步;
updated_at也可能忘记更新;关闭操作没有校验当前状态和权限;后续如果需要写 Audit Log,还要到处补代码。使用 SessionManager 的append_message()、close_session()等接口,可以把状态校验、更新时间、持久化和日志集中管理,调用方不需要关心底层实现。
题目 4:编程实现——写一个最小 SessionManager
要求支持 create_session()、get_session()、close_session()、restore_session(),使用字典存储,Session 至少包含 session_id、messages、status。
import uuid
from dataclasses import dataclass, field
@dataclass
class Session:
session_id: str
messages: list[dict] = field(
default_factory=list
)
status: str = "ACTIVE"
class SessionManager:
def __init__(self):
self._sessions = {}
def create_session(
self,
) -> Session:
session = Session(
session_id=(
"sess_"
+ uuid.uuid4().hex[:8]
)
)
self._sessions[
session.session_id
] = session
return session
def get_session(
self,
session_id: str,
) -> Session:
if (
session_id
not in self._sessions
):
raise KeyError(
session_id
)
return self._sessions[
session_id
]
def close_session(
self,
session_id: str,
) -> None:
self.get_session(
session_id
).status = "CLOSED"
def restore_session(
self,
session_id: str,
) -> Session:
session = self.get_session(
session_id
)
session.status = "ACTIVE"
return session
if __name__ == "__main__":
manager = SessionManager()
session = (
manager.create_session()
)
print(session)
manager.close_session(
session.session_id
)
print(
manager.get_session(
session.session_id
)
)
manager.restore_session(
session.session_id
)
print(
manager.get_session(
session.session_id
)
)
#这个版本只用于理解生命周期。真实项目还需要 Working Memory、Tool History、Run History 和持久化。
题目 5:综合应用——一个 Session 里为什么可以有多个 Run?
请用“先定位 Bug → 再修复 → 再跑完整测试”的流程说明 Session 和 Run 的关系。查看参考答案
Session 表示一段持续上下文,例如“处理这个项目的问题”;用户可以在同一会话里连续提出多个任务。第一次 Run 负责定位 Bug,第二次 Run 根据已有上下文执行修复,第三次 Run 再跑完整测试并总结。三次 Run 都共享同一个 Session 的 Conversation、项目规则和部分 Memory,但每个 Run 自己有独立的 status、start_time、end_time、steps 和 error。这样既保持会话连续性,又能单独观测每次任务。
题目 6:对比分析——close、expire、delete 三种 Session 生命周期操作有什么区别?
请说明它们分别适合什么场景,以及为什么不能全部用一个 active=False 替代。
close 通常表示用户或系统明确结束当前会话,但历史仍然保留,可以读取甚至显式恢复;expire 表示因为长时间无活动或策略自动失效,通常也保留历史,但恢复可能需要额外检查;delete 则意味着真正删除持久化数据。三者语义不同,影响恢复能力、审计和数据保留策略。如果全部压成
active=False,后续就无法区分“用户关闭”“自动过期”和“数据已删除”。
题目 7:概念理解——为什么 SessionManager 应该独立于 Agent Engine?
请从职责分离和未来持久化两个角度解释。
Agent Engine 负责推进 Main Loop、调用模型和 Tool、更新任务状态;SessionManager 负责会话的创建、查找、恢复、关闭和保存。如果两者混在一起,Agent Engine 会同时承担执行逻辑和存储逻辑,难以测试和扩展。独立以后,当前可以用内存字典,未来切 SQLite、MySQL 或其他 Store 时只需要修改 SessionManager 或 Storage Adapter,而 Agent Engine 的核心逻辑不需要跟着变化。
题目 8:故障分析——两个请求都从 Session 版本 3 开始更新 Working Memory,会发生什么?
第一个请求先提交并把版本更新为 4,第二个请求随后仍携带 expected_version=3。请说明服务端为什么不应该直接接受第二次覆盖,以及客户端接下来应该怎么做。
第二个请求基于旧快照计算,如果直接覆盖,会把第一个请求刚写入的状态悄悄丢掉。服务端应返回版本冲突,而不是假装成功。客户端重新读取版本 4,比较两次更新是否可以合并,再基于最新版本提交。若产品希望同一 Session 严格串行,也可以在入口处排队;关键是不能出现两边都显示成功、最后只留下一份状态的情况。
进一步阅读
- Session Management 基础资料:理解 Web 系统中会话标识、生命周期和状态恢复的常见设计。
- Stateful vs Stateless Service:为后面 Flask 服务化理解“HTTP 请求本身无状态,Session 需要额外存储”做准备。
- Event Sourcing / Snapshot 思想:可以类比完整消息历史与当前 Working Memory 快照。
- Database Persistence 相关资料:后面会使用 SQLite 保存 Session、Run、Trace。
- Optimistic Locking 相关资料:真实并发会话更新时,可以避免多个请求互相覆盖状态。
- TTL / Expiration Policy:理解长期无活动 Session 为什么需要自动过期策略。
- Distributed Session Store 相关资料:多 Worker 或多实例部署时,内存 Session 为什么不够。
- Audit Logging 相关资料:Tool History 与 Run History 会在后续 Tracing 中继续演进。
术语表
- 会话(Session):用户与 Agent 在一段连续交互中的状态容器,通常包含 messages、working_memory、tool_history、runs、status 和时间信息。
- 会话标识(session_id):唯一定位某个 Session 的 ID。客户端后续请求通过它告诉服务端“继续哪一段会话”。
- 会话管理器(SessionManager):负责创建、查找、更新、恢复、关闭和列出 Session 的统一组件,把会话生命周期与 Agent Engine 解耦。
- 任务执行(Run):Session 内的一次具体 Agent 任务生命周期,例如“定位 Bug”“修复 Bug”“重新验证”。一个 Session 可以包含多个 Run。
- 活动状态(ACTIVE):表示 Session 当前允许继续追加消息、更新 Memory 和发起新 Run 的状态。
- 关闭状态(CLOSED):表示 Session 已结束新的交互,但历史通常仍然保留,可以读取,是否允许恢复由产品策略决定。
- 过期状态(EXPIRED):由于长时间无活动、TTL 或策略而失效的会话状态,与用户主动关闭不同。
- 会话恢复(Session Restore):根据 session_id 重新加载 Session 的 messages、Working Memory、Tool History 和历史 Run,让 Agent 继续此前上下文。
- 会话归属(Session Ownership):Session 与用户或租户之间的绑定关系。
session_id用来定位会话,归属校验才决定当前请求是否有权访问。 - 乐观锁(Optimistic Locking):更新时携带读取到的版本号;如果保存前版本已经变化,就拒绝旧写入,避免两个请求互相覆盖。
- 幂等键(Idempotency Key):标识一次写请求的唯一 ID。客户端重试相同请求时,服务端返回已有结果,而不是重复追加消息或创建 Run。
本章总结
这一节,我们把 Agent 从:
单次 Run 内部有 Working Memory
继续推进到,多轮会话真正可以被创建、恢复和继续。
核心结构是:
session_id
↓
messages
↓
working_memory
↓
tool_history
↓
runs
session_id 是整个会话的稳定索引。
messages 保存原始 Conversation History。
working_memory 保存当前最重要的任务认知。
tool_history 记录之前真正执行过哪些 Tool。
runs 则记录这段会话里发生过多少次具体任务执行。
当 Session 进入真实服务,还要一起带上三条工程边界:用归属关系控制谁能读写,用版本号阻止并发覆盖,用幂等键消化网络重试。它们不改变 Session 的基本定义,却决定了多轮会话能不能稳定地跑在真实请求环境中。
这一节最重要的两个概念边界是,Session 不等于 messages。
以及,Session 不等于 Run。
Session 是更长期的会话容器。
Run 是 Session 里的一次具体任务生命周期。
所以一个用户完全可以:
上午:
Run 1 定位问题
下午:
Run 2 继续修复
晚上:
Run 3 完整验证
三次 Run 都属于同一个 Session。
也正因为 Session 独立出来,我们才能真正支持:
创建 Session
恢复 Session
读取历史 Session
关闭 Session
我们还实现了一个内存版:
SessionManager
支持:
create_session
get_session
append_message
append_tool_history
add_run
close_session
restore_session
list_sessions
虽然现在底层只是一份 Python 字典,但这并不代表设计没有价值。
恰恰相反,先把接口稳定下来以后,未来替换成:
SQLite
MySQL
PostgreSQL
Redis
Agent Engine 不需要重写。
这是整门课程一直坚持的思路,先把边界设计清楚,再逐步替换实现。
如果你想验证自己是否真的掌握这一节,可以做一个非常实际的实验。
先创建 Session:
用户:
帮我修复启动失败。
让 Agent 更新:
Working Memory:
已定位到 requests 缺失。
然后:
close_session
模拟用户离开。
再:
restore_session
用户说:
继续刚才的任务。
如果 Agent 能直接回答,“我们刚才已经定位到 requests 缺失,下一步检查并修复 requirements.txt。”
而不是重新从零开始,那么 Session 的核心价值就已经跑通了。
下一节,我们会继续处理一个几乎所有长 Agent 都会碰到的问题,Context Window 为什么会爆?
现在我们已经有:
Session
Conversation
Working Memory
Tool History
Runs
这些信息会越来越多。
如果每一轮都无限塞给模型,最终一定会碰到上下文上限。
所以下一节,我们会真正开始讨论,Context Window、Token、长任务为什么越来越容易爆,以及该怎么提前发现。

















请登录后查看评论内容