Guides
Revert
使用 SessionRevertService 回滚会话边界,并在启用 snapshot 时同步恢复文件状态。
回滚用于处理已经产生的会话内容。它和 resume 的目标不同:resume 继续完成一轮未收口运行,revert 把会话标记回某个历史边界,并可在确认后清理边界之后的消息和 part。
基本语义
SessionRevertService 通过 ports.session_revert 接入 agent。实际执行由 revert_runtime 属性返回的 SessionRevertRuntime 完成,核心操作有三个:
| 操作 | 作用 | 是否删除消息 |
|---|---|---|
revert(request, persistence) | 标记回滚边界,恢复文件状态,写入 session.revert 和 summary。 | 否 |
unrevert(*, session_id, persistence) | 撤销当前回滚状态,恢复到回滚前文件快照。 | 否 |
cleanup(*, session, persistence) | 确认回滚,删除边界之后的消息和 part,并清除 session.revert。 | 是 |
revert 和 cleanup 是两步操作。这样 UI 可以先展示回滚后的 diff 与会话状态,再由业务确认是否真正删除后续内容。
前置条件
回滚依赖持久化历史,因此需要使用可读取历史的 Persistence。跨进程或服务重启后执行回滚时,通常使用 SqlPersistence。
from we0agent.domain.models.agent import We0AgentPorts
from we0agent.snapshot.runtime import LocalFileSystemRuntime
from we0agent.builtins.service.session_revert import SessionRevertService
file_system = LocalFileSystemRuntime(root="/abs/app-runtime")
session_revert = SessionRevertService(
session_id="ses_demo",
file_system=file_system,
worktree="/abs/project",
)
ports = We0AgentPorts(session_revert=session_revert)worktree 是 agent 读写文件的目录。即使案例只回滚对话,也建议传入明确的 runtime root 和 worktree,使回滚能力的装配方式保持一致。
消息级对话回滚
消息级对话回滚适合撤销某条用户消息之后的 assistant 输出。没有文件副作用时,回滚主要改变会话状态:写入 session.revert,并在 cleanup 时删除边界之后的消息和 part。
from we0agent.domain.session.revert import SessionRevertRequest
messages = await persistence.messages("ses_demo")
target_message = next(message for message in messages if message.info.role == "user")
session = await session_revert.revert_runtime.revert(
SessionRevertRequest(
session_id="ses_demo",
message_id=target_message.info.id,
),
persistence,
)
# UI 可读取 session.revert,展示当前回滚边界
assert session.revert is not None
# 撤销回滚,回到 revert 前状态
restored = await session_revert.revert_runtime.unrevert(
session_id="ses_demo",
persistence=persistence,
)
# 再次回滚并确认删除
session = await session_revert.revert_runtime.revert(
SessionRevertRequest(
session_id="ses_demo",
message_id=target_message.info.id,
),
persistence,
)
await session_revert.revert_runtime.cleanup(session=session, persistence=persistence)message_id 应来自已持久化的用户消息。业务 UI 通常在展示会话列表时保存这个标识;后台服务也可以通过 await persistence.messages(session_id) 读取消息后选择边界。
Part 级对话回滚
Part 级回滚适合保留同一条消息的前半段,只撤销某个 part 及其后续内容。例如 UI 允许选中某个工具调用或文本段落时,可以把该 part 的 id 作为边界。
from we0agent.domain.session.revert import SessionRevertRequest
messages = await persistence.messages("ses_demo")
target_message = messages[-1]
target_part = target_message.parts[0]
session = await session_revert.revert_runtime.revert(
SessionRevertRequest(
session_id="ses_demo",
message_id=target_message.info.id,
part_id=target_part.id,
),
persistence,
)
# 业务确认后删除该 part 及其后续 part,并删除后续消息
await session_revert.revert_runtime.cleanup(session=session, persistence=persistence)如果目标 part 前面没有可保留的 text 或 tool part,回滚会退化为消息级边界。这个判断由 SessionRevertService 内部完成。
回滚后继续对话
回滚后可以显式调用 cleanup() 再继续对话,也可以直接发起下一轮 prompt。We0Agent 在提交新用户消息前会清理当前 session.revert。
session = await session_revert.revert_runtime.revert(
SessionRevertRequest(
session_id="ses_demo",
message_id=target_message.info.id,
),
persistence,
)
await session_revert.revert_runtime.cleanup(session=session, persistence=persistence)
await agent.invoke(
abort=asyncio.Event(),
session_id="ses_demo",
messages=[user_message("从回滚点重新回答上一轮问题。")],
)代码和对话一起回滚案例
代码回滚适合 agent 修改了 worktree 文件的场景。agent 运行时记录 snapshot,回滚时同时恢复文件状态和会话边界。
import asyncio
from pathlib import Path
from we0agent.agent.agent import We0Agent
from we0agent.domain.models.agent import We0AgentPorts
from we0agent.domain.session.revert import SessionRevertRequest
from we0agent.snapshot.runtime import LocalFileSystemRuntime
from we0agent.builtins.service.session_revert import SessionRevertService
session_id = "ses_code"
worktree = Path("/abs/project")
file_system = LocalFileSystemRuntime(root="/abs/app-runtime")
session_revert = SessionRevertService(
session_id=session_id,
file_system=file_system,
worktree=worktree,
)
agent = We0Agent(
name="coder",
model=model,
tools=tools,
system_prompt=prompt,
persistence=persistence,
ports=We0AgentPorts(session_revert=session_revert),
)
await agent.invoke(
abort=asyncio.Event(),
session_id=session_id,
messages=[user_message("修改 src/app.py,并说明改动。")],
)
session = await session_revert.revert_runtime.revert(
SessionRevertRequest(
session_id=session_id,
message_id="msg_user_change",
),
persistence,
)
# session.summary 记录回滚后的文件增删统计
# session.revert.diff 记录从回滚快照到当前状态的 diff 文本
# 取消回滚,恢复到 revert 前文件快照
restored = await session_revert.revert_runtime.unrevert(session_id=session_id, persistence=persistence)
# 再次回滚并确认清理会话历史
session = await session_revert.revert_runtime.revert(
SessionRevertRequest(
session_id=session_id,
message_id="msg_user_change",
),
persistence,
)
await session_revert.revert_runtime.cleanup(session=session, persistence=persistence)这类回滚会产生两类结果:会话层写入 session.revert 与 summary;文件层把 worktree 恢复到目标边界对应的状态。若业务确认结果,可以继续调用 cleanup 删除边界之后的会话内容;若需要取消,则调用 unrevert。
边界选择
SessionRevertRequest 支持消息级和 part 级两种边界:
| 字段 | 含义 |
|---|---|
session_id | 目标会话。 |
message_id | 回滚边界所在消息。 |
part_id | 可选;填写后精确回滚到某个 part。 |
消息级回滚适合撤销某轮用户输入之后的所有响应。part 级回滚适合保留同一条 assistant message 前半段内容,只撤销某个工具调用或后续输出。
与其它能力的关系
| 能力 | 关系 |
|---|---|
| Persistence | 回滚读取并修改会话历史,必须使用同一个持久化后端。 |
| Snapshot | 文件恢复依赖 snapshot;工具写到 worktree 外时无法恢复。 |
| Status | 回滚执行前会通过 runner manager 做 busy 判定。 |
| Events | 回滚后会发布 session.diff 事件,供 UI 刷新 diff。 |
易错点
- 会话正在运行时执行回滚会抛
We0BusyError,应先等待 idle 或执行 abort。 revert只标记回滚状态,不删除消息;确认删除必须调用cleanup。unrevert只能撤销尚未 cleanup 的回滚。- 文件恢复只覆盖 snapshot 追踪的 worktree,外部副作用仍需业务工具自行处理。
session_id、persistence和SessionRevertService绑定的会话必须一致。
下一步
- 接入 snapshot:Snapshots。
- 查看内建类参数:Built-ins: SessionRevertService。
- 查看可运行入口:Examples: conversation_revert_feature。