会话删除协议
正式服务端契约为 AppServer services/contracts/session_deletion.py、routers/rt_session.py、
openapi.json 及 docs/PROTOCOL.md 的 Session Deletion 节。客户端声明位于
protocol/schema/session_deletion.py,HTTP 调用位于 protocol/client/session_deletion.py。
SessionDeletionRequest 冻结 request_id、根 cid/sid 和最多 255 个后代身份。请求不接受
客户端指定所有者;服务端使用认证主体逐项核对。后代范围由本地代理树拥有者提供,协议层
不扫描图、读取文件或关闭运行资源。重复目标、额外字段及不匹配的身份均不能成为合法回执。
delete_sessions() 向 POST /session/delete 提交一次请求。只有 HTTP 200 且根身份、请求 ID、
status=deleted 和完整目标集合均匹配时,才返回 SessionDeletionReceipt。
get_session_deletion() 使用 GET /session/delete?request_id=... 查询同一意图,执行同样校验。
两者复用现有端点和应用鉴权配置,不生成新请求身份,不隐式重提命令。
| 结果 | 客户端含义 |
|---|---|
| 匹配的完成回执 | 远端删除完成;本地持久化清理仍由本地所有者完成 |
session_busy / owner_mismatch / session_missing / request_id_conflict |
本次提交被明确拒绝,不能显示删除成功 |
传输失败、非法回执、服务端错误或 session_cleanup_pending |
结果未知,保留原意图并查询恢复 |
查询返回 request_not_found 或查询被拒绝 |
未获得原请求的完成证据,不代表原请求已取消 |
| 调用被取消 | 取消信号向生命周期所有者传播,不推断远端回滚 |
服务端将显式集合中的 Session、Turn、Queue、Transcript、事件及其关联执行记录原子删除。 活动或未 settled 的 Turn、未决 Effect 阻止整个集合删除;独立 fork 保留自己的历史副本, 不因来源删除而自动删除。未创建的后代也会记录身份墓碑,防止迟到创建。 服务端保留不含正文的幂等回执、身份墓碑及缓存停写标记,旧身份不可重用。 用户文件、共享配置、外部工具效果和独立 Agent/MCP 连接身份不属于远端会话删除范围。
本地资源分别归属于历史 Store(游标、用量、待完成 fork)、Transcript Store/Writer、
子代理图持久化(关系和邮箱)、Run Store(事件、快照、远端请求、outbox、事实)、
Effect Journal(独立保存工具 Effect 的 cid/sid 归属)和审批 Store。Run outbox 的
effect_id 是本地派发身份,与服务端工具 Effect 不属于同一身份空间,不能用来互相关联。
协议层不直接操作这些资源。
根 Harness 的 RootConversationSession.delete_session() 是删除生命周期入口。它在会话变更锁内
核验冻结身份、收束子代理并持久化删除意图,再通过 ProtocolSessionDeletionAdapter 提交请求;适配器
只转换正式 SDK 的请求、回执和拒绝/未知结果,不在协议层发现本地资源。只有远端完整回执确认后,
才退休运行身份,分发 SessionEnd(reason="deleted") 并调用本地清理 Store。Hook、事件报告关闭和后台 flush
仍由各自 owner 负责,清理 Store 取得 Transcript 独占锁并写入持久停写标记后,迟到写入会被
已有触发器和文件标记拒绝,不能重新创建旧会话记录。
本地持久化删除
SQLiteSessionDeletionStore 通过 SessionDeletionStore 端口接收 LocalDeletionPlan。
调用方在发送前调用 prepare();调用 delete() 前必须确认远端删除并关闭目标运行资源;该 Store 不发送远端请求,
不结束任务或关闭 Hook。阻塞的文件及 SQLite 操作应由调用方调度到工作线程。
计划显式固定根身份、请求身份、完整会话集合和已确认映射的本地 Run Session 身份。根身份
不能从目标排序或当前会话推断;缺少根身份的计划不能恢复。CLI/TUI 的确定性
本地身份由现有身份派生函数补齐;Run Store 同时核对 Command 中冻结的远端绑定与已持久化
的远端请求映射,尚未发出请求的 queued Run 也属于清理范围。不能将
线上 sid 当作 Run 的 session_id。目标内代理图若仍包含集合外子会话,清理拒绝完成,
不能静默扩大远端已经确认的范围。
历史库中的 session_deletions 在首次网络请求前提交计划和所有存储的实际路径,并在同一事务
拒绝其他请求占有重叠目标。只有首次提交被明确拒绝时可释放意图;查询失败不能释放它。
恢复使用原计划和完整回执,即使当前绑定另一会话也不会替换根身份。文件适配器校验
会话标识、目录及实际路径,拒绝符号链接、Windows 重解析点、硬链接和非普通文件,并先
取得全部目标的独占锁。Transcript writer 在打开期间持有共享锁;其他进程仍在写入时,
删除报告占用错误,释放前已取得的锁。进程退出会释放操作系统锁。
文件锁取得后先保存 Transcript 停写标记,再依序清理 Effect/工具结果、审批事实、Run 的 关联表、Transcript 正文、代理图及邮箱、历史游标/用量/待完成 fork。各数据库在自己的 事务中同时提交停写标记与删除,触发器拒绝新插入、更新及其他进程的迟到写入。 整个过程不声称拥有跨数据库及文件的原子事务。
仅当所有步骤完成时,计划标记为完成。文件缺失可以继续;占用、权限或 SQLite 错误向上
传播,已完成的清理不回滚,原计划由 pending() 读取并幂等重试。同请求的范围变化、
存储目录或时区导致的路径变化均被拒绝,避免在新位置执行空清理后误报旧位置已删除。
目标关系保存在计划中,恢复不依赖可能已过期的游标或已删除的代理图。
Effect Journal 创建时要求明确的会话坐标;旧表仅从保存的正式核对结果迁移归属。 缺少归属证据的旧 Effect 会导致本地清理明确失败并保留待恢复计划,不猜测所有者、删除 无关记录或报告成功。已提交的确定结果及未确定效果的幂等语义保持不变。
最小身份停写记录、已完成请求的身份回执、空的 .deleted 标记和稳定的 .lock 文件不
保存正文且不自动过期。只有整个本地状态目录被显式重置并停用所有使用它的进程时,才可
一起移除;单独回收标记会失去旧身份不可重用的保证。用户文件、共享配置、永久权限规则
及对照会话不属于清理范围。
当前已提供正式协议 SDK、本地持久化清理、Harness 生命周期协调,以及 TUI /delete 命令。
命令使用原生菜单布局,按 Codex 确认菜单展示默认取消、子会话范围和不可撤销警告;
确认后再次核对打开菜单时的坐标,再以唯一请求 ID 提交删除。前台等待期间输入被屏障
接管,只有完整 deleted 回执才退出并抑制旧会话的恢复提示。rejected、unknown、
local_failed 等结果留在当前界面并显示对应反馈,取消菜单不发送远端请求。
/delete recover <请求ID> 只查询原请求,并复用运行资源关闭与本地清理边界;重启后同样可用。
本地准备失败与远端确认后的清理失败分别展示,取消等待不等于撤销删除。恢复其他会话成功时
保留当前会话;已完成请求重复恢复直接返回持久完成事实。
删除与删除恢复等待不启用转圈动画,输入屏障、重复提交合并和取消后的对账语义保持一致。 完整成功后由统一收尾打印删除前冻结的可靠非零用量,不保留普通 resume 指引;无可靠计数时静默。 未决删除后主动退出只提示原请求的恢复命令,不声称删除成功。终端收尾保留会话历史与滚屏。
真实服务验收
退出摘要联调使用 python -m tests.manual.session_delete_live --directory <全新隔离目录> --exit-summary。
它通过完整源码入口和真实 Provider 校验服务端累计、全部退出别名、冷恢复、归档、空会话,以及
服务端删除成功但响应丢失后的跨进程恢复。--route responses 只覆盖隔离配置中的 Provider 路由;
上游未报告缓存明细时保持未知,不能将没有精确用量行误判为显示故障。
需要删除前核对数据库账本时,增加 --pause-before-delete;脚本输出
ready_for_database_verification 后,读取隔离目录的 facts.json 获取测试身份,完成只读核对后在
该目录创建空文件 continue-delete。没有该选项时不暂停。Worker 接管、真实权限拒绝与 SQLite
收尾失败的运维验收复用 tests/manual/exit_summary_live.py 的相应函数,故障范围仅限隔离测试身份。
tests/manual/session_delete_live.py 从完整 mind.py 入口启动原生 TTY,使用用户配置的正式
服务地址和当前 Provider,在新建的配置、工作区与状态目录中创建对照会话、根会话和真实
子代理。回环代理只转发正式服务的响应并记录删除身份与状态,不替代 AppServer 或模型。
在仓库根目录激活虚拟环境后,指定尚不存在的隔离目录:
python -m tests.manual.session_delete_live --directory build/session-delete-live
脚本验收运行中拒绝、菜单默认取消后的继续对话、确认删除、精确根与子会话范围、同请求
重放、不存在目标、新进程本地核验、旧会话恢复被拒绝和对照会话恢复。菜单完成帧与冻结的
Codex 快照比对;远端以正式 /turn/status 和删除回执核验。结构化结果只记录本轮身份与
状态,TTY 画面包含隔离测试对话。用户配置不修改,借用的 Provider 密钥在脚本退出时
从隔离配置移除。
验收后可先重做独立本地核验,再清理清单记载的测试会话和隔离状态;清理保留脱敏证据, 并拒绝删除仍有活动轮次的测试会话:
python -m tests.manual.session_delete_live --directory build/session-delete-live --verify
python -m tests.manual.session_delete_live --directory build/session-delete-live --cleanup
失败时保留终端与请求事实供排查,不将未知结果当作删除成功;在活动轮次结束后执行相同
目录的 --cleanup。服务版本按线上 OpenAPI 版本及摘要记录,不从相邻仓库提交推断部署版本。