赞助商LobeHubLobeHub了解更多
ddshfind
登录

第 3 课:智能体循环与会话:一切有据可查

一句话版:DSH 把智能体的整个工作过程记成一份只追加、不修改的会话日志——模型看见什么,日志就记录什么;恢复、fork、回放、遥测、界面全都从这一份日志派生,所以任务跑到一半,也能无损地继续、分叉或回看。


1. 用户故事:任务跑到一半,怎么无损继续?

想象一个场景:你让智能体把一个旧仓库从 Vue 2 迁移到 Vue 3。它已经跑了三个小时——改了几十个文件、跑了好几轮测试、和模型来回商量了上百步。这时候发生了三件让人血压升高的事:

  • 电脑突然重启,进程没了;
  • 或者你改主意了:想换一条更保守的迁移路线试试,又不舍得丢掉已经做完的进度;
  • 或者你想回放一下:昨天到底是哪一步把那个配置文件改坏的?

在普通工具里,这三件事约等于「重来」或「凭记忆找」。在 DSH 里,它们全部是无损、精确、有据可查的:

你想要的DSH 的做法结果
崩溃后继续resumeSessionId 加载持久化会话轮次编号和派生历史从已加载日志继续,像从未中断过
换条路试试fork 一份会话从稳定检查点复制出平行分支,原会话毫发无损
回看历史读取会话日志每一步模型请求、工具调用、结果都有原始记录

这堂课就讲清楚一件事:为什么这一切都做得到。答案藏在一个词里——运行可重建


2. 核心思想:会话是 append-only 事件流,权威日志

会话 = 真源,消息历史 = 派生

先看 dsh-session 包对自己的定义(来源:packages/core/session/README.zh.md):

事件溯源的会话日志和内存存储。Session 是 agent 全部交互历史的仅追加真源,LLM 消息历史由它派生。

把这句话拆开看:

  • 仅追加(append-only):事件只能往日志尾部追加,谁也不能回头修改或删除已经写下的记录;
  • 真源(source of truth):会话日志是唯一权威;模型看到的消息历史、界面显示的 transcript,都是从这份日志派生出来的副本,而不是另外一份状态;
  • 由它派生:你不需要同时维护「日志」「模型历史」「界面状态」三份数据——只有一份,其余全是投影。

模型可见 ⟺ 已记录

架构文档把这条原则写成一句公式(来源:docs/architecture.zh.md ·「会话日志」):

会话日志是权威依据。deriveMessages() 投影出模型历史;原始 assistant/chunk 事件保证回放和 UI 保真。fork、恢复、transcript(文本记录)渲染、遥测和持久化均派生自该事件流。

模型可见 ⟺ 已记录:在 step/start 进入的消息加上折叠后的 request/header 可以重建每个请求。

「模型可见 ⟺ 已记录」是整堂课的心脏:凡是模型能看到的东西,日志里必然有记录;日志里没有的东西,模型也看不到。 不存在「模型偷偷用了什么却没记下来」的黑洞,也不存在「日志里记了但模型看不到」的幽灵。正因为如此,持久化、恢复、fork、回放、遥测、UI——六个看起来毫不相干的子系统,全部从同一份事件流取数,彼此不会对不上账。

💡 打比方:这不是「记日记」,而是「全程录像」。日记是事后凭回忆写的,会漏、会改;录像是事件发生那一刻的原始记录,每一帧都是真的。


3. 三层结构:会话 → 轮次 → 步骤

会话日志不是一团乱麻,它有清晰的三层结构:

会话(append-only 事件流 · 权威日志)模型可见 ⟺ 已记录:可恢复、可 fork、可回放轮次 1领取一条消息轮次 2领取下一条消息步骤1步骤2步骤3步骤1步骤2每个步骤 = 一次模型请求 + 它的工具

会话 → 轮次 → 步骤:事件全都追加进日志,从任意检查点都能重建

  • 会话(Session):一整段交互历史,对应一份 append-only 事件日志,拥有全局唯一的 SessionId
  • 轮次(Turn):从领取一条消息开始,到这次回答结束。轮次由 turn/startturn/end 两个事件包围,turn/end 会如实记录结束原因——正常结束、aborted 取消、error 失败,崩溃恢复还会合成 interrupted
  • 步骤(Step)一次模型请求 + 它的工具。步骤由 step/startstep/end 包围;每次成功的模型调用都会留下一条 assistant/message——哪怕这次调用返回了空内容,或因为 max-tokens 被截断,日志照样记录(空内容只是不进入派生的消息历史,持久事件和用量都在)。

agent-lifecycle 文档一句话点破了分工(来源:docs/agent-lifecycle.zh.md):

