章末自测

题目 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、长任务为什么越来越容易爆,以及该怎么提前发现。

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发
头像-大赛人网
欢迎您留下宝贵的见解!
提交
头像-大赛人网

昵称

取消
昵称

    请登录后查看评论内容