持久的回放事实保存在 session/event 中,实时控制与状态则保存在 agent/* 中。

也就是说:日志管「发生了什么」,事件管「现在怎么样」。 前者可以精确回放,后者负责实时驱动(比如 running / idle 状态、收件箱队列)。

一个轮次里往往有好几个步骤:模型说「我要读文件」→ 工具执行 → 结果返回 → 模型再说「我要改文件」→ 工具执行 → ……直到模型认为任务完成,轮次才闭合。只有闭合的轮次,才构成稳定的 fork / 检查点边界。


4. 从同一份日志找回一切:恢复、fork 与检索

恢复:resumeSessionId 续跑

DSH 的智能体循环驱动器(dsh-agent-loop)提供两条进入会话的路径:

  • 创建ctx.agents.create(...)——用新的 sessionId 从零开始;
  • 恢复ctx.agents.resume({ resumeSessionId, ... })——加载一份已经存在的持久化会话接着跑。

agent-loop 文档这样描述恢复(来源:packages/core/agent-loop/README.zh.md):

ctx.agents.resume({ resumeSessionId, agentOptions?, setup?, signal? }):通过 ctx.sessionPersistence 加载持久化会话,使用同一 id 注册 agent,重建历史……轮次编号和派生历史从已加载日志继续。此操作要求存在会话持久化后端;缺少持久化时,resume 会以明确错误拒绝。

三个要点:恢复不是从头重放,而是接着编号续跑(轮次编号和派生历史从已加载日志继续);恢复要求会话真的持久化过;如果压根没配持久化后端,它会直接报错而不是假装成功——宁可不支持,也不给你一份「看起来一样、其实丢了上下文」的假恢复。

fork:复制边界

想「换条路试试」?用 ctx.sessions.fork(source, boundary?, childSessionId?)。它的语义(来源:packages/core/session/README.zh.md):

解析实时会话对象或 id,选取截至 boundary 事件序号(含该事件)的种子(默认为当前最后一个事件),要求所选前缀结束时没有开放轮次,再创建带谱系元数据的实时子会话。

  • 复制边界:默认取当前最后一个事件,也可以显式指定 boundary,但所选前缀的结尾必须是闭合的轮次——不能在一半的轮次上 fork;
  • 谱系元数据:子会话会记录 parentSession 等信息,谁是复制品一查便知;
  • 原会话无损:fork 只是「读日志 + 派生新会话」,源会话一点没动。

检索:session-query 全文检索

日志一天天变大,怎么从里面精确找东西?DSH 提供了专门的会话检索能力家族(session-query)。它「提供经过授权的实时与持久会话日志检索,且独立于压缩」——即使某段上下文后来被压缩替换掉了,原始日志仍在,照样搜得到。

  • searchSessions():跨会话全文搜索,按匹配度最强的事件分组返回;
  • searchEvents():在单个会话内搜索事件;
  • SQLite 提供方用全文搜索(FTS)实现索引;
  • 安全细节:查询词按字面量解释,绝不会被当作可执行的搜索语法——把搜索当数据看,而不是当代码跑。

🎁 三件事的共性:恢复、fork、检索全都只读日志。正因为日志是权威的、完整的、只追加的,这三件事才能各自独立成立、互不冲突。


关键点回顾

这一课记住这五句话就够了:

  1. 运行可重建是 DSH 的承诺:一切模型可见的内容都记录在权威会话日志里——模型可见 ⟺ 已记录
  2. 会话 = append-only 事件流:事件只能追加、不能改写;模型消息历史和 UI transcript 都是从日志派生的投影,日志是唯一真源。
  3. 三层结构:会话 → 轮次 → 步骤。轮次领取一条消息并闭合;步骤 = 一次模型请求 + 它的工具。
  4. 恢复与 forkresumeSessionId 从持久化日志接着编号续跑;fork(source, boundary) 在闭合轮次的稳定边界复制平行会话,原会话无损。
  5. 全文检索:session-query 让日志可搜索(searchSessions / searchEvents),且独立于压缩——被替换掉的旧上下文也查得到。

🚀 下一课预告:模型光有「记忆」还不够,它得能动手。第 3 课我们讲「工具与执行」——智能体是怎么把「调用一个函数」变成一次有权限、有沙箱、有记录的真正行动。

自测题 · 循环与会话

完成作答后点击「提交答案」,可以查看对错与解析。

1. 关于「模型可见 ⟺ 已记录」,下列哪个说法正确?
2. 会话 → 轮次 → 步骤三层结构,哪个说法正确?
3. 关于会话恢复(resume),哪个说法正确?
4. 关于 fork 与全文检索,哪个说法正确?